add/update and extend various comments
This commit is contained in:
parent
4c3fb85fcf
commit
ab26d952ed
|
@ -9,15 +9,22 @@ from helper import BorgException
|
||||||
|
|
||||||
|
|
||||||
class BorgQtThread(QThread):
|
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):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.create_process()
|
self.create_process()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
"""Kill the process when the thread stops."""
|
||||||
self.p.kill()
|
self.p.kill()
|
||||||
self.json_err = None
|
self.json_err = None
|
||||||
|
|
||||||
def create_process(self):
|
def create_process(self):
|
||||||
|
"""Creates the process which executes borg."""
|
||||||
|
|
||||||
|
# self.create_command() needs to be implemented on each subclass.
|
||||||
self.create_command()
|
self.create_command()
|
||||||
self.p = subprocess.Popen(self.command,
|
self.p = subprocess.Popen(self.command,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
|
@ -30,6 +37,9 @@ class BorgQtThread(QThread):
|
||||||
self.process_json_error(self.json_err)
|
self.process_json_error(self.json_err)
|
||||||
|
|
||||||
def 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:
|
if json_err:
|
||||||
error = json_err.splitlines()[0]
|
error = json_err.splitlines()[0]
|
||||||
if 'stale' in error:
|
if 'stale' in error:
|
||||||
|
@ -40,6 +50,7 @@ class BorgQtThread(QThread):
|
||||||
|
|
||||||
|
|
||||||
class ListThread(BorgQtThread):
|
class ListThread(BorgQtThread):
|
||||||
|
"""Returns a list of all archives in the repository."""
|
||||||
def create_command(self):
|
def create_command(self):
|
||||||
self.command = ['borg', 'list', '--log-json', '--json']
|
self.command = ['borg', 'list', '--log-json', '--json']
|
||||||
|
|
||||||
|
@ -57,6 +68,7 @@ class ListThread(BorgQtThread):
|
||||||
|
|
||||||
|
|
||||||
class InfoThread(BorgQtThread):
|
class InfoThread(BorgQtThread):
|
||||||
|
"""Return the statistics about the current repository."""
|
||||||
def create_command(self):
|
def create_command(self):
|
||||||
self.command = ['borg', 'info', '--log-json', '--json']
|
self.command = ['borg', 'info', '--log-json', '--json']
|
||||||
|
|
||||||
|
@ -72,7 +84,7 @@ class InfoThread(BorgQtThread):
|
||||||
|
|
||||||
|
|
||||||
class BackupThread(BorgQtThread):
|
class BackupThread(BorgQtThread):
|
||||||
"""A class to create a backup with borg.
|
"""Creates a backup with borg.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prefix (str) the prefix for the archive name.
|
prefix (str) the prefix for the archive name.
|
||||||
|
@ -95,12 +107,14 @@ class BackupThread(BorgQtThread):
|
||||||
self.command.extend(self.excludes)
|
self.command.extend(self.excludes)
|
||||||
|
|
||||||
def _process_prefix(self, prefix):
|
def _process_prefix(self, prefix):
|
||||||
|
"""Prepares the prefix for the final command."""
|
||||||
if prefix:
|
if prefix:
|
||||||
self.prefix = prefix + "_"
|
self.prefix = prefix + "_"
|
||||||
else:
|
else:
|
||||||
self.prefix = ""
|
self.prefix = ""
|
||||||
|
|
||||||
def _process_excludes(self, excludes):
|
def _process_excludes(self, excludes):
|
||||||
|
"""Pairs every exclude with the required option for borg."""
|
||||||
processed_items = []
|
processed_items = []
|
||||||
if excludes:
|
if excludes:
|
||||||
for item in excludes:
|
for item in excludes:
|
||||||
|
@ -111,7 +125,7 @@ class BackupThread(BorgQtThread):
|
||||||
|
|
||||||
|
|
||||||
class RestoreThread(BorgQtThread):
|
class RestoreThread(BorgQtThread):
|
||||||
"""A lass to restore a backup with borg.
|
"""Restores a backup with borg.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
archive_name (str) the name of the archive to restore.
|
archive_name (str) the name of the archive to restore.
|
||||||
|
@ -127,6 +141,9 @@ class RestoreThread(BorgQtThread):
|
||||||
('::' + self.archive_name)]
|
('::' + self.archive_name)]
|
||||||
|
|
||||||
def create_process(self):
|
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.create_command()
|
||||||
self.p = subprocess.Popen(self.command,
|
self.p = subprocess.Popen(self.command,
|
||||||
cwd=self.restore_path,
|
cwd=self.restore_path,
|
||||||
|
@ -137,10 +154,10 @@ class RestoreThread(BorgQtThread):
|
||||||
|
|
||||||
|
|
||||||
class DeleteThread(BorgQtThread):
|
class DeleteThread(BorgQtThread):
|
||||||
"""A lass to restore a backup with borg.
|
"""Deletes an archive from the repository.
|
||||||
|
|
||||||
Args:
|
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):
|
def __init__(self, archive_name):
|
||||||
self.archive_name = archive_name
|
self.archive_name = archive_name
|
||||||
|
@ -152,10 +169,11 @@ class DeleteThread(BorgQtThread):
|
||||||
|
|
||||||
|
|
||||||
class MountThread(BorgQtThread):
|
class MountThread(BorgQtThread):
|
||||||
"""A lass to restore a backup with borg.
|
"""Mounts an archive at the given path.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
archive_name (str) the name of the archive to restore.
|
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):
|
def __init__(self, archive_name, mount_path):
|
||||||
self.archive_name = archive_name
|
self.archive_name = archive_name
|
||||||
|
|
|
@ -33,23 +33,38 @@ def convert_size(size_bytes):
|
||||||
|
|
||||||
|
|
||||||
def open_path(target_path):
|
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):
|
if os.path.exists(target_path):
|
||||||
QDesktopServices.openUrl(QUrl.fromLocalFile(
|
QDesktopServices.openUrl(QUrl.fromLocalFile(
|
||||||
os.path.abspath(target_path)))
|
os.path.abspath(target_path)))
|
||||||
|
|
||||||
|
|
||||||
def create_path(path):
|
def create_path(path):
|
||||||
|
"""Creates the given path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str) The path to create."""
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|
||||||
|
|
||||||
def remove_path(path):
|
def remove_path(path):
|
||||||
|
"""Removes the given path recursively.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str) The path to delete."""
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
|
|
||||||
|
|
||||||
def check_path(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):
|
if os.access(path, os.W_OK):
|
||||||
return True
|
return True
|
||||||
exception = Exception("The selected path isn't writeable!")
|
exception = Exception("The selected path isn't writeable!")
|
||||||
|
|
|
@ -72,6 +72,8 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def closeEvent(self, *args, **kwargs):
|
def closeEvent(self, *args, **kwargs):
|
||||||
super(QMainWindow, self).closeEvent(*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:
|
if self.mount_paths:
|
||||||
for path in self.mount_paths:
|
for path in self.mount_paths:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
|
@ -88,6 +90,7 @@ class MainWindow(QMainWindow):
|
||||||
self.src_path = self.treeview_files.model().filePath(signal)
|
self.src_path = self.treeview_files.model().filePath(signal)
|
||||||
|
|
||||||
def _check_path(self):
|
def _check_path(self):
|
||||||
|
"""Makes sure that the user selected a path to backup."""
|
||||||
message = ("Please select a file or directory "
|
message = ("Please select a file or directory "
|
||||||
"before creating a backup.")
|
"before creating a backup.")
|
||||||
if not hasattr(self, 'src_path'):
|
if not hasattr(self, 'src_path'):
|
||||||
|
@ -130,6 +133,8 @@ class MainWindow(QMainWindow):
|
||||||
target_path = None
|
target_path = None
|
||||||
show_error(error)
|
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:
|
if check_path(target_path) and archive_name:
|
||||||
try:
|
try:
|
||||||
restore_path = os.path.join(target_path, archive_name)
|
restore_path = os.path.join(target_path, archive_name)
|
||||||
|
@ -153,7 +158,9 @@ class MainWindow(QMainWindow):
|
||||||
archive_name = None
|
archive_name = None
|
||||||
show_error(error)
|
show_error(error)
|
||||||
|
|
||||||
|
# Only continue if an archive was selected.
|
||||||
if archive_name:
|
if archive_name:
|
||||||
|
# Prompt the user before continuing.
|
||||||
if self.yes_no("Do you want to delete this archive?"):
|
if self.yes_no("Do you want to delete this archive?"):
|
||||||
try:
|
try:
|
||||||
thread = borg.DeleteThread(archive_name)
|
thread = borg.DeleteThread(archive_name)
|
||||||
|
@ -175,7 +182,7 @@ class MainWindow(QMainWindow):
|
||||||
self.list_archive.addItems(archive_names)
|
self.list_archive.addItems(archive_names)
|
||||||
|
|
||||||
def update_ui(self):
|
def update_ui(self):
|
||||||
"""Lists all the archive names in the UI."""
|
"""Updates the archive list and repository stats in the UI."""
|
||||||
try:
|
try:
|
||||||
self._update_archives()
|
self._update_archives()
|
||||||
self._update_repository_stats()
|
self._update_repository_stats()
|
||||||
|
@ -183,6 +190,8 @@ class MainWindow(QMainWindow):
|
||||||
show_error(e)
|
show_error(e)
|
||||||
|
|
||||||
def _update_repository_stats(self):
|
def _update_repository_stats(self):
|
||||||
|
"""Update the repository stats and display them in a human readable
|
||||||
|
format."""
|
||||||
thread = borg.InfoThread()
|
thread = borg.InfoThread()
|
||||||
stats = thread.run()
|
stats = thread.run()
|
||||||
self.label_repo_original_size.setText(
|
self.label_repo_original_size.setText(
|
||||||
|
@ -198,6 +207,8 @@ class MainWindow(QMainWindow):
|
||||||
+ convert_size(stats['unique_csize']))
|
+ convert_size(stats['unique_csize']))
|
||||||
|
|
||||||
def mount_backup(self):
|
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:
|
try:
|
||||||
archive_name = self.selected_archive
|
archive_name = self.selected_archive
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -205,9 +216,11 @@ class MainWindow(QMainWindow):
|
||||||
archive_name = None
|
archive_name = None
|
||||||
show_error(error)
|
show_error(error)
|
||||||
|
|
||||||
|
# only continue if the user selected an archive
|
||||||
if archive_name:
|
if archive_name:
|
||||||
mount_path = os.path.join('/tmp/', archive_name)
|
mount_path = os.path.join('/tmp/', archive_name)
|
||||||
create_path(mount_path)
|
create_path(mount_path)
|
||||||
|
# only continue if the mount_path is writeable
|
||||||
if os.access(mount_path, os.W_OK):
|
if os.access(mount_path, os.W_OK):
|
||||||
thread = borg.MountThread(archive_name, mount_path)
|
thread = borg.MountThread(archive_name, mount_path)
|
||||||
try:
|
try:
|
||||||
|
@ -218,9 +231,14 @@ class MainWindow(QMainWindow):
|
||||||
show_error(e)
|
show_error(e)
|
||||||
remove_path(mount_path)
|
remove_path(mount_path)
|
||||||
else:
|
else:
|
||||||
|
# Opens the path in a file manager
|
||||||
open_path(mount_path)
|
open_path(mount_path)
|
||||||
|
|
||||||
def yes_no(self, question):
|
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,
|
button_reply = QMessageBox.question(self, 'Borg-Qt', question,
|
||||||
QMessageBox.Yes |
|
QMessageBox.Yes |
|
||||||
QMessageBox.No, QMessageBox.No)
|
QMessageBox.No, QMessageBox.No)
|
||||||
|
|
|
@ -6,8 +6,11 @@ from PyQt5 import uic
|
||||||
|
|
||||||
|
|
||||||
class ProgressDialog(QDialog):
|
class ProgressDialog(QDialog):
|
||||||
"""The main window of the application. It provides the various functions to
|
"""Displays a progress dialog while a thread is running. When the thread
|
||||||
control BorgBackup."""
|
stops, the dialog disappears.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
thread (thread) the thread to execute."""
|
||||||
def __init__(self, thread):
|
def __init__(self, thread):
|
||||||
super(ProgressDialog, self).__init__()
|
super(ProgressDialog, self).__init__()
|
||||||
# Load the UI file to get the dialogs layout.
|
# Load the UI file to get the dialogs layout.
|
||||||
|
|
Loading…
Reference in New Issue