From 8c9f7bc329f745c8f8aca362a393797152d3dd1f Mon Sep 17 00:00:00 2001 From: Andreas Zweili Date: Thu, 7 Feb 2019 21:35:14 +0100 Subject: [PATCH] rework the borg interface --- borg_qt/borg_interface.py | 242 +++++++++++++++++--------------------- borg_qt/main_window.py | 12 +- tests/test_borg.py | 53 +++++---- 3 files changed, 148 insertions(+), 159 deletions(-) diff --git a/borg_qt/borg_interface.py b/borg_qt/borg_interface.py index a71d01d..b369147 100644 --- a/borg_qt/borg_interface.py +++ b/borg_qt/borg_interface.py @@ -5,71 +5,73 @@ import json from PyQt5.QtCore import QThread -from helper import BorgException, create_path, remove_path +from helper import BorgException -def _process_json_error(json_err): - if json_err: - error = json_err.splitlines()[0] - if 'stale' in error: - pass - else: - err = json.loads(error) - raise BorgException(err['message']) +class BorgQtThread(QThread): + def __init__(self): + super().__init__() + self.create_pocess() + + def stop(self): + self.p.kill() + self.json_err = None + + def create_pocess(self): + self.create_command() + self.p = subprocess.Popen(self.command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding='utf8') + + def run(self): + self.json_output, self.json_err = self.p.communicate() + self.p.wait() + self.process_json_error(self.json_err) + + def process_json_error(self, json_err): + if json_err: + error = json_err.splitlines()[0] + if 'stale' in error: + pass + else: + err = json.loads(error) + raise BorgException(err['message']) -def _process_json_archives(json_output): - archives = [] - if json_output: - output = json.loads(json_output) - for i in output['archives']: - archives.append(i) - return archives +class ListThread(BorgQtThread): + def create_command(self): + self.command = ['borg', 'list', '--log-json', '--json'] + + def run(self): + super().run() + self._process_json_archives() + return self.archives + + def _process_json_archives(self): + self.archives = [] + if self.json_output: + output = json.loads(self.json_output) + for i in output['archives']: + self.archives.append(i) -def _process_json_repo_stats(json_output): - if json_output: - output = json.loads(json_output) - stats = output['cache']['stats'] - return stats +class InfoThread(BorgQtThread): + def create_command(self): + self.command = ['borg', 'info', '--log-json', '--json'] + + def run(self): + super().run() + self._process_json_repo_stats() + return self.stats + + def _process_json_repo_stats(self): + if self.json_output: + output = json.loads(self.json_output) + self.stats = output['cache']['stats'] -def _get_json_archives(): - p = subprocess.Popen(['borg', 'list', '--log-json', '--json'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf8') - json_output, json_err = p.communicate() - _process_json_error(json_err) - return json_output - - -def _process_prefix(prefix): - if prefix: - return prefix + "_" - else: - return "" - - -def _process_includes(includes): - processed_items = [] - for item in includes: - processed_items.append(item) - return processed_items - - -def _process_excludes(excludes): - processed_items = [] - if excludes: - for item in excludes: - processed_items.extend(['-e', item]) - return processed_items - else: - return processed_items - - -class BackupThread(QThread): +class BackupThread(BorgQtThread): """A class to create a backup with borg. Args: @@ -78,15 +80,10 @@ class BackupThread(QThread): excludes (list) a list of all the paths to exclude from the backup. """ def __init__(self, includes, excludes=None, prefix=None): + self._process_includes(includes) + self._process_excludes(excludes) + self._process_prefix(prefix) super().__init__() - self.includes = _process_includes(includes) - self.excludes = _process_excludes(excludes) - self.prefix = _process_prefix(prefix) - self.create_command() - - def stop(self): - self.p.kill() - self.json_err = None def create_command(self): self.command = ['borg', 'create', '--log-json', '--json', @@ -97,17 +94,29 @@ class BackupThread(QThread): if self.excludes: self.command.extend(self.excludes) - def run(self): - self.p = subprocess.Popen(self.command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf8') - self.json_output, self.json_err = self.p.communicate() - self.p.wait() - _process_json_error(self.json_err) + def _process_prefix(self, prefix): + if prefix: + self.prefix = prefix + "_" + else: + self.prefix = "" + + def _process_includes(self, includes): + processed_items = [] + for item in includes: + processed_items.append(item) + self.includes = processed_items + + def _process_excludes(self, excludes): + processed_items = [] + if excludes: + for item in excludes: + processed_items.extend(['-e', item]) + self.excludes = processed_items + else: + self.excludes = processed_items -class RestoreThread(QThread): +class RestoreThread(BorgQtThread): """A lass to restore a backup with borg. Args: @@ -115,87 +124,50 @@ class RestoreThread(QThread): restore_path (str) the path where to restore should get stored at. """ def __init__(self, archive_name, restore_path): - super().__init__() self.archive_name = archive_name self.restore_path = restore_path + super().__init__() - def stop(self): - self.p.kill() - remove_path(self.restore_path) - self.json_err = None + def create_command(self): + self.command = ['borg', 'extract', '--log-json', + ('::' + self.archive_name)] - def run(self): - create_path(self.restore_path) - self.p = subprocess.Popen(['borg', 'extract', '--log-json', - ('::' - + self.archive_name)], + def create_pocess(self): + self.create_command() + self.p = subprocess.Popen(self.command, cwd=self.restore_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf8') - self.json_output, self.json_err = self.p.communicate() - self.p.wait() - _process_json_error(self.json_err) -class DeleteThread(QThread): +class DeleteThread(BorgQtThread): """A lass to restore a backup with borg. Args: archive_name (str) the name of the archive to restore. """ def __init__(self, archive_name): - super().__init__() self.archive_name = archive_name + super().__init__() - def stop(self): - self.p.kill() - self.json_err = None - - def run(self): - self.p = subprocess.Popen(['borg', 'delete', '--log-json', - ('::' - + self.archive_name)], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf8') - self.json_output, self.json_err = self.p.communicate() - self.p.wait() - _process_json_error(self.json_err) + def create_command(self): + self.command = ['borg', 'delete', '--log-json', + ('::' + self.archive_name)] -def backup(includes, excludes=None, prefix=None): - thread = BackupThread(includes, excludes=excludes, prefix=prefix) - thread.run() +class MountThread(BorgQtThread): + """A lass to restore a backup with borg. + Args: + archive_name (str) the name of the archive to restore. + """ + def __init__(self, archive_name, mount_path): + self.archive_name = archive_name + self.mount_path = mount_path + super().__init__() -def get_archives(): - """Returns a list of all the archives in the repository.""" - return _process_json_archives(_get_json_archives()) - - -def get_repository_stats(): - p = subprocess.Popen(['borg', 'info', '--log-json', '--json'], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf8') - json_output, json_err = p.communicate() - _process_json_error(json_err) - return _process_json_repo_stats(json_output) - - -def mount(archive_name, mount_path): - p = subprocess.Popen(['borg', 'mount', '--log-json', - ('::' - + archive_name), - mount_path], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding='utf8') - json_output, json_err = p.communicate() - p.wait() - _process_json_error(json_err) + def create_command(self): + self.command = ['borg', 'mount', '--log-json', + ('::' + self.archive_name), self.mount_path] diff --git a/borg_qt/main_window.py b/borg_qt/main_window.py index 0130274..d9d7d75 100644 --- a/borg_qt/main_window.py +++ b/borg_qt/main_window.py @@ -132,6 +132,7 @@ class MainWindow(QMainWindow): if target_path and archive_name: try: restore_path = os.path.join(target_path, archive_name) + create_path(restore_path) thread = borg.RestoreThread(archive_name, restore_path) dialog = ProgressDialog(thread) dialog.label_info.setText( @@ -140,6 +141,7 @@ class MainWindow(QMainWindow): open_path(restore_path) except BorgException as e: show_error(e) + remove_path(restore_path) def delete_backup(self): """Deletes the selected archive from the repository.""" @@ -163,9 +165,10 @@ class MainWindow(QMainWindow): def _update_archives(self): """Lists all the archive names in the UI.""" + thread = borg.ListThread() self.list_archive.clear() archive_names = [] - for archive in borg.get_archives(): + for archive in thread.run(): archive_names.append(archive['name']) self.list_archive.addItems(archive_names) @@ -178,7 +181,8 @@ class MainWindow(QMainWindow): show_error(e) def _update_repository_stats(self): - stats = borg.get_repository_stats() + thread = borg.InfoThread() + stats = thread.run() self.label_repo_original_size.setText( "Original Size: " + convert_size(stats['total_size'])) @@ -203,11 +207,13 @@ class MainWindow(QMainWindow): mount_path = os.path.join('/tmp/', archive_name) create_path(mount_path) if os.access(mount_path, os.W_OK): + thread = borg.MountThread(archive_name, mount_path) try: - borg.mount(archive_name, mount_path) + thread.run() self.mount_paths.append(mount_path) open_path(mount_path) except BorgException as e: show_error(e) + remove_path(mount_path) else: open_path(mount_path) diff --git a/tests/test_borg.py b/tests/test_borg.py index d9981ae..9405ce0 100644 --- a/tests/test_borg.py +++ b/tests/test_borg.py @@ -16,12 +16,14 @@ app = QApplication(sys.argv) class BackupTestCase(BorgInterfaceTest): def test_backup(self): - borg.backup(['.']) + self.backup_thread = borg.BackupThread(['.']) + self.backup_thread.run() output = subprocess.check_output(['borg', 'list'], encoding='utf8') self.assertNotEqual(-1, output.find(strftime('%Y-%m-%d_%H:'))) def test_backup_with_prefix(self): - borg.backup(['.'], prefix='test') + self.backup_thread = borg.BackupThread(['.'], prefix='test') + self.backup_thread.run() output = subprocess.check_output(['borg', 'list'], encoding='utf8') self.assertNotEqual(-1, output.find(strftime('test_%Y-%m-%d_%H:'))) @@ -29,52 +31,61 @@ class BackupTestCase(BorgInterfaceTest): class RestoreTestCase(BorgInterfaceTest): def setUp(self): super().setUp() - borg.backup(['.']) + self.backup_thread = borg.BackupThread(['.']) + self.backup_thread.run() + self.target_path = '/tmp/restore/' + self.list_thread = borg.ListThread() + repo_archives = self.list_thread.run() + self.archive_name = repo_archives[0]['name'] + self.restore_path = os.path.join(self.target_path, self.archive_name) + create_path(self.restore_path) def tearDown(self): remove_path(self.target_path) super().tearDown() def test_restore(self): - repo_archives = borg.get_archives() - archive_name = repo_archives[0]['name'] - self.target_path = '/tmp/restore/' - restore_path = os.path.join(self.target_path, archive_name) - thread = borg.RestoreThread(archive_name, restore_path) + thread = borg.RestoreThread(self.archive_name, self.restore_path) thread.run() self.assertTrue(os.path.exists( - os.path.join(restore_path, os.path.realpath(__file__)))) + os.path.join(self.restore_path, os.path.realpath(__file__)))) class DeleteTestCase(BorgInterfaceTest): def setUp(self): super().setUp() - borg.backup(['.']) + self.backup_thread = borg.BackupThread(['.']) + self.backup_thread.run() + self.list_thread = borg.ListThread() + repo_archives = self.list_thread.run() + self.archive_name = repo_archives[0]['name'] def test_delete(self): - repo_archives = borg.get_archives() - archive_name = repo_archives[0]['name'] - thread = borg.DeleteThread(archive_name) + thread = borg.DeleteThread(self.archive_name) thread.run() - repo_archives = borg.get_archives() + self.list_thread = borg.ListThread() + repo_archives = self.list_thread.run() self.assertEqual(repo_archives, []) class MountTestCase(BorgInterfaceTest): def setUp(self): super().setUp() - borg.backup(['.']) + self.backup_thread = borg.BackupThread(['.']) + self.backup_thread.run() + self.list_thread = borg.ListThread() + repo_archives = self.list_thread.run() + self.archive_name = repo_archives[0]['name'] + self.mount_path = os.path.join('/tmp/', self.archive_name) + create_path(self.mount_path) def tearDown(self): os.system('borg umount ' + self.mount_path) remove_path(self.mount_path) super().tearDown() - def test_restore(self): - repo_archives = borg.get_archives() - archive_name = repo_archives[0]['name'] - self.mount_path = os.path.join('/tmp/', archive_name) - create_path(self.mount_path) - borg.mount(archive_name, self.mount_path) + def test_mount(self): + thread = borg.MountThread(self.archive_name, self.mount_path) + thread.run() self.assertTrue(os.path.exists( os.path.join(self.mount_path, os.path.realpath(__file__))))