diff --git a/src/vorta/assets/UI/archivetab.ui b/src/vorta/assets/UI/archivetab.ui index dfaa5d512..6c206033f 100644 --- a/src/vorta/assets/UI/archivetab.ui +++ b/src/vorta/assets/UI/archivetab.ui @@ -257,7 +257,7 @@ - + diff --git a/src/vorta/borg/info_archive.py b/src/vorta/borg/info_archive.py index 72caf06c3..afb94b2f3 100644 --- a/src/vorta/borg/info_archive.py +++ b/src/vorta/borg/info_archive.py @@ -41,6 +41,11 @@ def process_result(self, result): # Update remote archives. for remote_archive in remote_archives: archive = ArchiveModel.get_or_none(snapshot_id=remote_archive['id'], repo=repo_id) + if archive is None: + # archive id was changed during rename, so we need to find it by name + archive = ArchiveModel.get_or_none(name=remote_archive['name'], repo=repo_id) + archive.snapshot_id = remote_archive['id'] + archive.name = remote_archive['name'] # incase name changed # archive.time = parser.parse(remote_archive['time']) archive.duration = remote_archive['duration'] diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py index c29218c97..b18401f7a 100644 --- a/src/vorta/views/archive_tab.py +++ b/src/vorta/views/archive_tab.py @@ -10,7 +10,6 @@ QAbstractItemView, QApplication, QHeaderView, - QInputDialog, QLayout, QMenu, QMessageBox, @@ -79,6 +78,7 @@ def __init__(self, parent=None, app=None): self.app = app self.toolBox.setCurrentIndex(0) self.repoactions_enabled = True + self.renamed_archive_original_name = None self.remaining_refresh_archives = ( 0 # number of archives that are left to refresh before action buttons are enabled again ) @@ -111,6 +111,7 @@ def __init__(self, parent=None, app=None): self.archiveTable.setTextElideMode(QtCore.Qt.TextElideMode.ElideLeft) self.archiveTable.setAlternatingRowColors(True) self.archiveTable.cellDoubleClicked.connect(self.cell_double_clicked) + self.archiveTable.cellChanged.connect(self.cell_changed) self.archiveTable.setSortingEnabled(True) self.archiveTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.archiveTable.customContextMenuRequested.connect(self.archiveitem_contextmenu) @@ -126,7 +127,7 @@ def __init__(self, parent=None, app=None): # connect archive actions self.bMountArchive.clicked.connect(self.bmountarchive_clicked) self.bRefreshArchive.clicked.connect(self.refresh_archive_info) - self.bRename.clicked.connect(self.rename_action) + self.bRename.clicked.connect(self.cell_double_clicked) self.bDelete.clicked.connect(self.delete_action) self.bExtract.clicked.connect(self.extract_action) self.compactButton.clicked.connect(self.compact_action) @@ -206,7 +207,7 @@ def archiveitem_contextmenu(self, pos: QPoint): ) ) 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.bRename.icon(), self.bRename.text(), self.cell_double_clicked)) # deletion possible with one but also multiple archives menu.addAction(self.bDelete.icon(), self.bDelete.text(), self.delete_action) @@ -823,7 +824,11 @@ def extract_archive_result(self, result): """Finished extraction.""" self._toggle_all_buttons(True) - def cell_double_clicked(self, row, column): + def cell_double_clicked(self, row=None, column=None): + if not row or not column: + row = self.archiveTable.currentRow() + column = self.archiveTable.currentColumn() + if column == 3: archive_name = self.selected_archive_name() if not archive_name: @@ -834,6 +839,46 @@ def cell_double_clicked(self, row, column): if mount_point is not None: QDesktopServices.openUrl(QtCore.QUrl(f'file:///{mount_point}')) + if column == 4: + item = self.archiveTable.item(row, column) + self.renamed_archive_original_name = item.text() + item.setFlags(item.flags() | QtCore.Qt.ItemFlag.ItemIsEditable) + self.archiveTable.editItem(item) + + def cell_changed(self, row, column): + # return if this is not a name change + if column != 4: + return + + item = self.archiveTable.item(row, column) + new_name = item.text() + profile = self.profile() + + # if the name hasn't changed or if this slot is called when first repopulating the table, do nothing. + if new_name == self.renamed_archive_original_name or not self.renamed_archive_original_name: + return + + if not new_name: + item.setText(self.renamed_archive_original_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.')) + item.setText(self.renamed_archive_original_name) + return + + params = BorgRenameJob.prepare(profile, self.renamed_archive_original_name, new_name) + if not params['ok']: + self._set_status(params['message']) + + job = BorgRenameJob(params['cmd'], params, self.profile().repo.id) + job.updated.connect(self._set_status) + job.result.connect(self.rename_result) + self._toggle_all_buttons(False) + self.app.jobs_manager.add_job(job) + def row_of_archive(self, archive_name): items = self.archiveTable.findItems(archive_name, QtCore.Qt.MatchFlag.MatchExactly) rows = [item.row() for item in items if item.column() == 4] @@ -968,45 +1013,11 @@ def show_diff_result(self, archive_newer, archive_older, model): self._resultwindow = window # for testing window.show() - def rename_action(self): - profile = self.profile() - - 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 = BorgRenameJob.prepare(profile, archive_name, new_name) - if not params['ok']: - self._set_status(params['message']) - - job = BorgRenameJob(params['cmd'], params, self.profile().repo.id) - job.updated.connect(self._set_status) - job.result.connect(self.rename_result) - self._toggle_all_buttons(False) - self.app.jobs_manager.add_job(job) - else: - self._set_status(self.tr("No archive selected")) - def rename_result(self, result): if result['returncode'] == 0: + self.refresh_archive_info() self._set_status(self.tr('Archive renamed.')) + self.renamed_archive_original_name = None self.populate_from_profile() else: self._toggle_all_buttons(True) diff --git a/tests/integration/test_archives.py b/tests/integration/test_archives.py index 98d653fbc..e74f9bb33 100644 --- a/tests/integration/test_archives.py +++ b/tests/integration/test_archives.py @@ -171,15 +171,11 @@ def test_archive_rename(qapp, qtbot, mocker): tab.archiveTable.selectRow(0) new_archive_name = 'idf89d8f9d8fd98' - mocker.patch.object(vorta.views.archive_tab.QInputDialog, 'getText', return_value=(new_archive_name, True)) - tab.rename_action() + pos = tab.archiveTable.visualRect(tab.archiveTable.model().index(0, 4)).center() + qtbot.mouseClick(tab.archiveTable.viewport(), QtCore.Qt.MouseButton.LeftButton, pos=pos) + qtbot.mouseDClick(tab.archiveTable.viewport(), QtCore.Qt.MouseButton.LeftButton, pos=pos) + qtbot.keyClicks(tab.archiveTable.viewport().focusWidget(), new_archive_name) + qtbot.keyClick(tab.archiveTable.viewport().focusWidget(), QtCore.Qt.Key.Key_Return) # Successful rename case - qtbot.waitUntil(lambda: tab.mountErrors.text() == 'Archive renamed.', **pytest._wait_defaults) - assert ArchiveModel.select().filter(name=new_archive_name).count() == 1 - - # Duplicate name case - tab.archiveTable.selectRow(0) - exp_text = 'An archive with this name already exists.' - tab.rename_action() - qtbot.waitUntil(lambda: tab.mountErrors.text() == exp_text, **pytest._wait_defaults) + qtbot.waitUntil(lambda: tab.archiveTable.model().index(0, 4).data() == new_archive_name, **pytest._wait_defaults) diff --git a/tests/unit/test_archives.py b/tests/unit/test_archives.py index a7627af16..a37dcfd8c 100644 --- a/tests/unit/test_archives.py +++ b/tests/unit/test_archives.py @@ -183,16 +183,12 @@ def test_archive_rename(qapp, qtbot, mocker, borg_json_output): stdout, stderr = borg_json_output('rename') popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) - mocker.patch.object(vorta.views.archive_tab.QInputDialog, 'getText', return_value=(new_archive_name, True)) - tab.rename_action() - # Successful rename case - qtbot.waitUntil(lambda: tab.mountErrors.text() == 'Archive renamed.', **pytest._wait_defaults) - assert ArchiveModel.select().filter(name=new_archive_name).count() == 1 + pos = tab.archiveTable.visualRect(tab.archiveTable.model().index(0, 4)).center() + qtbot.mouseClick(tab.archiveTable.viewport(), QtCore.Qt.MouseButton.LeftButton, pos=pos) + qtbot.mouseDClick(tab.archiveTable.viewport(), QtCore.Qt.MouseButton.LeftButton, pos=pos) + qtbot.keyClicks(tab.archiveTable.viewport().focusWidget(), new_archive_name) + qtbot.keyClick(tab.archiveTable.viewport().focusWidget(), QtCore.Qt.Key.Key_Return) - # Duplicate name case - tab.archiveTable.selectRow(0) - exp_text = 'An archive with this name already exists.' - mocker.patch.object(vorta.views.archive_tab.QInputDialog, 'getText', return_value=(new_archive_name, True)) - tab.rename_action() - qtbot.waitUntil(lambda: tab.mountErrors.text() == exp_text, **pytest._wait_defaults) + # Successful rename case + qtbot.waitUntil(lambda: tab.archiveTable.model().index(0, 4).data() == new_archive_name, **pytest._wait_defaults)