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/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def bootstrap_profile(self, bootstrap_file=None):
double_newline,
str(exception),
double_newline,
self.tr('Consider removing or repairing this file to ' 'get rid of this message.'),
self.tr('Consider removing or repairing this file to get rid of this message.'),
),
)
return
Expand Down
51 changes: 51 additions & 0 deletions src/vorta/views/archive/archive_delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from vorta.borg.delete import BorgDeleteJob
from vorta.store.models import ArchiveModel


class ArchiveDelete:
def __init__(self, tab):
self.tab = tab

def delete_action(self):
# Since this function modifies the UI, we can't put the whole function in a JobQueue.
archives = [archive.name for archive in self.tab.selected_archives()]

if not archives:
self.tab._set_status(self.tab.tr("No archive selected"))
return

params = BorgDeleteJob.prepare(self.tab.profile(), archives)
if not params['ok']:
self.tab._set_status(params['message'])
return

if len(archives) > 1:
body = self.tab.tr("Are you sure you want to delete all the selected archives?")
else:
body = self.tab.tr("Are you sure you want to delete the selected archive?")
if not self.tab.confirm_dialog(self.tab.tr("Confirm deletion"), body):
return

job = BorgDeleteJob(params['cmd'], params, self.tab.profile().repo.id)
job.updated.connect(self.tab._set_status)
job.result.connect(self.delete_result)
self.tab._toggle_all_buttons(False)
self.tab.app.jobs_manager.add_job(job)

def delete_result(self, result):
archives = result['params']['archives']
if result['returncode'] == 0:
if len(archives) > 1:
status = self.tab.tr('Archives deleted.')
else:
status = self.tab.tr('Archive deleted.')
self.tab._set_status(status)

repo = self.tab.profile().repo
for archive in archives:
archive_obj = ArchiveModel.get_or_none(name=archive, repo=repo)
if archive_obj:
archive_obj.delete_instance()
self.tab.populate_from_profile(preserve_view=True)

self.tab._toggle_all_buttons(True)
68 changes: 68 additions & 0 deletions src/vorta/views/archive/archive_maintenance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from vorta.borg.check import BorgCheckJob
from vorta.borg.compact import BorgCompactJob
from vorta.borg.prune import BorgPruneJob


class ArchiveMaintenance:
def __init__(self, tab):
self.tab = tab

def check_action(self):
params = BorgCheckJob.prepare(self.tab.profile())
if not params['ok']:
self.tab._set_status(params['message'])
return

# Conditions are met (borg binary available, etc)
archives = self.tab.selected_archives()
if archives:
params['cmd'][-1] += f'::{archives[0].name}'

job = BorgCheckJob(params['cmd'], params, self.tab.profile().repo.id)
job.updated.connect(self.tab._set_status)
job.result.connect(self.check_result)
self.tab._toggle_all_buttons(False)
self.tab.app.jobs_manager.add_job(job)

def check_result(self, result):
if result['returncode'] == 0:
self.tab._toggle_all_buttons(True)

def compact_action(self):
params = BorgCompactJob.prepare(self.tab.profile())
if params['ok']:
job = BorgCompactJob(params['cmd'], params, self.tab.profile().repo.id)
job.updated.connect(self.tab._set_status)
job.result.connect(self.compact_result)
self.tab._toggle_all_buttons(False)
self.tab.app.jobs_manager.add_job(job)
else:
self.tab._set_status(params['message'])

def compact_result(self, result):
self.tab._toggle_all_buttons(True)

def prune_action(self):
params = BorgPruneJob.prepare(self.tab.profile())
if params['ok']:
job = BorgPruneJob(params['cmd'], params, self.tab.profile().repo.id)
job.updated.connect(self.tab._set_status)
job.result.connect(self.prune_result)
self.tab._toggle_all_buttons(False)
self.tab.app.jobs_manager.add_job(job)
else:
self.tab._set_status(params['message'])

def prune_result(self, result):
if result['returncode'] == 0:
self.tab._set_status(self.tab.tr('Pruning finished.'))
self.tab.refresh_archive_list()
else:
self.tab._toggle_all_buttons(True)

def save_prune_setting(self, new_value=None):
profile = self.tab.profile()
for i in self.tab.prune_intervals:
setattr(profile, f'prune_{i}', getattr(self.tab, f'prune_{i}').value())
profile.prune_keep_within = self.tab.prune_keep_within.text()
profile.save()
154 changes: 24 additions & 130 deletions src/vorta/views/archive_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@
QWidget,
)

