From 03480112e615fb4b1315fb0e00522620a9dd2449 Mon Sep 17 00:00:00 2001 From: yfprojects <62463991+real-yfprojects@users.noreply.github.com> Date: Sat, 21 May 2022 10:49:46 +0000 Subject: [PATCH] Select and delete multiple archives. By @real-yfprojects (#1307) --- src/vorta/assets/UI/archivetab.ui | 42 +++++++-------- src/vorta/borg/delete.py | 15 ++++-- src/vorta/views/archive_tab.py | 87 +++++++++++++++++++------------ 3 files changed, 85 insertions(+), 59 deletions(-) diff --git a/src/vorta/assets/UI/archivetab.ui b/src/vorta/assets/UI/archivetab.ui index 8080bd24..4fbda639 100644 --- a/src/vorta/assets/UI/archivetab.ui +++ b/src/vorta/assets/UI/archivetab.ui @@ -265,25 +265,6 @@ - - - - - 0 - 0 - - - - Delete selected archive - - - Delete - - - Qt::ToolButtonTextBesideIcon - - - @@ -319,6 +300,25 @@ + + + + + 0 + 0 + + + + Delete selected archive + + + Delete + + + Qt::ToolButtonTextBesideIcon + + + @@ -349,8 +349,8 @@ 0 0 - 781 - 371 + 540 + 352 diff --git a/src/vorta/borg/delete.py b/src/vorta/borg/delete.py index 89f9a97b..c347954b 100644 --- a/src/vorta/borg/delete.py +++ b/src/vorta/borg/delete.py @@ -1,3 +1,5 @@ +from typing import List + from vorta.store.models import RepoModel from .borg_job import BorgJob @@ -23,17 +25,22 @@ class BorgDeleteJob(BorgJob): self.app.backup_progress_event.emit(self.tr('Archive deleted.')) @classmethod - def prepare(cls, profile): + def prepare(cls, profile, archives: List[str]): ret = super().prepare(profile) if not ret['ok']: return ret else: ret['ok'] = False # Set back to false, so we can do our own checks here. - cmd = ['borg', 'delete', '--info', '--log-json'] - cmd.append(f'{profile.repo.url}') + if len(archives) <= 0: + return ret - ret['ok'] = True + cmd = ['borg', 'delete', '--info', '--log-json'] + cmd.append(f'{profile.repo.url}::{archives[0]}') + cmd.extend(archives[1:]) + + ret['archives'] = archives ret['cmd'] = cmd + ret['ok'] = True return ret diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py index 6775cec8..df1be4f0 100644 --- a/src/vorta/views/archive_tab.py +++ b/src/vorta/views/archive_tab.py @@ -23,7 +23,6 @@ from vorta.borg.mount import BorgMountJob from vorta.borg.prune import BorgPruneJob from vorta.borg.rename import BorgRenameJob from vorta.borg.umount import BorgUmountJob -from vorta.i18n import trans_late from vorta.store.models import ArchiveModel, BackupProfileMixin from vorta.utils import (choose_file_dialog, format_archive_name, get_asset, get_mount_points, pretty_bytes) @@ -56,6 +55,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): #: Tooltip dict to save the tooltips set in the designer self.tooltip_dict: Dict[QWidget, str] = {} self.tooltip_dict[self.bDiff] = self.bDiff.toolTip() + self.tooltip_dict[self.bDelete] = self.bDelete.toolTip() header = self.archiveTable.horizontalHeader() header.setVisible(True) @@ -161,16 +161,16 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): self.refresh_archive_info)) archive_actions.append( menu.addAction(self.bMountArchive.icon(), self.bMountArchive.text(), - self.bmount_clicked)) + self.bmountarchive_clicked)) 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)) - archive_actions.append( - menu.addAction(self.bDelete.icon(), self.bDelete.text(), - self.delete_action)) + # deletion possible with one but also multiple archives + menu.addAction(self.bDelete.icon(), self.bDelete.text(), + self.delete_action) if not (self.repoactions_enabled and len(selected_rows) <= 1): for action in archive_actions: @@ -185,7 +185,7 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): selected_rows = self.archiveTable.selectionModel().selectedRows( index.column()) diff_action.setEnabled(self.repoactions_enabled - and len(selected_rows) > 1) + and len(selected_rows) == 2) menu.popup(self.archiveTable.viewport().mapToGlobal(pos)) @@ -296,15 +296,18 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): # Toggle archive actions frame layout: QLayout = self.fArchiveActions.layout() - # Make sure at maximum 2 rows are selected. - if len(indexes) > 2: - selectionModel.select( - indexes[0], QItemSelectionModel.SelectionFlag.Deselect - | QItemSelectionModel.SelectionFlag.Rows) - indexes = selectionModel.selectedRows() + # 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] + self.bDelete.setToolTip(tooltip + " " + + self.tr("(Select two archives)")) # Toggle diff button - if len(indexes) >= 2: + if len(indexes) == 2: # Enable diff button self.bDiff.setEnabled(True) self.bDiff.setToolTip(self.tooltip_dict.get(self.bDiff, "")) @@ -763,35 +766,51 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin): def delete_action(self): # Since this function modify the UI, we can't put the whole function in a JobQUeue. - params = BorgDeleteJob.prepare(self.profile()) + # 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) if not params['ok']: self._set_status(params['message']) return - self.archive_name = self.selected_archive_name() - if self.archive_name is not None: - if not self.confirm_dialog(trans_late('ArchiveTab', "Confirm deletion"), - trans_late('ArchiveTab', "Are you sure you want to delete the archive?")): - return - params['cmd'][-1] += f'::{self.archive_name}' - 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) - + if len(archives) > 1: + body = self.tr("Are you sure you want to delete all the selected archives?") else: - self._set_status(self.tr("No archive selected")) + 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) def delete_result(self, result): + archives = result['params']['archives'] if result['returncode'] == 0: - self._set_status(self.tr('Archive deleted.')) - deleted_row = self.archiveTable.findItems(self.archive_name, QtCore.Qt.MatchExactly)[0].row() - self.archiveTable.removeRow(deleted_row) - ArchiveModel.get(name=self.archive_name).delete_instance() - del self.archive_name - else: - self._toggle_all_buttons(True) + 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) def diff_action(self): """