Skip to content
Merged
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
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)
Comment thread
diivi marked this conversation as resolved.
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):
Comment thread
diivi marked this conversation as resolved.
# 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
Comment thread
diivi marked this conversation as resolved.

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.'))
Comment thread
real-yfprojects marked this conversation as resolved.
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)