From 5e046386d75a3aff5b6b4fb7435cbf0ba15dfc51 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Wed, 31 Aug 2022 09:21:35 +0200 Subject: [PATCH] Specify archive using `-a` for borg v2 `mount`. * src/vorta/views/archive_tab.py : Move all command building logic into `mount.py`. * src/vorta/borg/mount.py (BorgMountJob.prepare): Add command building logic previously in other places. * src/vorta/borg/mount.py (BorgMountJob.prepare): Use `-a` command line option to select a single archive. * src/vorta/utils.py (SHELL_PATTERN_ELEMENT): A pattern that can be used to detect shell pattern syntax. * src/vorta/utils.py (get_mount_points): Implement parsing a borg v2 cmd. --- src/vorta/borg/mount.py | 22 ++++++++++++--- src/vorta/utils.py | 49 ++++++++++++++++++++++++---------- src/vorta/views/archive_tab.py | 15 +++-------- 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/vorta/borg/mount.py b/src/vorta/borg/mount.py index cac9ef38..d141a2a6 100644 --- a/src/vorta/borg/mount.py +++ b/src/vorta/borg/mount.py @@ -1,15 +1,18 @@ +import logging import os from vorta.store.models import SettingsModel -from vorta.utils import borg_compat +from vorta.utils import SHELL_PATTERN_ELEMENT, borg_compat from .borg_job import BorgJob +logger = logging.getLogger(__name__) + class BorgMountJob(BorgJob): def started_event(self): self.updated.emit(self.tr('Mounting archive into folder…')) @classmethod - def prepare(cls, profile): + def prepare(cls, profile, archive: str = None): ret = super().prepare(profile) if not ret['ok']: return ret @@ -26,8 +29,21 @@ class BorgMountJob(BorgJob): if borg_compat.check('V2'): cmd.extend(["-r", profile.repo.url]) + + if archive: + # in shell patterns ?, * and [...] have a special meaning + pattern = SHELL_PATTERN_ELEMENT.sub(r'\\1', archive) # escape them + cmd.extend(['-a', pattern]) else: - cmd.append(f'{profile.repo.url}') + source = f'{profile.repo.url}' + + if archive: + source += f'::{archive}' + + cmd.append(source) + + if archive: + ret['mounted_archive'] = archive ret['ok'] = True ret['cmd'] = cmd diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 15ca35a2..c3b8e10d 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -358,6 +358,9 @@ def format_archive_name(profile, archive_name_tpl): return archive_name_tpl.format(**available_vars) +SHELL_PATTERN_ELEMENT = re.compile(r'([?\[\]*])') + + def get_mount_points(repo_url): mount_points = {} repo_mounts = [] @@ -368,23 +371,41 @@ def get_mount_points(repo_url): if 'mount' not in proc.cmdline(): continue - for idx, parameter in enumerate(proc.cmdline()): - if parameter.startswith(repo_url): - # mount from this repo + if borg_compat.check('V2'): + # command line syntax: + # `borg mount -r (-a )` + cmd = proc.cmdline() + if repo_url in cmd: + i = cmd.index(repo_url) + if len(cmd) > i + 1: + mount_point = cmd[i + 1] - # The borg mount command specifies that the mount_point - # parameter comes after the archive name - if len(proc.cmdline()) > idx + 1: - mount_point = proc.cmdline()[idx + 1] - - # archive or full mount? - if parameter[len(repo_url) :].startswith('::'): - archive_name = parameter[len(repo_url) + 2 :] - mount_points[archive_name] = mount_point - break + # Archive mount? + ao = '-a' in cmd + if ao or '--glob-archives' in cmd: + i = cmd.index('-a' if ao else '--glob-archives') + if len(cmd) >= i + 1 and not SHELL_PATTERN_ELEMENT.search(cmd[i + 1]): + mount_points[mount_point] = cmd[i + 1] else: - # repo mount point repo_mounts.append(mount_point) + else: + for idx, parameter in enumerate(proc.cmdline()): + if parameter.startswith(repo_url): + # mount from this repo + + # The borg mount command specifies that the mount_point + # parameter comes after the archive name + if len(proc.cmdline()) > idx + 1: + mount_point = proc.cmdline()[idx + 1] + + # archive or full mount? + if parameter[len(repo_url) :].startswith('::'): + archive_name = parameter[len(repo_url) + 2 :] + mount_points[archive_name] = mount_point + break + else: + # repo mount point + repo_mounts.append(mount_point) except (psutil.ZombieProcess, psutil.AccessDenied, psutil.NoSuchProcess): # Getting process details may fail (e.g. zombie process on macOS) diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py index a9b73952..622fed5e 100644 --- a/src/vorta/views/archive_tab.py +++ b/src/vorta/views/archive_tab.py @@ -52,7 +52,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): """Init.""" super().__init__(parent) self.setupUi(parent) - self.mount_points = {} # mount points of archives + self.mount_points = {} # mapping of archive name to mount point self.repo_mount_point: Optional[str] = None # mount point of whole repo self.menu = None self.app = app @@ -565,17 +565,11 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): The archive to mount or None, by default None """ profile = self.profile() - params = BorgMountJob.prepare(profile) + params = BorgMountJob.prepare(profile, archive=archive_name) if not params['ok']: self._set_status(params['message']) return - if archive_name: - # mount archive - params['cmd'][-1] += f'::{archive_name}' - params['current_archive'] = archive_name - # else mount complete repo - def receive(): mount_point = dialog.selectedFiles() if mount_point: @@ -598,13 +592,12 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): mount_point = result['params']['mount_point'] - if result['params'].get('current_archive'): + if result['params'].get('mounted_archive'): # archive was mounted - archive_name = result['params']['current_archive'] + archive_name = result['params']['mounted_archive'] self.mount_points[archive_name] = mount_point # update column in table - archive_name = result['params']['current_archive'] row = self.row_of_archive(archive_name) item = QTableWidgetItem(result['cmd'][-1]) self.archiveTable.setItem(row, 3, item)