Skip to content

Commit

Permalink
Inline archive renaming. By @diivi (#1734)
Browse files Browse the repository at this point in the history
  • Loading branch information
diivi authored Aug 15, 2023
1 parent 92f285f commit 30c5722
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/vorta/assets/UI/archivetab.ui
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
</property>
</widget>
</item>
<item>
<item>
<widget class="QToolButton" name="bRename">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
Expand Down
5 changes: 5 additions & 0 deletions src/vorta/borg/info_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
91 changes: 51 additions & 40 deletions src/vorta/views/archive_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
QAbstractItemView,
QApplication,
QHeaderView,
QInputDialog,
QLayout,
QMenu,
QMessageBox,
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand All @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 6 additions & 10 deletions tests/integration/test_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 7 additions & 11 deletions tests/unit/test_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 30c5722

Please sign in to comment.