Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/vorta/assets/UI/archivetab.ui
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
Expand Down Expand Up @@ -255,6 +258,9 @@
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
Expand Down
89 changes: 82 additions & 7 deletions src/vorta/views/archive_tab.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand All @@ -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):
"""
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down