from vorta.borg.check import BorgCheckJob
from vorta.borg.compact import BorgCompactJob
from vorta.borg.delete import BorgDeleteJob
from vorta.borg.info_archive import BorgInfoArchiveJob
from vorta.borg.list_repo import BorgListRepoJob
from vorta.borg.prune import BorgPruneJob
from vorta.i18n import trans_late, translate
from vorta.i18n.richtext import escape, format_richtext, link
from vorta.store.models import ArchiveModel, SettingsModel
Expand All @@ -32,8 +28,10 @@
get_asset,
get_mount_points,
)
from vorta.views.archive.archive_delete import ArchiveDelete
from vorta.views.archive.archive_diff import ArchiveDiff
from vorta.views.archive.archive_extract import ArchiveExtract
from vorta.views.archive.archive_maintenance import ArchiveMaintenance
from vorta.views.archive.archive_mount import ArchiveMount
from vorta.views.archive.archive_rename import ArchiveRename
from vorta.views.base_tab import BaseTab
Expand Down Expand Up @@ -76,6 +74,8 @@ def __init__(self, parent=None, app=None, profile_provider=None):
self.archive_extract = ArchiveExtract(self)
self.archive_diff = ArchiveDiff(self)
self.archive_rename = ArchiveRename(self)
self.archive_delete = ArchiveDelete(self)
self.archive_maintenance = ArchiveMaintenance(self)

#: Tooltip dict to save the tooltips set in the designer
self.tooltip_dict: Dict[QWidget, str] = {}
Expand Down Expand Up @@ -138,14 +138,14 @@ def __init__(self, parent=None, app=None, profile_provider=None):
self.bMountArchive.clicked.connect(self.archive_mount.bmountarchive_clicked)
self.bRefreshArchive.clicked.connect(self.refresh_archive_info)
self.bRename.clicked.connect(self.archive_rename.cell_double_clicked)
self.bDelete.clicked.connect(self.delete_action)
self.bDelete.clicked.connect(self.archive_delete.delete_action)
self.bExtract.clicked.connect(self.archive_extract.extract_action)
self.compactButton.clicked.connect(self.compact_action)
self.compactButton.clicked.connect(self.archive_maintenance.compact_action)

# 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.bPrune.clicked.connect(self.archive_maintenance.prune_action)
self.bCheck.clicked.connect(self.archive_maintenance.check_action)
self.bDiff.clicked.connect(self.archive_diff.diff_action)
self.bMountRepo.clicked.connect(self.archive_mount.bmountrepo_clicked)

Expand All @@ -158,8 +158,8 @@ def __init__(self, parent=None, app=None, profile_provider=None):

# Connect prune setting signals (only once in init, not in populate_from_profile)
for i in self.prune_intervals:
getattr(self, f'prune_{i}').valueChanged.connect(self.save_prune_setting)
self.prune_keep_within.editingFinished.connect(self.save_prune_setting)
getattr(self, f'prune_{i}').valueChanged.connect(self.archive_maintenance.save_prune_setting)
self.prune_keep_within.editingFinished.connect(self.archive_maintenance.save_prune_setting)

self.track_profile_change(call_now=True)
self.set_icons()
Expand Down Expand Up @@ -211,7 +211,7 @@ def archiveitem_contextmenu(self, pos: QPoint):
(self.bMountArchive, self.archive_mount.bmountarchive_clicked),
(self.bExtract, self.archive_extract.extract_action),
(self.bRename, self.archive_rename.cell_double_clicked),
(self.bDelete, self.delete_action),
(self.bDelete, self.archive_delete.delete_action),
]

for button, connection in button_connection_pairs:
Expand Down Expand Up @@ -274,6 +274,19 @@ def _toggle_all_buttons(self, enabled=True):
# Restore states
self.on_selection_change()

def toggle_compact_button_visibility(self):
"""
Enable or disable the compact button depending on the Borg version.
This function runs once on startup, and every time the profile is changed.
"""
if borg_compat.check("COMPACT_SUBCOMMAND"):
self.compactButton.setEnabled(True)
self.compactButton.setToolTip(self.tooltip_dict[self.compactButton])
else:
self.compactButton.setEnabled(False)
tooltip = self.tooltip_dict[self.compactButton]
self.compactButton.setToolTip(tooltip + " " + self.tr("(This feature needs Borg 1.2.0 or higher)"))

def populate_from_profile(self, preserve_view=False):
"""Populate archive list and prune settings from profile."""
profile = self.profile()
Expand Down Expand Up @@ -461,59 +474,6 @@ def save_archive_template(self, tpl, key):
else:
self.prunePrefixPreview.setText(preview)

def check_action(self):
params = BorgCheckJob.prepare(self.profile())
if not params['ok']:
self._set_status(params['message'])
return

