diff --git a/src/vorta/assets/UI/archivetab.ui b/src/vorta/assets/UI/archivetab.ui
index 9c81d49f2..5f49f4215 100644
--- a/src/vorta/assets/UI/archivetab.ui
+++ b/src/vorta/assets/UI/archivetab.ui
@@ -124,6 +124,9 @@
Qt::ToolButtonTextBesideIcon
+
+ QToolButton::InstantPopup
+
@@ -255,6 +258,9 @@
Qt::ToolButtonTextBesideIcon
+
+ QToolButton::InstantPopup
+
-
diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py
index d2af5757b..c5953fc23 100644
--- a/src/vorta/views/archive_tab.py
+++ b/src/vorta/views/archive_tab.py
@@ -1,12 +1,16 @@
import logging
+import os
+import random
+import shutil
+import string
import sys
from datetime import timedelta
from typing import Dict, Optional
-
from PyQt6 import QtCore, uic
-from PyQt6.QtCore import QItemSelectionModel, QMimeData, QPoint, Qt, pyqtSlot
+from PyQt6.QtCore import QItemSelectionModel, QMimeData, QPoint, Qt, QUrl, pyqtSlot
from PyQt6.QtGui import QDesktopServices, QKeySequence, QShortcut
from PyQt6.QtWidgets import (
+ QAction,
QAbstractItemView,
QApplication,
QHeaderView,
@@ -125,7 +129,6 @@ def __init__(self, parent=None, app=None):
self.archiveTable.selectionModel().selectionChanged.connect(self.on_selection_change)
# connect archive actions
- self.bMountArchive.clicked.connect(self.bmountarchive_clicked)
self.bRefreshArchive.clicked.connect(self.refresh_archive_info)
self.bRename.clicked.connect(self.cell_double_clicked)
self.bDelete.clicked.connect(self.delete_action)
@@ -137,7 +140,17 @@ def __init__(self, parent=None, app=None):
self.bPrune.clicked.connect(self.prune_action)
self.bCheck.clicked.connect(self.check_action)
self.bDiff.clicked.connect(self.diff_action)
- self.bMountRepo.clicked.connect(self.bmountrepo_clicked)
+ self.menuMountArchive = QMenu(self.bMountArchive)
+ self.menuMountArchive.addAction(translate("MountArchive", "Mount to Folder…"), self.bmountarchive_clicked)
+ self.menuMountArchive.addAction(
+ translate("MountArchive", "Quick Mount…"), lambda: self.bmountarchive_clicked(True)
+ )
+ self.bMountArchive.setMenu(self.menuMountArchive)
+
+ self.menuMountRepo = QMenu(self.bMountRepo)
+ self.menuMountRepo.addAction(translate("MountRepo", "Mount to Folder…"), self.bmountrepo_clicked)
+ self.menuMountRepo.addAction(translate("MountRepo", "Quick Mount…"), self.quick_mount_action)
+ self.bMountRepo.setMenu(self.menuMountRepo)
self.archiveNameTemplate.textChanged.connect(
lambda tpl, key='new_archive_name': self.save_archive_template(tpl, key)
@@ -399,6 +412,7 @@ def on_selection_change(self, selected=None, deselected=None):
# special treatment for dynamic mount/unmount button.
self.bmountarchive_refresh()
+ self.bmountrepo_refresh()
tooltip = self.bMountArchive.toolTip()
self.bMountArchive.setToolTip(tooltip + " " + reason)
@@ -546,7 +560,7 @@ def selected_archive_name(self):
return archive_cell.text()
return None
- def bmountarchive_clicked(self):
+ def bmountarchive_clicked(self, quick=False):
"""
Handle `bMountArchive` being clicked.
@@ -560,9 +574,46 @@ def bmountarchive_clicked(self):
if archive_name in self.mount_points:
self.unmount_action(archive_name=archive_name)
+ elif quick:
+ self.quick_mount_action(archive_name=archive_name)
else:
self.mount_action(archive_name=archive_name)
+ def get_vorta_quick_mountpoint(self):
+ """
+ return a temporary directory in the user's home folder ~/vorta-quick-mount-{randomcharacters}
+ """
+ return os.path.join(
+ os.path.expanduser('~'),
+ 'vorta-quick-mount-' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)),
+ )
+
+ def quick_mount_action(self, archive_name=None):
+ """
+ mount the selected archive or repository to a temporary directory.
+ """
+ profile = self.profile()
+ params = BorgMountJob.prepare(profile, archive=archive_name)
+ if not params['ok']:
+ self._set_status(params['message'])
+ return
+
+ mount_point = self.get_vorta_quick_mountpoint()
+ while os.path.exists(mount_point) and os.listdir(mount_point):
+ mount_point = self.get_vorta_quick_mountpoint()
+
+ os.mkdir(mount_point)
+
+ params['cmd'].append(mount_point)
+ params['mount_point'] = mount_point
+
+ if params['ok']:
+ self._toggle_all_buttons(False)
+ job = BorgMountJob(params['cmd'], params, self.profile().repo.id)
+ job.updated.connect(self.mountErrors.setText)
+ job.result.connect(lambda result: self.mount_result(result, quick=True))
+ self.app.jobs_manager.add_job(job)
+
def bmountrepo_clicked(self):
"""
Handle `bMountRepo` being clicked.
@@ -588,11 +639,22 @@ def bmountarchive_refresh(self, icon_only=False):
if not icon_only:
self.bMountArchive.setText(self.tr("Unmount"))
self.bMountArchive.setToolTip(self.tr('Unmount the selected archive from the file system'))
+ self.bMountArchive.setMenu(None)
+ try:
+ self.bMountArchive.clicked.disconnect(self.bmountarchive_clicked) # avoid race condition
+ self.bMountArchive.clicked.connect(self.bmountarchive_clicked)
+ except TypeError:
+ self.bMountArchive.clicked.connect(self.bmountarchive_clicked)
else:
self.bMountArchive.setIcon(get_colored_icon('folder-open'))
if not icon_only:
self.bMountArchive.setText(self.tr("Mount…"))
self.bMountArchive.setToolTip(self.tr("Mount the selected archive " + "as a folder in the file system"))
+ try:
+ self.bMountArchive.clicked.disconnect(self.bmountarchive_clicked)
+ self.bMountArchive.setMenu(self.menuMountArchive)
+ except TypeError: # when bMountArchive.clicked is not connected
+ pass
def bmountrepo_refresh(self):
"""
@@ -605,10 +667,18 @@ def bmountrepo_refresh(self):
self.bMountRepo.setText(self.tr("Unmount"))
self.bMountRepo.setToolTip(self.tr('Unmount the repository from the file system'))
self.bMountRepo.setIcon(get_colored_icon('eject'))
+ self.bMountRepo.setMenu(None)
+ self.bMountRepo.clicked.connect(self.bmountrepo_clicked)
else:
+ try:
+ # disconnect the button to open the menu
+ self.bMountRepo.clicked.disconnect(self.bmountrepo_clicked)
+ except TypeError: # on first run, when the button is not connected
+ pass
self.bMountRepo.setText(self.tr("Mount…"))
self.bMountRepo.setIcon(get_colored_icon('folder-open'))
self.bMountRepo.setToolTip(self.tr("Mount the repository as a folder in the file system"))
+ self.bMountRepo.setMenu(self.menuMountRepo)
def mount_action(self, archive_name=None):
"""
@@ -644,12 +714,16 @@ def receive():
dialog = choose_file_dialog(self, self.tr("Choose Mount Point"), want_folder=True)
dialog.open(receive)
- def mount_result(self, result):
+ def mount_result(self, result, quick=False):
if result['returncode'] == 0:
self._set_status(self.tr('Mounted successfully.'))
mount_point = result['params']['mount_point']
+ if quick:
+ # open the folder
+ QDesktopServices.openUrl(QUrl.fromLocalFile(mount_point))
+
if result['params'].get('mounted_archive'):
# archive was mounted
archive_name = result['params']['mounted_archive']
@@ -706,6 +780,8 @@ def umount_result(self, result):
if result['returncode'] == 0:
self._set_status(self.tr('Un-mounted successfully.'))
+ if os.path.basename(mount_point).startswith("vorta-quick-mount-"):
+ shutil.rmtree(mount_point)
if archive_name:
# unmount single archive
@@ -714,7 +790,6 @@ def umount_result(self, result):
item = QTableWidgetItem('')
self.archiveTable.setItem(row, 3, item)
- # update button
self.bmountarchive_refresh()
else:
# unmount repo