rework the borg interface
This commit is contained in:
parent
6ad26c4f3e
commit
8c9f7bc329
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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__))))
|
||||
|
|
Loading…
Reference in New Issue