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 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]

View File

@ -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)

View File

@ -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__))))