From ab26d952edea7f066b9895dbf7a7f45ae4bf4193 Mon Sep 17 00:00:00 2001 From: Andreas Zweili Date: Sun, 10 Feb 2019 17:21:41 +0100 Subject: [PATCH] add/update and extend various comments --- borg_qt/borg_interface.py | 28 +++++++++++++++++++++++----- borg_qt/helper.py | 17 ++++++++++++++++- borg_qt/main_window.py | 20 +++++++++++++++++++- borg_qt/progress.py | 7 +++++-- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/borg_qt/borg_interface.py b/borg_qt/borg_interface.py index b08fd33..e87a278 100644 --- a/borg_qt/borg_interface.py +++ b/borg_qt/borg_interface.py @@ -9,15 +9,22 @@ from helper import BorgException class BorgQtThread(QThread): + """Provides the base for interfacing with borg. The method + self.create_command needs to be implemented on each child class in order to + make it work.""" def __init__(self): super().__init__() self.create_process() def stop(self): + """Kill the process when the thread stops.""" self.p.kill() self.json_err = None def create_process(self): + """Creates the process which executes borg.""" + + # self.create_command() needs to be implemented on each subclass. self.create_command() self.p = subprocess.Popen(self.command, stdout=subprocess.PIPE, @@ -30,6 +37,9 @@ class BorgQtThread(QThread): self.process_json_error(self.json_err) def process_json_error(self, json_err): + """Looks in the returned json error string for errors and provides them + as BorgException in case there are any. Ignores errors about stale + locks of borg.""" if json_err: error = json_err.splitlines()[0] if 'stale' in error: @@ -40,6 +50,7 @@ class BorgQtThread(QThread): class ListThread(BorgQtThread): + """Returns a list of all archives in the repository.""" def create_command(self): self.command = ['borg', 'list', '--log-json', '--json'] @@ -57,6 +68,7 @@ class ListThread(BorgQtThread): class InfoThread(BorgQtThread): + """Return the statistics about the current repository.""" def create_command(self): self.command = ['borg', 'info', '--log-json', '--json'] @@ -72,7 +84,7 @@ class InfoThread(BorgQtThread): class BackupThread(BorgQtThread): - """A class to create a backup with borg. + """Creates a backup with borg. Args: prefix (str) the prefix for the archive name. @@ -95,12 +107,14 @@ class BackupThread(BorgQtThread): self.command.extend(self.excludes) def _process_prefix(self, prefix): + """Prepares the prefix for the final command.""" if prefix: self.prefix = prefix + "_" else: self.prefix = "" def _process_excludes(self, excludes): + """Pairs every exclude with the required option for borg.""" processed_items = [] if excludes: for item in excludes: @@ -111,7 +125,7 @@ class BackupThread(BorgQtThread): class RestoreThread(BorgQtThread): - """A lass to restore a backup with borg. + """Restores a backup with borg. Args: archive_name (str) the name of the archive to restore. @@ -127,6 +141,9 @@ class RestoreThread(BorgQtThread): ('::' + self.archive_name)] def create_process(self): + """The create_process needs to get overwritten because borg restores + the archive into the current folder. Therefore the process needs to cd + into the target path.""" self.create_command() self.p = subprocess.Popen(self.command, cwd=self.restore_path, @@ -137,10 +154,10 @@ class RestoreThread(BorgQtThread): class DeleteThread(BorgQtThread): - """A lass to restore a backup with borg. + """Deletes an archive from the repository. Args: - archive_name (str) the name of the archive to restore. + archive_name (str) the name of the archive to delete. """ def __init__(self, archive_name): self.archive_name = archive_name @@ -152,10 +169,11 @@ class DeleteThread(BorgQtThread): class MountThread(BorgQtThread): - """A lass to restore a backup with borg. + """Mounts an archive at the given path. Args: archive_name (str) the name of the archive to restore. + mount_path (str) the target path to mount the archive at. """ def __init__(self, archive_name, mount_path): self.archive_name = archive_name diff --git a/borg_qt/helper.py b/borg_qt/helper.py index 0cb93a0..1399d9c 100644 --- a/borg_qt/helper.py +++ b/borg_qt/helper.py @@ -33,23 +33,38 @@ def convert_size(size_bytes): def open_path(target_path): - """Opens the file manager at the given location.""" + """Opens the file manager at the given location. + + Args: + target_path (str) The path to open in the file manager.""" if os.path.exists(target_path): QDesktopServices.openUrl(QUrl.fromLocalFile( os.path.abspath(target_path))) def create_path(path): + """Creates the given path. + + Args: + path (str) The path to create.""" if not os.path.exists(path): os.makedirs(path) def remove_path(path): + """Removes the given path recursively. + + Args: + path (str) The path to delete.""" if os.path.exists(path): shutil.rmtree(path) def check_path(path): + """Checks if the given path is writeable. + + Args: + path (str) The path to check.""" if os.access(path, os.W_OK): return True exception = Exception("The selected path isn't writeable!") diff --git a/borg_qt/main_window.py b/borg_qt/main_window.py index 54f4861..163c18c 100644 --- a/borg_qt/main_window.py +++ b/borg_qt/main_window.py @@ -72,6 +72,8 @@ class MainWindow(QMainWindow): def closeEvent(self, *args, **kwargs): super(QMainWindow, self).closeEvent(*args, **kwargs) + # When the application gets close unmount all the archives and remove + # their paths. if self.mount_paths: for path in self.mount_paths: if os.path.exists(path): @@ -88,6 +90,7 @@ class MainWindow(QMainWindow): self.src_path = self.treeview_files.model().filePath(signal) def _check_path(self): + """Makes sure that the user selected a path to backup.""" message = ("Please select a file or directory " "before creating a backup.") if not hasattr(self, 'src_path'): @@ -130,6 +133,8 @@ class MainWindow(QMainWindow): target_path = None show_error(error) + # Only restore the backup if the target is writeable and the archive + # was selected. if check_path(target_path) and archive_name: try: restore_path = os.path.join(target_path, archive_name) @@ -153,7 +158,9 @@ class MainWindow(QMainWindow): archive_name = None show_error(error) + # Only continue if an archive was selected. if archive_name: + # Prompt the user before continuing. if self.yes_no("Do you want to delete this archive?"): try: thread = borg.DeleteThread(archive_name) @@ -175,7 +182,7 @@ class MainWindow(QMainWindow): self.list_archive.addItems(archive_names) def update_ui(self): - """Lists all the archive names in the UI.""" + """Updates the archive list and repository stats in the UI.""" try: self._update_archives() self._update_repository_stats() @@ -183,6 +190,8 @@ class MainWindow(QMainWindow): show_error(e) def _update_repository_stats(self): + """Update the repository stats and display them in a human readable + format.""" thread = borg.InfoThread() stats = thread.run() self.label_repo_original_size.setText( @@ -198,6 +207,8 @@ class MainWindow(QMainWindow): + convert_size(stats['unique_csize'])) def mount_backup(self): + """Mount the selected archive in the tmp directory. If it succeeds the + mount_path gets written to a property of the main_window.""" try: archive_name = self.selected_archive except AttributeError: @@ -205,9 +216,11 @@ class MainWindow(QMainWindow): archive_name = None show_error(error) + # only continue if the user selected an archive if archive_name: mount_path = os.path.join('/tmp/', archive_name) create_path(mount_path) + # only continue if the mount_path is writeable if os.access(mount_path, os.W_OK): thread = borg.MountThread(archive_name, mount_path) try: @@ -218,9 +231,14 @@ class MainWindow(QMainWindow): show_error(e) remove_path(mount_path) else: + # Opens the path in a file manager open_path(mount_path) def yes_no(self, question): + """Simple yes/no dialog. + + Args: + question (str) The question to display to the user.""" button_reply = QMessageBox.question(self, 'Borg-Qt', question, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) diff --git a/borg_qt/progress.py b/borg_qt/progress.py index 233d565..e034285 100644 --- a/borg_qt/progress.py +++ b/borg_qt/progress.py @@ -6,8 +6,11 @@ from PyQt5 import uic class ProgressDialog(QDialog): - """The main window of the application. It provides the various functions to - control BorgBackup.""" + """Displays a progress dialog while a thread is running. When the thread + stops, the dialog disappears. + + Args: + thread (thread) the thread to execute.""" def __init__(self, thread): super(ProgressDialog, self).__init__() # Load the UI file to get the dialogs layout.