2022-03-24 06:27:07 +00:00
|
|
|
import logging
|
2019-01-21 10:44:01 +00:00
|
|
|
import os.path
|
2018-11-22 01:05:54 +00:00
|
|
|
import sys
|
2018-11-01 16:53:41 +00:00
|
|
|
from datetime import timedelta
|
2022-04-16 05:30:31 +00:00
|
|
|
from typing import Dict, Optional
|
2019-07-04 19:17:09 +00:00
|
|
|
from PyQt5 import QtCore, uic
|
2022-05-05 09:21:54 +00:00
|
|
|
from PyQt5.QtCore import QItemSelectionModel, QMimeData, QPoint, Qt, pyqtSlot
|
2022-03-24 06:27:07 +00:00
|
|
|
from PyQt5.QtGui import QDesktopServices, QKeySequence
|
2022-05-05 09:21:54 +00:00
|
|
|
from PyQt5.QtWidgets import (
|
|
|
|
QAction,
|
|
|
|
QApplication,
|
|
|
|
QHeaderView,
|
|
|
|
QInputDialog,
|
|
|
|
QLayout,
|
|
|
|
QMenu,
|
|
|
|
QMessageBox,
|
|
|
|
QShortcut,
|
|
|
|
QTableView,
|
|
|
|
QTableWidgetItem,
|
|
|
|
QWidget,
|
|
|
|
)
|
2021-10-04 11:31:41 +00:00
|
|
|
from vorta.borg.check import BorgCheckJob
|
2022-02-21 17:23:56 +00:00
|
|
|
from vorta.borg.compact import BorgCompactJob
|
2021-10-04 11:31:41 +00:00
|
|
|
from vorta.borg.delete import BorgDeleteJob
|
|
|
|
from vorta.borg.diff import BorgDiffJob
|
|
|
|
from vorta.borg.extract import BorgExtractJob
|
2022-03-24 06:27:07 +00:00
|
|
|
from vorta.borg.info_archive import BorgInfoArchiveJob
|
2021-10-04 11:31:41 +00:00
|
|
|
from vorta.borg.list_archive import BorgListArchiveJob
|
|
|
|
from vorta.borg.list_repo import BorgListRepoJob
|
|
|
|
from vorta.borg.mount import BorgMountJob
|
|
|
|
from vorta.borg.prune import BorgPruneJob
|
|
|
|
from vorta.borg.rename import BorgRenameJob
|
2022-03-24 06:27:07 +00:00
|
|
|
from vorta.borg.umount import BorgUmountJob
|
2021-11-17 09:14:11 +00:00
|
|
|
from vorta.store.models import ArchiveModel, BackupProfileMixin
|
2019-07-04 19:17:09 +00:00
|
|
|
from vorta.utils import choose_file_dialog, format_archive_name, get_asset, get_mount_points, pretty_bytes
|
2022-05-07 10:04:35 +00:00
|
|
|
from vorta.views import diff_result, extract_dialog
|
|
|
|
from vorta.views.diff_result import DiffResultDialog, DiffTree
|
|
|
|
from vorta.views.extract_dialog import ExtractDialog, ExtractTree
|
2022-03-24 06:27:07 +00:00
|
|
|
from vorta.views.source_tab import SizeItem
|
2020-05-31 11:29:26 +00:00
|
|
|
from vorta.views.utils import get_colored_icon
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2018-11-22 01:18:12 +00:00
|
|
|
uifile = get_asset('UI/archivetab.ui')
|
2020-05-31 11:29:26 +00:00
|
|
|
ArchiveTabUI, ArchiveTabBase = uic.loadUiType(uifile)
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2018-11-22 01:18:12 +00:00
|
|
|
class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin):
|
2018-11-04 08:23:17 +00:00
|
|
|
prune_intervals = ['hour', 'day', 'week', 'month', 'year']
|
|
|
|
|
2020-10-30 05:17:24 +00:00
|
|
|
def __init__(self, parent=None, app=None):
|
2022-03-24 06:27:07 +00:00
|
|
|
"""Init."""
|
2018-10-27 17:24:34 +00:00
|
|
|
super().__init__(parent)
|
|
|
|
self.setupUi(parent)
|
2022-04-16 05:30:31 +00:00
|
|
|
self.mount_points = {} # mount points of archives
|
2022-05-05 09:21:54 +00:00
|
|
|
self.repo_mount_point: Optional[str] = None # mount point of whole repo
|
2019-01-21 10:44:01 +00:00
|
|
|
self.menu = None
|
2020-10-30 05:17:24 +00:00
|
|
|
self.app = app
|
2018-12-14 08:03:26 +00:00
|
|
|
self.toolBox.setCurrentIndex(0)
|
2022-03-24 06:27:07 +00:00
|
|
|
self.repoactions_enabled = True
|
|
|
|
|
|
|
|
#: Tooltip dict to save the tooltips set in the designer
|
|
|
|
self.tooltip_dict: Dict[QWidget, str] = {}
|
2022-05-05 09:21:54 +00:00
|
|
|
self.tooltip_dict[self.bDiff] = self.bDiff.toolTip()
|
2022-05-21 10:49:46 +00:00
|
|
|
self.tooltip_dict[self.bDelete] = self.bDelete.toolTip()
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2018-11-22 01:18:12 +00:00
|
|
|
header = self.archiveTable.horizontalHeader()
|
2018-10-27 17:24:34 +00:00
|
|
|
header.setVisible(True)
|
2018-11-02 06:06:25 +00:00
|
|
|
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
2018-10-27 17:24:34 +00:00
|
|
|
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
2018-11-01 16:53:41 +00:00
|
|
|
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
2019-01-28 00:41:42 +00:00
|
|
|
header.setSectionResizeMode(3, QHeaderView.Interactive)
|
2019-01-21 10:44:01 +00:00
|
|
|
header.setSectionResizeMode(4, QHeaderView.Stretch)
|
2018-11-22 05:48:16 +00:00
|
|
|
header.setStretchLastSection(True)
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2018-11-22 01:05:54 +00:00
|
|
|
if sys.platform != 'darwin':
|
|
|
|
self._set_status('') # Set platform-specific hints.
|
|
|
|
|
2018-11-22 01:18:12 +00:00
|
|
|
self.archiveTable.setSelectionBehavior(QTableView.SelectRows)
|
|
|
|
self.archiveTable.setEditTriggers(QTableView.NoEditTriggers)
|
2019-01-28 00:41:42 +00:00
|
|
|
self.archiveTable.setWordWrap(False)
|
|
|
|
self.archiveTable.setTextElideMode(QtCore.Qt.ElideLeft)
|
2018-11-22 07:03:19 +00:00
|
|
|
self.archiveTable.setAlternatingRowColors(True)
|
2019-01-21 10:44:01 +00:00
|
|
|
self.archiveTable.cellDoubleClicked.connect(self.cell_double_clicked)
|
2019-10-02 04:05:40 +00:00
|
|
|
self.archiveTable.setSortingEnabled(True)
|
2022-03-24 06:27:07 +00:00
|
|
|
self.archiveTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
|
|
self.archiveTable.customContextMenuRequested.connect(self.archiveitem_contextmenu)
|
|
|
|
|
|
|
|
# shortcuts
|
|
|
|
shortcut_copy = QShortcut(QKeySequence.StandardKey.Copy, self.archiveTable)
|
|
|
|
shortcut_copy.activated.connect(self.archive_copy)
|
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
# single and double selection feature
|
|
|
|
self.archiveTable.setSelectionMode(QTableView.SelectionMode.ExtendedSelection)
|
|
|
|
self.archiveTable.selectionModel().selectionChanged.connect(self.on_selection_change)
|
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
# connect archive actions
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bMountArchive.clicked.connect(self.bmountarchive_clicked)
|
2022-03-24 06:27:07 +00:00
|
|
|
self.bRefreshArchive.clicked.connect(self.refresh_archive_info)
|
|
|
|
self.bRename.clicked.connect(self.rename_action)
|
|
|
|
self.bDelete.clicked.connect(self.delete_action)
|
|
|
|
self.bExtract.clicked.connect(self.extract_action)
|
2022-02-21 17:23:56 +00:00
|
|
|
self.compactButton.clicked.connect(self.compact_action)
|
2021-02-18 01:44:10 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
# other signals
|
|
|
|
self.bList.clicked.connect(self.refresh_archive_list)
|
|
|
|
self.bPrune.clicked.connect(self.prune_action)
|
|
|
|
self.bCheck.clicked.connect(self.check_action)
|
|
|
|
self.bDiff.clicked.connect(self.diff_action)
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bMountRepo.clicked.connect(self.bmountrepo_clicked)
|
2022-03-24 06:27:07 +00:00
|
|
|
|
2018-12-14 08:03:26 +00:00
|
|
|
self.archiveNameTemplate.textChanged.connect(
|
2022-05-05 09:21:54 +00:00
|
|
|
lambda tpl, key='new_archive_name': self.save_archive_template(tpl, key)
|
|
|
|
)
|
2018-12-14 08:03:26 +00:00
|
|
|
self.prunePrefixTemplate.textChanged.connect(
|
2022-05-05 09:21:54 +00:00
|
|
|
lambda tpl, key='prune_prefix': self.save_archive_template(tpl, key)
|
|
|
|
)
|
2018-12-14 08:03:26 +00:00
|
|
|
|
2018-11-17 08:51:53 +00:00
|
|
|
self.populate_from_profile()
|
2022-08-25 07:11:12 +00:00
|
|
|
self.selected_archives = None # TODO: remove unused variable
|
2021-02-18 01:44:10 +00:00
|
|
|
self.set_icons()
|
2020-03-23 06:20:09 +00:00
|
|
|
|
2022-02-05 18:24:56 +00:00
|
|
|
# Connect to palette change
|
|
|
|
self.app.paletteChanged.connect(lambda p: self.set_icons())
|
|
|
|
|
2020-05-31 11:29:26 +00:00
|
|
|
def set_icons(self):
|
2021-02-18 01:44:10 +00:00
|
|
|
"Used when changing between light- and dark mode"
|
2022-03-24 06:27:07 +00:00
|
|
|
self.bCheck.setIcon(get_colored_icon('check-circle'))
|
|
|
|
self.bDiff.setIcon(get_colored_icon('stream-solid'))
|
|
|
|
self.bPrune.setIcon(get_colored_icon('cut'))
|
|
|
|
self.bList.setIcon(get_colored_icon('refresh'))
|
2022-02-21 17:23:56 +00:00
|
|
|
self.compactButton.setIcon(get_colored_icon('broom-solid'))
|
2020-05-31 11:29:26 +00:00
|
|
|
self.toolBox.setItemIcon(0, get_colored_icon('tasks'))
|
|
|
|
self.toolBox.setItemIcon(1, get_colored_icon('cut'))
|
2022-03-24 06:27:07 +00:00
|
|
|
self.bRefreshArchive.setIcon(get_colored_icon('refresh'))
|
|
|
|
self.bRename.setIcon(get_colored_icon('edit'))
|
|
|
|
self.bDelete.setIcon(get_colored_icon('trash'))
|
|
|
|
self.bExtract.setIcon(get_colored_icon('cloud-download'))
|
|
|
|
|
2022-08-25 07:11:12 +00:00
|
|
|
self.bmountarchive_refresh(icon_only=True)
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bmountrepo_refresh()
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
@pyqtSlot(QPoint)
|
|
|
|
def archiveitem_contextmenu(self, pos: QPoint):
|
|
|
|
# index under cursor
|
|
|
|
index = self.archiveTable.indexAt(pos)
|
|
|
|
if not index.isValid():
|
|
|
|
return # popup only for items
|
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
selected_rows = self.archiveTable.selectionModel().selectedRows(index.column())
|
2022-03-24 06:27:07 +00:00
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
if selected_rows and index not in selected_rows:
|
|
|
|
return # popup only for selected items
|
|
|
|
|
|
|
|
menu = QMenu(self.archiveTable)
|
2022-03-24 06:27:07 +00:00
|
|
|
menu.addAction(
|
|
|
|
get_colored_icon('copy'),
|
|
|
|
self.tr("Copy"),
|
|
|
|
lambda: self.archive_copy(index=index),
|
|
|
|
)
|
|
|
|
menu.addSeparator()
|
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
# archive actions
|
|
|
|
archive_actions = []
|
|
|
|
archive_actions.append(
|
|
|
|
menu.addAction(
|
|
|
|
self.bRefreshArchive.icon(),
|
|
|
|
self.bRefreshArchive.text(),
|
|
|
|
self.refresh_archive_info,
|
2022-05-21 10:49:46 +00:00
|
|
|
)
|
2022-05-05 09:21:54 +00:00
|
|
|
)
|
|
|
|
archive_actions.append(
|
|
|
|
menu.addAction(
|
2022-05-07 06:13:48 +00:00
|
|
|
self.bMountArchive.icon(),
|
|
|
|
self.bMountArchive.text(),
|
2022-05-21 10:49:46 +00:00
|
|
|
self.bmountarchive_clicked,
|
2022-08-15 05:21:14 +00:00
|
|
|
)
|
|
|
|
)
|
2022-05-05 09:21:54 +00:00
|
|
|
archive_actions.append(menu.addAction(self.bExtract.icon(), self.bExtract.text(), self.extract_action))
|
|
|
|
archive_actions.append(menu.addAction(self.bRename.icon(), self.bRename.text(), self.rename_action))
|
2022-05-21 10:49:46 +00:00
|
|
|
# deletion possible with one but also multiple archives
|
|
|
|
menu.addAction(self.bDelete.icon(), self.bDelete.text(), self.delete_action)
|
2022-05-05 09:21:54 +00:00
|
|
|
|
|
|
|
if not (self.repoactions_enabled and len(selected_rows) <= 1):
|
|
|
|
for action in archive_actions:
|
|
|
|
action.setEnabled(False)
|
|
|
|
|
|
|
|
# diff action
|
|
|
|
menu.addSeparator()
|
|
|
|
diff_action = QAction(self.bDiff.icon(), self.bDiff.text(), menu)
|
|
|
|
diff_action.triggered.connect(self.diff_action)
|
|
|
|
menu.addAction(diff_action)
|
|
|
|
|
|
|
|
selected_rows = self.archiveTable.selectionModel().selectedRows(index.column())
|
|
|
|
diff_action.setEnabled(self.repoactions_enabled and len(selected_rows) == 2)
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
menu.popup(self.archiveTable.viewport().mapToGlobal(pos))
|
2020-05-31 11:29:26 +00:00
|
|
|
|
2021-01-20 05:07:32 +00:00
|
|
|
def cancel_action(self):
|
|
|
|
self._set_status(self.tr("Action cancelled."))
|
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
|
2018-11-04 15:37:46 +00:00
|
|
|
def _set_status(self, text):
|
2018-10-28 15:40:38 +00:00
|
|
|
self.mountErrors.setText(text)
|
|
|
|
self.mountErrors.repaint()
|
|
|
|
|
2018-11-04 15:37:46 +00:00
|
|
|
def _toggle_all_buttons(self, enabled=True):
|
2022-03-24 06:27:07 +00:00
|
|
|
"""
|
|
|
|
Set all the buttons in the archive panel to the given state.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
enabled : bool, optional
|
|
|
|
The enabled state, by default True
|
|
|
|
"""
|
|
|
|
self.repoactions_enabled = enabled
|
|
|
|
|
|
|
|
for button in [
|
|
|
|
self.bCheck,
|
|
|
|
self.bList,
|
|
|
|
self.bPrune,
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bDiff,
|
|
|
|
self.fArchiveActions,
|
|
|
|
self.bMountRepo,
|
|
|
|
]:
|
2018-11-27 11:33:16 +00:00
|
|
|
button.setEnabled(enabled)
|
|
|
|
button.repaint()
|
2018-11-04 15:37:46 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
# Restore states
|
|
|
|
self.on_selection_change()
|
2021-02-18 01:44:10 +00:00
|
|
|
|
2018-11-17 08:51:53 +00:00
|
|
|
def populate_from_profile(self):
|
2018-11-27 11:33:16 +00:00
|
|
|
"""Populate archive list and prune settings from profile."""
|
2018-11-17 08:51:53 +00:00
|
|
|
profile = self.profile()
|
2018-11-20 04:06:09 +00:00
|
|
|
if profile.repo is not None:
|
2022-04-16 05:30:31 +00:00
|
|
|
# get mount points
|
|
|
|
self.mount_points, repo_mount_points = get_mount_points(profile.repo.url)
|
|
|
|
if repo_mount_points:
|
|
|
|
self.repo_mount_point = repo_mount_points[0]
|
|
|
|
|
2019-01-20 03:50:10 +00:00
|
|
|
self.toolBox.setItemText(0, self.tr('Archives for %s') % profile.repo.url)
|
2018-11-22 05:48:16 +00:00
|
|
|
archives = [s for s in profile.repo.archives.select().order_by(ArchiveModel.time.desc())]
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2021-02-11 06:19:37 +00:00
|
|
|
sorting = self.archiveTable.isSortingEnabled()
|
|
|
|
self.archiveTable.setSortingEnabled(False)
|
2018-11-22 05:48:16 +00:00
|
|
|
for row, archive in enumerate(archives):
|
2018-11-22 01:18:12 +00:00
|
|
|
self.archiveTable.insertRow(row)
|
2018-11-22 05:48:16 +00:00
|
|
|
|
|
|
|
formatted_time = archive.time.strftime('%Y-%m-%d %H:%M')
|
2018-11-22 01:18:12 +00:00
|
|
|
self.archiveTable.setItem(row, 0, QTableWidgetItem(formatted_time))
|
2020-11-29 00:51:24 +00:00
|
|
|
self.archiveTable.setItem(row, 1, SizeItem(pretty_bytes(archive.size)))
|
2018-11-22 05:48:16 +00:00
|
|
|
if archive.duration is not None:
|
|
|
|
formatted_duration = str(timedelta(seconds=round(archive.duration)))
|
2018-11-01 16:53:41 +00:00
|
|
|
else:
|
2018-11-22 05:48:16 +00:00
|
|
|
formatted_duration = ''
|
2019-01-21 10:44:01 +00:00
|
|
|
|
2018-11-22 01:18:12 +00:00
|
|
|
self.archiveTable.setItem(row, 2, QTableWidgetItem(formatted_duration))
|
2019-01-21 10:44:01 +00:00
|
|
|
|
|
|
|
mount_point = self.mount_points.get(archive.name)
|
|
|
|
if mount_point is not None:
|
2019-01-28 00:41:42 +00:00
|
|
|
item = QTableWidgetItem(mount_point)
|
2019-01-21 10:44:01 +00:00
|
|
|
self.archiveTable.setItem(row, 3, item)
|
|
|
|
|
|
|
|
self.archiveTable.setItem(row, 4, QTableWidgetItem(archive.name))
|
|
|
|
|
2018-11-22 05:48:16 +00:00
|
|
|
self.archiveTable.setRowCount(len(archives))
|
2021-02-11 06:19:37 +00:00
|
|
|
self.archiveTable.setSortingEnabled(sorting)
|
2018-11-27 11:33:16 +00:00
|
|
|
item = self.archiveTable.item(0, 0)
|
|
|
|
self.archiveTable.scrollToItem(item)
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
self.archiveTable.selectionModel().clearSelection()
|
2018-11-17 08:51:53 +00:00
|
|
|
self._toggle_all_buttons(enabled=True)
|
2018-11-02 11:14:54 +00:00
|
|
|
else:
|
2019-01-21 10:44:01 +00:00
|
|
|
self.mount_points = {}
|
2018-11-22 01:18:12 +00:00
|
|
|
self.archiveTable.setRowCount(0)
|
2019-01-20 03:50:10 +00:00
|
|
|
self.toolBox.setItemText(0, self.tr('Archives'))
|
2018-11-17 08:51:53 +00:00
|
|
|
self._toggle_all_buttons(enabled=False)
|
2018-11-04 15:37:46 +00:00
|
|
|
|
2018-12-14 08:03:26 +00:00
|
|
|
self.archiveNameTemplate.setText(profile.new_archive_name)
|
|
|
|
self.prunePrefixTemplate.setText(profile.prune_prefix)
|
|
|
|
|
2019-08-21 12:54:00 +00:00
|
|
|
# Populate pruning options from database
|
|
|
|
profile = self.profile()
|
|
|
|
for i in self.prune_intervals:
|
|
|
|
getattr(self, f'prune_{i}').setValue(getattr(profile, f'prune_{i}'))
|
|
|
|
getattr(self, f'prune_{i}').valueChanged.connect(self.save_prune_setting)
|
|
|
|
self.prune_keep_within.setText(profile.prune_keep_within)
|
|
|
|
self.prune_keep_within.editingFinished.connect(self.save_prune_setting)
|
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
def on_selection_change(self, selected=None, deselected=None):
|
2022-03-24 06:27:07 +00:00
|
|
|
"""
|
|
|
|
React to a change of the selection of the archiveTableView.
|
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
Enables or disables archive actions and the diff button.
|
|
|
|
Makes sure at maximum 2 rows are selected.
|
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
Parameters
|
|
|
|
----------
|
2022-05-05 09:21:54 +00:00
|
|
|
selected : QItemSelection, optional
|
2022-03-24 06:27:07 +00:00
|
|
|
The new selection.
|
2022-05-05 09:21:54 +00:00
|
|
|
deselected : QItemSelection, optional
|
2022-03-24 06:27:07 +00:00
|
|
|
The previous selection.
|
|
|
|
"""
|
2022-05-05 09:21:54 +00:00
|
|
|
# handle selection of more than 2 rows
|
|
|
|
selectionModel: QItemSelectionModel = self.archiveTable.selectionModel()
|
|
|
|
indexes = selectionModel.selectedRows()
|
|
|
|
|
|
|
|
# Toggle archive actions frame
|
|
|
|
layout: QLayout = self.fArchiveActions.layout()
|
|
|
|
|
2022-05-21 10:49:46 +00:00
|
|
|
# toggle delete button
|
|
|
|
if len(indexes) > 0:
|
|
|
|
self.bDelete.setEnabled(True)
|
|
|
|
self.bDelete.setToolTip(self.tooltip_dict.get(self.bDelete, ""))
|
|
|
|
else:
|
|
|
|
self.bDelete.setEnabled(False)
|
|
|
|
tooltip = self.tooltip_dict[self.bDelete]
|
2022-08-25 07:11:12 +00:00
|
|
|
self.bDelete.setToolTip(tooltip + " " + self.tr("(Select minimum one archive)"))
|
2022-05-05 09:21:54 +00:00
|
|
|
|
|
|
|
# Toggle diff button
|
2022-05-21 10:49:46 +00:00
|
|
|
if len(indexes) == 2:
|
2022-05-05 09:21:54 +00:00
|
|
|
# Enable diff button
|
|
|
|
self.bDiff.setEnabled(True)
|
|
|
|
self.bDiff.setToolTip(self.tooltip_dict.get(self.bDiff, ""))
|
|
|
|
else:
|
|
|
|
# disable diff button
|
|
|
|
self.bDiff.setEnabled(False)
|
|
|
|
|
|
|
|
tooltip = self.tooltip_dict[self.bDiff]
|
|
|
|
self.bDiff.setToolTip(tooltip + " " + self.tr("(Select two archives)"))
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
if len(indexes) == 1:
|
|
|
|
# Enable archive actions
|
|
|
|
self.fArchiveActions.setEnabled(True)
|
|
|
|
|
|
|
|
for index in range(layout.count()):
|
|
|
|
widget = layout.itemAt(index).widget()
|
|
|
|
widget.setToolTip(self.tooltip_dict.get(widget, ""))
|
2022-04-16 05:30:31 +00:00
|
|
|
|
2022-05-07 06:13:48 +00:00
|
|
|
# refresh bMountArchive for the selected archive
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bmountarchive_refresh()
|
2022-03-24 06:27:07 +00:00
|
|
|
else:
|
|
|
|
# too few or too many selected.
|
|
|
|
self.fArchiveActions.setEnabled(False)
|
|
|
|
|
|
|
|
for index in range(layout.count()):
|
|
|
|
widget = layout.itemAt(index).widget()
|
|
|
|
tooltip = widget.toolTip()
|
|
|
|
|
|
|
|
tooltip = self.tooltip_dict.setdefault(widget, tooltip)
|
2022-05-05 09:21:54 +00:00
|
|
|
widget.setToolTip(tooltip + " " + self.tr("(Select exactly one archive)"))
|
2022-03-24 06:27:07 +00:00
|
|
|
|
2022-08-25 07:11:12 +00:00
|
|
|
# special treatment for dynamic mount/unmount button.
|
|
|
|
self.bmountarchive_refresh()
|
|
|
|
tooltip = self.bMountArchive.toolTip()
|
|
|
|
self.bMountArchive.setToolTip(tooltip + " " + self.tr("(Select exactly one archive)"))
|
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
def archive_copy(self, index=None):
|
|
|
|
"""
|
|
|
|
Copy an archive name to the clipboard.
|
|
|
|
|
|
|
|
Copies the first selected archive if no index is specified.
|
|
|
|
"""
|
|
|
|
if index is None:
|
|
|
|
indexes = self.archiveTable.selectionModel().selectedRows()
|
|
|
|
|
|
|
|
if not indexes:
|
|
|
|
return
|
|
|
|
|
|
|
|
index = indexes[0]
|
|
|
|
|
|
|
|
archive_name = self.archiveTable.item(index.row(), 4).text()
|
|
|
|
|
|
|
|
data = QMimeData()
|
|
|
|
data.setText(archive_name)
|
|
|
|
|
|
|
|
QApplication.clipboard().setMimeData(data)
|
|
|
|
|
2018-12-14 08:03:26 +00:00
|
|
|
def save_archive_template(self, tpl, key):
|
|
|
|
profile = self.profile()
|
|
|
|
try:
|
2019-01-20 03:50:10 +00:00
|
|
|
preview = self.tr('Preview: %s') % format_archive_name(profile, tpl)
|
2018-12-14 08:03:26 +00:00
|
|
|
setattr(profile, key, tpl)
|
|
|
|
profile.save()
|
|
|
|
except Exception:
|
2019-01-20 03:50:10 +00:00
|
|
|
preview = self.tr('Error in archive name template.')
|
2018-12-14 08:03:26 +00:00
|
|
|
|
|
|
|
if key == 'new_archive_name':
|
|
|
|
self.archiveNamePreview.setText(preview)
|
|
|
|
else:
|
|
|
|
self.prunePrefixPreview.setText(preview)
|
|
|
|
|
2018-11-04 15:37:46 +00:00
|
|
|
def check_action(self):
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgCheckJob.prepare(self.profile())
|
2018-11-23 13:33:31 +00:00
|
|
|
if not params['ok']:
|
2019-02-19 13:42:19 +00:00
|
|
|
self._set_status(params['message'])
|
2018-11-23 13:33:31 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# Conditions are met (borg binary available, etc)
|
|
|
|
row_selected = self.archiveTable.selectionModel().selectedRows()
|
|
|
|
if row_selected:
|
2019-01-21 10:44:01 +00:00
|
|
|
archive_cell = self.archiveTable.item(row_selected[0].row(), 4)
|
2019-01-20 03:50:10 +00:00
|
|
|
if archive_cell:
|
|
|
|
archive_name = archive_cell.text()
|
|
|
|
params['cmd'][-1] += f'::{archive_name}'
|
2018-11-23 13:33:31 +00:00
|
|
|
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgCheckJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
|
|
|
job.result.connect(self.check_result)
|
2018-11-23 13:33:31 +00:00
|
|
|
self._toggle_all_buttons(False)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2018-11-04 15:37:46 +00:00
|
|
|
|
|
|
|
def check_result(self, result):
|
|
|
|
if result['returncode'] == 0:
|
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
|
2022-02-21 17:23:56 +00:00
|
|
|
def compact_action(self):
|
|
|
|
params = BorgCompactJob.prepare(self.profile())
|
|
|
|
if params['ok']:
|
|
|
|
job = BorgCompactJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
|
|
|
job.result.connect(self.compact_result)
|
|
|
|
self._toggle_all_buttons(False)
|
|
|
|
self.app.jobs_manager.add_job(job)
|
|
|
|
else:
|
|
|
|
self._set_status(params['message'])
|
|
|
|
|
|
|
|
def compact_result(self, result):
|
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
|
2018-11-04 08:23:17 +00:00
|
|
|
def prune_action(self):
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgPruneJob.prepare(self.profile())
|
2018-11-04 08:23:17 +00:00
|
|
|
if params['ok']:
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgPruneJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
|
|
|
job.result.connect(self.prune_result)
|
2018-11-04 15:37:46 +00:00
|
|
|
self._toggle_all_buttons(False)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2021-02-18 01:44:10 +00:00
|
|
|
else:
|
|
|
|
self._set_status(params['message'])
|
2018-11-04 08:23:17 +00:00
|
|
|
|
|
|
|
def prune_result(self, result):
|
|
|
|
if result['returncode'] == 0:
|
2019-01-20 03:50:10 +00:00
|
|
|
self._set_status(self.tr('Pruning finished.'))
|
2022-03-24 06:27:07 +00:00
|
|
|
self.refresh_archive_list()
|
2018-11-04 08:23:17 +00:00
|
|
|
else:
|
2018-11-04 15:37:46 +00:00
|
|
|
self._toggle_all_buttons(True)
|
2018-11-04 08:23:17 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
def refresh_archive_list(self):
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgListRepoJob.prepare(self.profile())
|
2018-11-04 08:23:17 +00:00
|
|
|
if params['ok']:
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgListRepoJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
|
|
|
job.result.connect(self.list_result)
|
2018-11-04 15:37:46 +00:00
|
|
|
self._toggle_all_buttons(False)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2021-02-18 01:44:10 +00:00
|
|
|
else:
|
|
|
|
self._set_status(params['message'])
|
2018-11-04 08:23:17 +00:00
|
|
|
|
|
|
|
def list_result(self, result):
|
2018-11-04 15:37:46 +00:00
|
|
|
self._toggle_all_buttons(True)
|
2018-11-04 08:23:17 +00:00
|
|
|
if result['returncode'] == 0:
|
2019-01-20 03:50:10 +00:00
|
|
|
self._set_status(self.tr('Refreshed archives.'))
|
2018-11-17 08:51:53 +00:00
|
|
|
self.populate_from_profile()
|
2021-02-23 02:12:09 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
def refresh_archive_info(self):
|
2021-02-23 02:12:09 +00:00
|
|
|
archive_name = self.selected_archive_name()
|
|
|
|
if archive_name is not None:
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgInfoArchiveJob.prepare(self.profile(), archive_name)
|
2021-02-23 02:12:09 +00:00
|
|
|
if params['ok']:
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgInfoArchiveJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
2022-03-24 06:27:07 +00:00
|
|
|
job.result.connect(self.info_result)
|
2021-02-23 02:12:09 +00:00
|
|
|
self._toggle_all_buttons(False)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2021-02-23 02:12:09 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
def info_result(self, result):
|
2021-02-23 02:12:09 +00:00
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
if result['returncode'] == 0:
|
|
|
|
self._set_status(self.tr('Refreshed archive.'))
|
|
|
|
self.populate_from_profile()
|
2018-11-04 08:23:17 +00:00
|
|
|
|
2019-01-21 10:44:01 +00:00
|
|
|
def selected_archive_name(self):
|
|
|
|
row_selected = self.archiveTable.selectionModel().selectedRows()
|
|
|
|
if row_selected:
|
|
|
|
archive_cell = self.archiveTable.item(row_selected[0].row(), 4)
|
|
|
|
if archive_cell:
|
|
|
|
return archive_cell.text()
|
|
|
|
return None
|
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
def bmountarchive_clicked(self):
|
2022-03-24 06:27:07 +00:00
|
|
|
"""
|
2022-04-16 05:30:31 +00:00
|
|
|
Handle `bMountArchive` being clicked.
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
Mount or umount the current archive depending on its current state.
|
|
|
|
"""
|
|
|
|
archive_name = self.selected_archive_name()
|
|
|
|
|
|
|
|
if not archive_name:
|
|
|
|
logger.warning("Archive name of selection is empty.")
|
|
|
|
return
|
|
|
|
|
|
|
|
if archive_name in self.mount_points:
|
2022-04-16 05:30:31 +00:00
|
|
|
self.unmount_action(archive_name=archive_name)
|
|
|
|
else:
|
|
|
|
self.mount_action(archive_name=archive_name)
|
|
|
|
|
|
|
|
def bmountrepo_clicked(self):
|
|
|
|
"""
|
|
|
|
Handle `bMountRepo` being clicked.
|
|
|
|
|
|
|
|
Mount or umount the repository depending on its current state.
|
|
|
|
"""
|
|
|
|
if self.repo_mount_point:
|
|
|
|
self.unmount_action()
|
2022-03-24 06:27:07 +00:00
|
|
|
else:
|
|
|
|
self.mount_action()
|
|
|
|
|
2022-08-25 07:11:12 +00:00
|
|
|
def bmountarchive_refresh(self, icon_only=False):
|
2022-03-24 06:27:07 +00:00
|
|
|
"""
|
2022-05-07 06:13:48 +00:00
|
|
|
Update label, tooltip and state of `bMountArchive`.
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
The new state depends on the mount status of the current archive.
|
|
|
|
This also updates the icon of the button.
|
|
|
|
"""
|
|
|
|
archive_name = self.selected_archive_name()
|
|
|
|
|
|
|
|
if archive_name in self.mount_points:
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bMountArchive.setIcon(get_colored_icon('eject'))
|
2022-08-25 07:11:12 +00:00
|
|
|
if not icon_only:
|
|
|
|
self.bMountArchive.setText(self.tr("Unmount"))
|
|
|
|
self.bMountArchive.setToolTip(self.tr('Unmount the selected archive from the file system'))
|
2022-04-16 05:30:31 +00:00
|
|
|
else:
|
|
|
|
self.bMountArchive.setIcon(get_colored_icon('folder-open'))
|
2022-08-25 07:11:12 +00:00
|
|
|
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"))
|
2022-04-16 05:30:31 +00:00
|
|
|
|
|
|
|
def bmountrepo_refresh(self):
|
|
|
|
"""
|
2022-05-07 06:13:48 +00:00
|
|
|
Update label, tooltip and state of `bMountRepo`.
|
2022-04-16 05:30:31 +00:00
|
|
|
|
|
|
|
The new state depends on the mount status of the current archive.
|
|
|
|
This also updates the icon of the button.
|
|
|
|
"""
|
|
|
|
if self.repo_mount_point:
|
|
|
|
self.bMountRepo.setText(self.tr("Unmount"))
|
2022-08-25 07:11:12 +00:00
|
|
|
self.bMountRepo.setToolTip(self.tr('Unmount the repository from the file system'))
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bMountRepo.setIcon(get_colored_icon('eject'))
|
2022-03-24 06:27:07 +00:00
|
|
|
else:
|
2022-04-16 05:30:31 +00:00
|
|
|
self.bMountRepo.setText(self.tr("Mount…"))
|
|
|
|
self.bMountRepo.setIcon(get_colored_icon('folder-open'))
|
2022-08-25 07:11:12 +00:00
|
|
|
self.bMountRepo.setToolTip(self.tr("Mount the repository as a folder in the file system"))
|
2022-04-16 05:30:31 +00:00
|
|
|
|
|
|
|
def mount_action(self, archive_name=None):
|
|
|
|
"""
|
|
|
|
Mount an archive or the whole repository.
|
|
|
|
|
|
|
|
Opens a file chooser to let the user choose a mount point and starts
|
|
|
|
the borg job for mounting afterwards.
|
2022-03-24 06:27:07 +00:00
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
archive_name : str, optional
|
|
|
|
The archive to mount or None, by default None
|
|
|
|
"""
|
2018-11-04 08:23:17 +00:00
|
|
|
profile = self.profile()
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgMountJob.prepare(profile)
|
2018-11-13 00:12:17 +00:00
|
|
|
if not params['ok']:
|
2019-02-19 13:42:19 +00:00
|
|
|
self._set_status(params['message'])
|
2018-11-13 00:12:17 +00:00
|
|
|
return
|
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
if archive_name:
|
|
|
|
# mount archive
|
|
|
|
params['cmd'][-1] += f'::{archive_name}'
|
|
|
|
params['current_archive'] = archive_name
|
|
|
|
# else mount complete repo
|
2018-10-27 17:24:34 +00:00
|
|
|
|
2018-11-20 00:50:52 +00:00
|
|
|
def receive():
|
2018-11-22 04:06:10 +00:00
|
|
|
mount_point = dialog.selectedFiles()
|
|
|
|
if mount_point:
|
|
|
|
params['cmd'].append(mount_point[0])
|
2022-04-16 05:30:31 +00:00
|
|
|
params['mount_point'] = mount_point[0]
|
|
|
|
|
2018-11-20 00:50:52 +00:00
|
|
|
if params['ok']:
|
|
|
|
self._toggle_all_buttons(False)
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgMountJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self.mountErrors.setText)
|
|
|
|
job.result.connect(self.mount_result)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2018-11-20 00:50:52 +00:00
|
|
|
|
2019-01-21 10:44:01 +00:00
|
|
|
dialog = choose_file_dialog(self, self.tr("Choose Mount Point"), want_folder=True)
|
2018-11-20 00:50:52 +00:00
|
|
|
dialog.open(receive)
|
|
|
|
|
2018-11-04 15:37:46 +00:00
|
|
|
def mount_result(self, result):
|
2018-10-27 17:24:34 +00:00
|
|
|
if result['returncode'] == 0:
|
2019-01-20 03:50:10 +00:00
|
|
|
self._set_status(self.tr('Mounted successfully.'))
|
2022-04-16 05:30:31 +00:00
|
|
|
|
|
|
|
mount_point = result['params']['mount_point']
|
|
|
|
|
2019-04-07 07:28:02 +00:00
|
|
|
if result['params'].get('current_archive'):
|
2022-04-16 05:30:31 +00:00
|
|
|
# archive was mounted
|
|
|
|
archive_name = result['params']['current_archive']
|
|
|
|
self.mount_points[archive_name] = mount_point
|
|
|
|
|
|
|
|
# update column in table
|
2019-04-07 07:28:02 +00:00
|
|
|
archive_name = result['params']['current_archive']
|
|
|
|
row = self.row_of_archive(archive_name)
|
|
|
|
item = QTableWidgetItem(result['cmd'][-1])
|
|
|
|
self.archiveTable.setItem(row, 3, item)
|
2018-11-22 04:06:10 +00:00
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
# update button
|
|
|
|
self.bmountarchive_refresh()
|
|
|
|
else:
|
|
|
|
# whole repo was mounted
|
|
|
|
self.repo_mount_point = mount_point
|
|
|
|
self.bmountrepo_refresh()
|
2022-03-24 06:27:07 +00:00
|
|
|
|
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
def unmount_action(self, archive_name=None):
|
|
|
|
"""
|
|
|
|
Unmount a (mounted) repository or archive.
|
|
|
|
|
|
|
|
If the target isn't mounted nothing happens.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
archive_name : str, optional
|
|
|
|
The archive to unmount, by default None
|
|
|
|
"""
|
|
|
|
if archive_name:
|
|
|
|
# unmount a single archive
|
|
|
|
mount_point = self.mount_points.get(archive_name)
|
|
|
|
else:
|
|
|
|
# unmount the whole repository
|
|
|
|
mount_point = self.repo_mount_point
|
2019-01-21 10:44:01 +00:00
|
|
|
|
|
|
|
if mount_point is not None:
|
2018-11-22 04:06:10 +00:00
|
|
|
profile = self.profile()
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgUmountJob.prepare(profile)
|
2018-11-22 04:06:10 +00:00
|
|
|
if not params['ok']:
|
2019-02-19 13:42:19 +00:00
|
|
|
self._set_status(params['message'])
|
2018-11-22 04:06:10 +00:00
|
|
|
return
|
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
if archive_name:
|
|
|
|
params['current_archive'] = archive_name
|
|
|
|
params['mount_point'] = mount_point
|
2019-01-21 10:44:01 +00:00
|
|
|
|
|
|
|
if os.path.normpath(mount_point) in params['active_mount_points']:
|
|
|
|
params['cmd'].append(mount_point)
|
2022-04-16 05:30:31 +00:00
|
|
|
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgUmountJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self.mountErrors.setText)
|
|
|
|
job.result.connect(self.umount_result)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2018-11-22 04:06:10 +00:00
|
|
|
else:
|
2019-01-21 10:44:01 +00:00
|
|
|
self._set_status(self.tr('Mount point not active.'))
|
2018-11-22 04:06:10 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def umount_result(self, result):
|
|
|
|
self._toggle_all_buttons(True)
|
2022-04-16 05:30:31 +00:00
|
|
|
archive_name = result['params'].get('current_archive')
|
|
|
|
mount_point = result['params']['mount_point']
|
|
|
|
|
2018-11-22 04:06:10 +00:00
|
|
|
if result['returncode'] == 0:
|
2019-01-20 03:50:10 +00:00
|
|
|
self._set_status(self.tr('Un-mounted successfully.'))
|
2022-03-24 06:27:07 +00:00
|
|
|
|
2022-04-16 05:30:31 +00:00
|
|
|
if archive_name:
|
|
|
|
# unmount single archive
|
|
|
|
del self.mount_points[archive_name]
|
|
|
|
row = self.row_of_archive(archive_name)
|
|
|
|
item = QTableWidgetItem('')
|
|
|
|
self.archiveTable.setItem(row, 3, item)
|
|
|
|
|
|
|
|
# update button
|
|
|
|
self.bmountarchive_refresh()
|
|
|
|
else:
|
|
|
|
# unmount repo
|
|
|
|
self.repo_mount_point = None
|
|
|
|
|
|
|
|
self.bmountrepo_refresh()
|
2020-12-16 03:10:38 +00:00
|
|
|
else:
|
2022-03-24 06:27:07 +00:00
|
|
|
self._set_status(self.tr('Unmounting failed. Make sure no programs are using {}').format(mount_point))
|
2018-11-01 16:53:41 +00:00
|
|
|
|
2018-11-24 02:01:50 +00:00
|
|
|
def save_prune_setting(self, new_value=None):
|
2018-11-04 08:23:17 +00:00
|
|
|
profile = self.profile()
|
|
|
|
for i in self.prune_intervals:
|
|
|
|
setattr(profile, f'prune_{i}', getattr(self, f'prune_{i}').value())
|
2018-11-24 02:01:50 +00:00
|
|
|
profile.prune_keep_within = self.prune_keep_within.text()
|
2018-11-04 08:23:17 +00:00
|
|
|
profile.save()
|
2018-11-22 10:25:22 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
def extract_action(self):
|
2022-05-07 10:04:35 +00:00
|
|
|
"""
|
|
|
|
Open a dialog for choosing what to extract from the selected archive.
|
|
|
|
"""
|
2018-11-27 11:33:16 +00:00
|
|
|
profile = self.profile()
|
|
|
|
|
|
|
|
row_selected = self.archiveTable.selectionModel().selectedRows()
|
|
|
|
if row_selected:
|
2019-01-21 10:44:01 +00:00
|
|
|
archive_cell = self.archiveTable.item(row_selected[0].row(), 4)
|
2018-11-27 11:33:16 +00:00
|
|
|
if archive_cell:
|
|
|
|
archive_name = archive_cell.text()
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgListArchiveJob.prepare(profile, archive_name)
|
2018-11-27 11:33:16 +00:00
|
|
|
|
|
|
|
if not params['ok']:
|
2019-02-19 13:42:19 +00:00
|
|
|
self._set_status(params['message'])
|
2018-11-27 11:33:16 +00:00
|
|
|
return
|
|
|
|
self._set_status('')
|
|
|
|
self._toggle_all_buttons(False)
|
|
|
|
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgListArchiveJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self.mountErrors.setText)
|
2022-03-24 06:27:07 +00:00
|
|
|
job.result.connect(self.extract_list_result)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2021-10-04 11:31:41 +00:00
|
|
|
return job
|
2018-11-27 11:33:16 +00:00
|
|
|
else:
|
2019-01-20 03:50:10 +00:00
|
|
|
self._set_status(self.tr('Select an archive to restore first.'))
|
2018-11-27 11:33:16 +00:00
|
|
|
|
2022-03-24 06:27:07 +00:00
|
|
|
def extract_list_result(self, result):
|
2022-05-07 10:04:35 +00:00
|
|
|
"""Process the contents of the archive to extract."""
|
2018-11-27 11:33:16 +00:00
|
|
|
self._set_status('')
|
|
|
|
if result['returncode'] == 0:
|
2020-11-20 00:46:09 +00:00
|
|
|
archive = ArchiveModel.get(name=result['params']['archive_name'])
|
2022-05-07 10:04:35 +00:00
|
|
|
model = ExtractTree()
|
|
|
|
self._set_status(self.tr("Processing archive contents"))
|
|
|
|
self._t = extract_dialog.ParseThread(result['data'], model)
|
|
|
|
self._t.finished.connect(lambda: self.extract_show_dialog(archive, model))
|
|
|
|
self._t.start()
|
|
|
|
|
|
|
|
def extract_show_dialog(self, archive, model):
|
|
|
|
"""Show the dialog for choosing the archive contents to extract."""
|
|
|
|
self._set_status('')
|
|
|
|
|
|
|
|
def process_result():
|
|
|
|
def receive():
|
|
|
|
extraction_folder = dialog.selectedFiles()
|
|
|
|
if extraction_folder:
|
|
|
|
params = BorgExtractJob.prepare(self.profile(), archive.name, model, extraction_folder[0])
|
|
|
|
if params['ok']:
|
|
|
|
self._toggle_all_buttons(False)
|
|
|
|
job = BorgExtractJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self.mountErrors.setText)
|
|
|
|
job.result.connect(self.extract_archive_result)
|
|
|
|
self.app.jobs_manager.add_job(job)
|
|
|
|
else:
|
|
|
|
self._set_status(params['message'])
|
|
|
|
|
|
|
|
dialog = choose_file_dialog(self, self.tr("Choose Extraction Point"), want_folder=True)
|
|
|
|
dialog.open(receive)
|
|
|
|
|
|
|
|
window = ExtractDialog(archive, model)
|
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
window.setParent(self, QtCore.Qt.Sheet)
|
|
|
|
self._window = window # for testing
|
|
|
|
window.show()
|
|
|
|
window.accepted.connect(process_result)
|
2020-11-20 00:46:09 +00:00
|
|
|
|
2018-11-27 11:33:16 +00:00
|
|
|
def extract_archive_result(self, result):
|
2022-05-07 10:04:35 +00:00
|
|
|
"""Finished extraction."""
|
2018-11-27 11:33:16 +00:00
|
|
|
self._toggle_all_buttons(True)
|
2019-01-21 10:44:01 +00:00
|
|
|
|
|
|
|
def cell_double_clicked(self, row, column):
|
|
|
|
if column == 3:
|
|
|
|
archive_name = self.selected_archive_name()
|
|
|
|
if not archive_name:
|
|
|
|
return
|
|
|
|
|
|
|
|
mount_point = self.mount_points.get(archive_name)
|
|
|
|
|
|
|
|
if mount_point is not None:
|
|
|
|
QDesktopServices.openUrl(QtCore.QUrl(f'file:///{mount_point}'))
|
|
|
|
|
|
|
|
def row_of_archive(self, archive_name):
|
|
|
|
items = self.archiveTable.findItems(archive_name, QtCore.Qt.MatchExactly)
|
|
|
|
rows = [item.row() for item in items if item.column() == 4]
|
|
|
|
return rows[0] if rows else None
|
2019-01-24 00:36:44 +00:00
|
|
|
|
2019-02-02 02:01:09 +00:00
|
|
|
def confirm_dialog(self, title, text):
|
2019-02-02 04:29:33 +00:00
|
|
|
msg = QMessageBox()
|
|
|
|
msg.setIcon(QMessageBox.Information)
|
|
|
|
msg.setText(text)
|
|
|
|
msg.setWindowTitle(title)
|
|
|
|
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
|
|
|
|
msg.button(msg.Yes).setText(self.tr("Yes"))
|
|
|
|
msg.button(msg.Cancel).setText(self.tr("Cancel"))
|
|
|
|
return msg.exec_() == QMessageBox.Yes
|
2019-02-02 02:01:09 +00:00
|
|
|
|
2019-01-24 00:36:44 +00:00
|
|
|
def delete_action(self):
|
2021-10-04 11:31:41 +00:00
|
|
|
# Since this function modify the UI, we can't put the whole function in a JobQUeue.
|
|
|
|
|
2022-05-21 10:49:46 +00:00
|
|
|
# determine selected archives
|
|
|
|
archives = []
|
|
|
|
for index in self.archiveTable.selectionModel().selectedRows():
|
|
|
|
archive_cell = self.archiveTable.item(index.row(), 4)
|
|
|
|
if archive_cell:
|
|
|
|
archives.append(archive_cell.text())
|
|
|
|
|
|
|
|
if not archives:
|
|
|
|
self._set_status(self.tr("No archive selected"))
|
|
|
|
return
|
|
|
|
|
|
|
|
params = BorgDeleteJob.prepare(self.profile(), archives)
|
2019-01-24 00:36:44 +00:00
|
|
|
if not params['ok']:
|
2019-02-19 13:42:19 +00:00
|
|
|
self._set_status(params['message'])
|
2019-01-24 00:36:44 +00:00
|
|
|
return
|
|
|
|
|
2022-05-21 10:49:46 +00:00
|
|
|
if len(archives) > 1:
|
|
|
|
body = self.tr("Are you sure you want to delete all the selected archives?")
|
2019-01-24 00:36:44 +00:00
|
|
|
else:
|
2022-05-21 10:49:46 +00:00
|
|
|
body = self.tr("Are you sure you want to delete the selected archive?")
|
|
|
|
if not self.confirm_dialog(self.tr("Confirm deletion"), body):
|
|
|
|
return
|
|
|
|
|
|
|
|
job = BorgDeleteJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
|
|
|
job.result.connect(self.delete_result)
|
|
|
|
self._toggle_all_buttons(False)
|
|
|
|
self.app.jobs_manager.add_job(job)
|
2019-01-24 00:36:44 +00:00
|
|
|
|
|
|
|
def delete_result(self, result):
|
2022-05-21 10:49:46 +00:00
|
|
|
archives = result['params']['archives']
|
2019-01-24 00:36:44 +00:00
|
|
|
if result['returncode'] == 0:
|
2022-05-21 10:49:46 +00:00
|
|
|
if len(archives) > 1:
|
|
|
|
status = self.tr('Archives deleted.')
|
|
|
|
else:
|
|
|
|
status = self.tr('Archive deleted.')
|
|
|
|
self._set_status(status)
|
|
|
|
|
|
|
|
# remove rows from list and database
|
|
|
|
for archive in archives:
|
|
|
|
for entry in self.archiveTable.findItems(archive, QtCore.Qt.MatchExactly):
|
|
|
|
self.archiveTable.removeRow(entry.row())
|
|
|
|
ArchiveModel.get(name=archive).delete_instance()
|
|
|
|
|
|
|
|
self._toggle_all_buttons(True)
|
2019-07-04 19:17:09 +00:00
|
|
|
|
|
|
|
def diff_action(self):
|
2022-05-05 09:21:54 +00:00
|
|
|
"""
|
|
|
|
Handle the diff button being clicked.
|
2019-07-04 19:17:09 +00:00
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
Exactly two archives must be selected in `archiveTable`. This is
|
|
|
|
usually enforced by `on_selection_change`.
|
|
|
|
"""
|
|
|
|
selected_archives = self.archiveTable.selectionModel().selectedRows()
|
|
|
|
profile = self.profile()
|
2019-07-04 19:17:09 +00:00
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
name1 = self.archiveTable.item(selected_archives[0].row(), 4).text()
|
|
|
|
name2 = self.archiveTable.item(selected_archives[1].row(), 4).text()
|
2019-07-04 19:17:09 +00:00
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
archive1, archive2 = (
|
|
|
|
profile.repo.archives.select()
|
|
|
|
.where((ArchiveModel.name == name1) | (ArchiveModel.name == name2))
|
|
|
|
.order_by(ArchiveModel.time.desc())
|
2022-08-15 05:21:14 +00:00
|
|
|
)
|
2020-11-20 00:46:09 +00:00
|
|
|
|
2022-05-05 09:21:54 +00:00
|
|
|
archive_name_newer = archive1.name
|
|
|
|
archive_name_older = archive2.name
|
|
|
|
|
|
|
|
# Start diff job
|
|
|
|
params = BorgDiffJob.prepare(profile, archive_name_older, archive_name_newer)
|
|
|
|
|
|
|
|
if params['ok']:
|
|
|
|
self._toggle_all_buttons(False)
|
|
|
|
job = BorgDiffJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self.mountErrors.setText)
|
|
|
|
job.result.connect(self.list_diff_result)
|
|
|
|
self.app.jobs_manager.add_job(job)
|
|
|
|
else:
|
|
|
|
self._set_status(params['message'])
|
2020-11-20 00:46:09 +00:00
|
|
|
|
2019-07-04 19:17:09 +00:00
|
|
|
def list_diff_result(self, result):
|
2022-05-05 09:21:54 +00:00
|
|
|
"""
|
|
|
|
Process the result of the `BorgDiffJob`.
|
|
|
|
|
|
|
|
The `BorgDiffJob` was initiated by `diff_action`.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
result : dict
|
|
|
|
The BorgJob result.
|
|
|
|
"""
|
2019-07-04 19:17:09 +00:00
|
|
|
self._set_status('')
|
|
|
|
if result['returncode'] == 0:
|
2022-05-05 09:21:54 +00:00
|
|
|
archive_newer = ArchiveModel.get(name=result['params']['archive_name_newer'])
|
|
|
|
archive_older = ArchiveModel.get(name=result['params']['archive_name_older'])
|
2022-04-18 16:53:46 +00:00
|
|
|
self._set_status(self.tr("Processing diff results."))
|
|
|
|
|
|
|
|
model = DiffTree()
|
|
|
|
|
2022-05-07 10:04:35 +00:00
|
|
|
self._t = diff_result.ParseThread(result['data'], result['params']['json_lines'], model)
|
2022-04-18 16:53:46 +00:00
|
|
|
self._t.finished.connect(lambda: self.show_diff_result(archive_newer, archive_older, model))
|
|
|
|
self._t.start()
|
|
|
|
|
|
|
|
def show_diff_result(self, archive_newer, archive_older, model):
|
|
|
|
self._t = None
|
|
|
|
|
|
|
|
# show dialog
|
|
|
|
self._toggle_all_buttons(True)
|
|
|
|
self._set_status('')
|
|
|
|
window = DiffResultDialog(archive_newer, archive_older, model)
|
|
|
|
window.setParent(self)
|
|
|
|
window.setWindowFlags(Qt.WindowType.Window)
|
|
|
|
window.setWindowModality(Qt.WindowModality.NonModal)
|
|
|
|
self._resultwindow = window # for testing
|
|
|
|
window.show()
|
2021-02-18 01:44:10 +00:00
|
|
|
|
|
|
|
def rename_action(self):
|
|
|
|
profile = self.profile()
|
2021-10-04 11:31:41 +00:00
|
|
|
params = BorgRenameJob.prepare(profile)
|
2021-02-18 01:44:10 +00:00
|
|
|
if not params['ok']:
|
|
|
|
self._set_status(params['message'])
|
|
|
|
return
|
|
|
|
|
|
|
|
archive_name = self.selected_archive_name()
|
|
|
|
if archive_name is not None:
|
|
|
|
new_name, finished = QInputDialog.getText(
|
|
|
|
self,
|
|
|
|
self.tr("Change name"),
|
|
|
|
self.tr("New archive name:"),
|
|
|
|
text=archive_name,
|
|
|
|
)
|
|
|
|
|
|
|
|
if not finished:
|
|
|
|
return
|
|
|
|
|
|
|
|
if not new_name:
|
|
|
|
self._set_status(self.tr('Archive name cannot be blank.'))
|
|
|
|
return
|
|
|
|
|
|
|
|
new_name_exists = ArchiveModel.get_or_none(name=new_name, repo=profile.repo)
|
|
|
|
if new_name_exists is not None:
|
|
|
|
self._set_status(self.tr('An archive with this name already exists.'))
|
|
|
|
return
|
|
|
|
|
|
|
|
params['cmd'][-1] += f'::{archive_name}'
|
|
|
|
params['cmd'].append(new_name)
|
2021-10-04 11:31:41 +00:00
|
|
|
job = BorgRenameJob(params['cmd'], params, self.profile().repo.id)
|
|
|
|
job.updated.connect(self._set_status)
|
|
|
|
job.result.connect(self.rename_result)
|
2021-02-18 01:44:10 +00:00
|
|
|
self._toggle_all_buttons(False)
|
2021-11-12 07:05:31 +00:00
|
|
|
self.app.jobs_manager.add_job(job)
|
2021-02-18 01:44:10 +00:00
|
|
|
else:
|
|
|
|
self._set_status(self.tr("No archive selected"))
|
|
|
|
|
|
|
|
def rename_result(self, result):
|
|
|
|
if result['returncode'] == 0:
|
|
|
|
self._set_status(self.tr('Archive renamed.'))
|
|
|
|
self.populate_from_profile()
|
|
|
|
else:
|
|
|
|
self._toggle_all_buttons(True)
|