diff --git a/setup.cfg b/setup.cfg index 5b740b7a..db59fcc2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,7 @@ install_requires = keyring apscheduler sentry-sdk + psutil pyobjc-core; sys_platform == 'darwin' pyobjc-framework-Cocoa; sys_platform == 'darwin' tests_require = diff --git a/src/vorta/borg/mount.py b/src/vorta/borg/mount.py index 42ea6bf1..3ad8c189 100644 --- a/src/vorta/borg/mount.py +++ b/src/vorta/borg/mount.py @@ -4,7 +4,7 @@ class BorgMountThread(BorgThread): def started_event(self): - self.updated.emit('Mounting snapshot into folder...') + self.updated.emit('Mounting archive into folder...') @classmethod def prepare(cls, profile): diff --git a/src/vorta/borg/umount.py b/src/vorta/borg/umount.py new file mode 100644 index 00000000..e040f1be --- /dev/null +++ b/src/vorta/borg/umount.py @@ -0,0 +1,33 @@ +import psutil +from .borg_thread import BorgThread + + +class BorgUmountThread(BorgThread): + + def started_event(self): + self.updated.emit('Unmounting archive...') + + @classmethod + def prepare(cls, profile): + ret = super().prepare(profile) + if not ret['ok']: + return ret + else: + ret['ok'] = False # Set back to false, so we can do our own checks here. + + ret['active_mount_points'] = [] + partitions = psutil.disk_partitions(all=True) + for p in partitions: + if p.device == 'borgfs': + ret['active_mount_points'].append(p.mountpoint) + + if len(ret['active_mount_points']) == 0: + ret['message'] = 'No active Borg mounts found.' + return ret + + cmd = ['borg', 'umount', '--log-json'] + + ret['ok'] = True + ret['cmd'] = cmd + + return ret diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py index bac39e47..5eeb0562 100644 --- a/src/vorta/views/archive_tab.py +++ b/src/vorta/views/archive_tab.py @@ -7,6 +7,7 @@ from vorta.borg.list import BorgListThread from vorta.borg.check import BorgCheckThread from vorta.borg.mount import BorgMountThread +from vorta.borg.umount import BorgUmountThread from vorta.utils import get_asset, pretty_bytes, choose_folder_dialog from vorta.models import BackupProfileMixin @@ -20,6 +21,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) + self.mount_point = None header = self.archiveTable.horizontalHeader() header.setVisible(True) @@ -140,9 +142,10 @@ def mount_action(self): params['cmd'][-1] += f'::{snapshot_name}' def receive(): - dir = dialog.selectedFiles() - if dir: - params['cmd'].append(dir[0]) + mount_point = dialog.selectedFiles() + if mount_point: + params['cmd'].append(mount_point[0]) + self.mount_point = mount_point[0] if params['ok']: self._toggle_all_buttons(False) thread = BorgMountThread(params['cmd'], params, parent=self) @@ -157,6 +160,38 @@ def mount_result(self, result): self._toggle_all_buttons(True) if result['returncode'] == 0: self._set_status('Mounted successfully.') + self.mountButton.setText('Unmount') + self.mountButton.clicked.disconnect() + self.mountButton.clicked.connect(self.umount_action) + else: + self.mount_point = None + + def umount_action(self): + if self.mount_point is not None: + profile = self.profile() + params = BorgUmountThread.prepare(profile) + if not params['ok']: + self._set_status(params['message']) + return + + if self.mount_point in params['active_mount_points']: + params['cmd'].append(self.mount_point) + thread = BorgUmountThread(params['cmd'], params, parent=self) + thread.updated.connect(self.mountErrors.setText) + thread.result.connect(self.umount_result) + thread.start() + else: + self._set_status('Mount point not active. Try restarting Vorta.') + return + + def umount_result(self, result): + self._toggle_all_buttons(True) + if result['returncode'] == 0: + self._set_status('Un-mounted successfully.') + self.mountButton.setText('Mount') + self.mountButton.clicked.disconnect() + self.mountButton.clicked.connect(self.mount_action) + self.mount_point = None def save_prune_setting(self, new_value): profile = self.profile()