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 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)
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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__))))
|
||||||
|
|
Loading…
Reference in New Issue