# Conditions are met (borg binary available, etc)
archives = self.selected_archives()
if archives:
params['cmd'][-1] += f'::{archives[0].name}'

job = BorgCheckJob(params['cmd'], params, self.profile().repo.id)
job.updated.connect(self._set_status)
job.result.connect(self.check_result)
self._toggle_all_buttons(False)
self.app.jobs_manager.add_job(job)

def check_result(self, result):
if result['returncode'] == 0:
self._toggle_all_buttons(True)

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)

def prune_action(self):
params = BorgPruneJob.prepare(self.profile())
if params['ok']:
job = BorgPruneJob(params['cmd'], params, self.profile().repo.id)
job.updated.connect(self._set_status)
job.result.connect(self.prune_result)
self._toggle_all_buttons(False)
self.app.jobs_manager.add_job(job)
else:
self._set_status(params['message'])

def prune_result(self, result):
if result['returncode'] == 0:
self._set_status(self.tr('Pruning finished.'))
self.refresh_archive_list()
else:
self._toggle_all_buttons(True)

def refresh_archive_list(self):
params = BorgListRepoJob.prepare(self.profile())
if params['ok']:
Expand Down Expand Up @@ -558,13 +518,6 @@ def selected_archive_name(self):
archives = self.selected_archives()
return archives[0].name if archives else None

def save_prune_setting(self, new_value=None):
profile = self.profile()
for i in self.prune_intervals:
setattr(profile, f'prune_{i}', getattr(self, f'prune_{i}').value())
profile.prune_keep_within = self.prune_keep_within.text()
profile.save()

def confirm_dialog(self, title, text):
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Information)
Expand All @@ -574,62 +527,3 @@ def confirm_dialog(self, title, text):
msg.button(QMessageBox.StandardButton.Yes).setText(self.tr("Yes"))
msg.button(QMessageBox.StandardButton.Cancel).setText(self.tr("Cancel"))
return msg.exec() == QMessageBox.StandardButton.Yes

def delete_action(self):
# Since this function modify the UI, we can't put the whole function in a JobQueue.

# determine selected archives
archives = [archive.name for archive in self.selected_archives()]

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

if len(archives) > 1:
body = self.tr("Are you sure you want to delete all the selected archives?")
else:
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:
if len(archives) > 1:
status = self.tr('Archives deleted.')
else:
status = self.tr('Archive deleted.')
self._set_status(status)

repo = self.profile().repo
for archive in archives:
archive_obj = ArchiveModel.get_or_none(name=archive, repo=repo)
if archive_obj:
archive_obj.delete_instance()
self.populate_from_profile(preserve_view=True)

self._toggle_all_buttons(True)

def toggle_compact_button_visibility(self):
"""
Enable or disable the compact button depending on the Borg version.
This function runs once on startup, and every time the profile is changed.
"""
if borg_compat.check("COMPACT_SUBCOMMAND"):
self.compactButton.setEnabled(True)
self.compactButton.setToolTip(self.tooltip_dict[self.compactButton])
else:
self.compactButton.setEnabled(False)
tooltip = self.tooltip_dict[self.compactButton]
self.compactButton.setToolTip(tooltip + " " + self.tr("(This feature needs Borg 1.2.0 or higher)"))
2 changes: 1 addition & 1 deletion tests/integration/test_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def test_archive_delete(qapp, qtbot, mocker, archive_env):
mocker.patch.object(vorta.views.archive_tab.ArchiveTab, 'confirm_dialog', lambda x, y, z: True)

tab.archiveTable.selectRow(0)
tab.delete_action()
tab.archive_delete.delete_action()
qtbot.waitUntil(lambda: 'Archive deleted.' in main.progressText.text(), **pytest._wait_defaults)

assert ArchiveModel.select().count() == archivesCount - 1
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_prune_intervals(qapp, qtbot):

for i in prune_intervals:
getattr(tab, f'prune_{i}').setValue(9)
tab.save_prune_setting(None)
tab.archive_maintenance.save_prune_setting(None)
profile = profile.refresh()
assert getattr(profile, f'prune_{i}') == 9

Expand Down Expand Up @@ -171,7 +171,7 @@ def test_archive_delete(qapp, qtbot, mocker, borg_json_output, archive_env):
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.ArchiveTab, 'confirm_dialog', lambda x, y, z: True)
tab.delete_action()
tab.archive_delete.delete_action()
qtbot.waitUntil(lambda: 'Archive deleted.' in main.progressText.text(), **pytest._wait_defaults)
assert ArchiveModel.select().count() == 1
assert tab.archiveTable.model().rowCount() == 1
Expand Down
Loading