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.
This commit is contained in:
real-yfprojects 2022-08-31 09:21:35 +02:00 committed by Manu
parent b4a7c5494e
commit 5e046386d7
3 changed files with 58 additions and 28 deletions

View File

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

View File

@ -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 <repo> <mountpoint> <path> (-a <archive_pattern>)`
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)

View File

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