rework the borg interface

This commit is contained in:
Andreas Zweili 2019-02-07 21:35:14 +01:00
parent 6ad26c4f3e
commit 8c9f7bc329
3 changed files with 148 additions and 159 deletions

View File

@ -5,71 +5,73 @@ import json
from PyQt5.QtCore import QThread from PyQt5.QtCore import QThread
from helper import BorgException, create_path, remove_path from helper import BorgException
def _process_json_error(json_err): class BorgQtThread(QThread):
if json_err: def __init__(self):
error = json_err.splitlines()[0] super().__init__()
if 'stale' in error: self.create_pocess()
pass
else: def stop(self):
err = json.loads(error) self.p.kill()
raise BorgException(err['message']) 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): class ListThread(BorgQtThread):
archives = [] def create_command(self):
if json_output: self.command = ['borg', 'list', '--log-json', '--json']
output = json.loads(json_output)
for i in output['archives']: def run(self):
archives.append(i) super().run()
return archives 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): class InfoThread(BorgQtThread):
if json_output: def create_command(self):
output = json.loads(json_output) self.command = ['borg', 'info', '--log-json', '--json']
stats = output['cache']['stats']
return stats 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(): class BackupThread(BorgQtThread):
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):
"""A class to create a backup with borg. """A class to create a backup with borg.
Args: Args:
@ -78,15 +80,10 @@ class BackupThread(QThread):
excludes (list) a list of all the paths to exclude from the backup. excludes (list) a list of all the paths to exclude from the backup.
""" """
def __init__(self, includes, excludes=None, prefix=None): def __init__(self, includes, excludes=None, prefix=None):
self._process_includes(includes)
self._process_excludes(excludes)
self._process_prefix(prefix)
super().__init__() 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): def create_command(self):
self.command = ['borg', 'create', '--log-json', '--json', self.command = ['borg', 'create', '--log-json', '--json',
@ -97,17 +94,29 @@ class BackupThread(QThread):
if self.excludes: if self.excludes:
self.command.extend(self.excludes) self.command.extend(self.excludes)
def run(self): def _process_prefix(self, prefix):
self.p = subprocess.Popen(self.command, if prefix:
stdout=subprocess.PIPE, self.prefix = prefix + "_"
stderr=subprocess.PIPE, else:
encoding='utf8') self.prefix = ""
self.json_output, self.json_err = self.p.communicate()
self.p.wait() def _process_includes(self, includes):
_process_json_error(self.json_err) 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. """A lass to restore a backup with borg.
Args: Args:
@ -115,87 +124,50 @@ class RestoreThread(QThread):
restore_path (str) the path where to restore should get stored at. restore_path (str) the path where to restore should get stored at.
""" """
def __init__(self, archive_name, restore_path): def __init__(self, archive_name, restore_path):
super().__init__()
self.archive_name = archive_name self.archive_name = archive_name
self.restore_path = restore_path self.restore_path = restore_path
super().__init__()
def stop(self): def create_command(self):
self.p.kill() self.command = ['borg', 'extract', '--log-json',
remove_path(self.restore_path) ('::' + self.archive_name)]
self.json_err = None
def run(self): def create_pocess(self):
create_path(self.restore_path) self.create_command()
self.p = subprocess.Popen(['borg', 'extract', '--log-json', self.p = subprocess.Popen(self.command,
('::'
+ self.archive_name)],
cwd=self.restore_path, cwd=self.restore_path,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
encoding='utf8') 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. """A lass to restore 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.
""" """
def __init__(self, archive_name): def __init__(self, archive_name):
super().__init__()
self.archive_name = archive_name self.archive_name = archive_name
super().__init__()
def stop(self): def create_command(self):
self.p.kill() self.command = ['borg', 'delete', '--log-json',
self.json_err = None ('::' + self.archive_name)]
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 backup(includes, excludes=None, prefix=None): class MountThread(BorgQtThread):
thread = BackupThread(includes, excludes=excludes, prefix=prefix) """A lass to restore a backup with borg.
thread.run()
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(): def create_command(self):
"""Returns a list of all the archives in the repository.""" self.command = ['borg', 'mount', '--log-json',
return _process_json_archives(_get_json_archives()) ('::' + self.archive_name), self.mount_path]
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)

View File

@ -132,6 +132,7 @@ class MainWindow(QMainWindow):
if target_path and archive_name: if 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)
create_path(restore_path)
thread = borg.RestoreThread(archive_name, restore_path) thread = borg.RestoreThread(archive_name, restore_path)
dialog = ProgressDialog(thread) dialog = ProgressDialog(thread)
dialog.label_info.setText( dialog.label_info.setText(
@ -140,6 +141,7 @@ class MainWindow(QMainWindow):
open_path(restore_path) open_path(restore_path)
except BorgException as e: except BorgException as e:
show_error(e) show_error(e)
remove_path(restore_path)
def delete_backup(self): def delete_backup(self):
"""Deletes the selected archive from the repository.""" """Deletes the selected archive from the repository."""
@ -163,9 +165,10 @@ class MainWindow(QMainWindow):
def _update_archives(self): def _update_archives(self):
"""Lists all the archive names in the UI.""" """Lists all the archive names in the UI."""
thread = borg.ListThread()
self.list_archive.clear() self.list_archive.clear()
archive_names = [] archive_names = []
for archive in borg.get_archives(): for archive in thread.run():
archive_names.append(archive['name']) archive_names.append(archive['name'])
self.list_archive.addItems(archive_names) self.list_archive.addItems(archive_names)
@ -178,7 +181,8 @@ class MainWindow(QMainWindow):
show_error(e) show_error(e)
def _update_repository_stats(self): def _update_repository_stats(self):
stats = borg.get_repository_stats() thread = borg.InfoThread()
stats = thread.run()
self.label_repo_original_size.setText( self.label_repo_original_size.setText(
"Original Size: " "Original Size: "
+ convert_size(stats['total_size'])) + convert_size(stats['total_size']))
@ -203,11 +207,13 @@ class MainWindow(QMainWindow):
mount_path = os.path.join('/tmp/', archive_name) mount_path = os.path.join('/tmp/', archive_name)
create_path(mount_path) create_path(mount_path)
if os.access(mount_path, os.W_OK): if os.access(mount_path, os.W_OK):
thread = borg.MountThread(archive_name, mount_path)
try: try:
borg.mount(archive_name, mount_path) thread.run()
self.mount_paths.append(mount_path) self.mount_paths.append(mount_path)
open_path(mount_path) open_path(mount_path)
except BorgException as e: except BorgException as e:
show_error(e) show_error(e)
remove_path(mount_path)
else: else:
open_path(mount_path) open_path(mount_path)

View File

@ -16,12 +16,14 @@ app = QApplication(sys.argv)
class BackupTestCase(BorgInterfaceTest): class BackupTestCase(BorgInterfaceTest):
def test_backup(self): def test_backup(self):
borg.backup(['.']) self.backup_thread = borg.BackupThread(['.'])
self.backup_thread.run()
output = subprocess.check_output(['borg', 'list'], encoding='utf8') output = subprocess.check_output(['borg', 'list'], encoding='utf8')
self.assertNotEqual(-1, output.find(strftime('%Y-%m-%d_%H:'))) self.assertNotEqual(-1, output.find(strftime('%Y-%m-%d_%H:')))
def test_backup_with_prefix(self): 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') output = subprocess.check_output(['borg', 'list'], encoding='utf8')
self.assertNotEqual(-1, output.find(strftime('test_%Y-%m-%d_%H:'))) self.assertNotEqual(-1, output.find(strftime('test_%Y-%m-%d_%H:')))
@ -29,52 +31,61 @@ class BackupTestCase(BorgInterfaceTest):
class RestoreTestCase(BorgInterfaceTest): class RestoreTestCase(BorgInterfaceTest):
def setUp(self): def setUp(self):
super().setUp() 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): def tearDown(self):
remove_path(self.target_path) remove_path(self.target_path)
super().tearDown() super().tearDown()
def test_restore(self): def test_restore(self):
repo_archives = borg.get_archives() thread = borg.RestoreThread(self.archive_name, self.restore_path)
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.run() thread.run()
self.assertTrue(os.path.exists( 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): class DeleteTestCase(BorgInterfaceTest):
def setUp(self): def setUp(self):
super().setUp() 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): def test_delete(self):
repo_archives = borg.get_archives() thread = borg.DeleteThread(self.archive_name)
archive_name = repo_archives[0]['name']
thread = borg.DeleteThread(archive_name)
thread.run() thread.run()
repo_archives = borg.get_archives() self.list_thread = borg.ListThread()
repo_archives = self.list_thread.run()
self.assertEqual(repo_archives, []) self.assertEqual(repo_archives, [])
class MountTestCase(BorgInterfaceTest): class MountTestCase(BorgInterfaceTest):
def setUp(self): def setUp(self):
super().setUp() 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): def tearDown(self):
os.system('borg umount ' + self.mount_path) os.system('borg umount ' + self.mount_path)
remove_path(self.mount_path) remove_path(self.mount_path)
super().tearDown() super().tearDown()
def test_restore(self): def test_mount(self):
repo_archives = borg.get_archives() thread = borg.MountThread(self.archive_name, self.mount_path)
archive_name = repo_archives[0]['name'] thread.run()
self.mount_path = os.path.join('/tmp/', archive_name)
create_path(self.mount_path)
borg.mount(archive_name, self.mount_path)
self.assertTrue(os.path.exists( self.assertTrue(os.path.exists(
os.path.join(self.mount_path, os.path.realpath(__file__)))) os.path.join(self.mount_path, os.path.realpath(__file__))))