Skip to content

Commit 53cd525

Browse files
author
Temp User
committed
feat: show busy dialog while parsing archive contents for extract
1 parent c28f8a9 commit 53cd525

3 files changed

Lines changed: 118 additions & 40 deletions

File tree

src/vorta/assets/UI/dialogs/repo/change_passphrase.ui

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,6 @@
5555
</property>
5656
</layout>
5757
</item>
58-
<item>
59-
<widget class="QLabel" name="errorText">
60-
<property name="sizePolicy">
61-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
62-
<horstretch>0</horstretch>
63-
<verstretch>0</verstretch>
64-
</sizepolicy>
65-
</property>
66-
<property name="minimumSize">
67-
<size>
68-
<width>0</width>
69-
<height>20</height>
70-
</size>
71-
</property>
72-
<property name="font">
73-
<font>
74-
<pointsize>11</pointsize>
75-
</font>
76-
</property>
77-
<property name="text">
78-
<string/>
79-
</property>
80-
<property name="textFormat">
81-
<enum>Qt::PlainText</enum>
82-
</property>
83-
<property name="alignment">
84-
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
85-
</property>
86-
<property name="wordWrap">
87-
<bool>true</bool>
88-
</property>
89-
</widget>
90-
</item>
9158
<item>
9259
<layout class="QHBoxLayout" name="horizontalLayout_3">
9360
<item>

src/vorta/views/archive_tab.py

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
QLayout,
1414
QMenu,
1515
QMessageBox,
16+
QProgressDialog,
1617
QStyledItemDelegate,
1718
QTableView,
1819
QTableWidgetItem,
@@ -23,7 +24,9 @@
2324
from vorta.borg.compact import BorgCompactJob
2425
from vorta.borg.delete import BorgDeleteJob
2526
from vorta.borg.diff import BorgDiffJob
27+
from vorta.borg.extract import BorgExtractJob
2628
from vorta.borg.info_archive import BorgInfoArchiveJob
29+
from vorta.borg.list_archive import BorgListArchiveJob
2730
from vorta.borg.list_repo import BorgListRepoJob
2831
from vorta.borg.prune import BorgPruneJob
2932
from vorta.borg.rename import BorgRenameJob
@@ -32,17 +35,19 @@
3235
from vorta.store.models import ArchiveModel, SettingsModel
3336
from vorta.utils import (
3437
borg_compat,
38+
choose_file_dialog,
3539
find_best_unit_for_sizes,
3640
format_archive_name,
3741
get_asset,
3842
get_mount_points,
3943
pretty_bytes,
4044
)
41-
from vorta.views.archive.archive_extract import ArchiveExtract
4245
from vorta.views.archive.archive_mount import ArchiveMount
4346
from vorta.views.base_tab import BaseTab
4447
from vorta.views.dialogs.archive import diff_result
48+
from vorta.views.dialogs.archive import extract as extract_dialog
4549
from vorta.views.dialogs.archive.diff_result import DiffResultDialog, DiffTree
50+
from vorta.views.dialogs.archive.extract import ExtractDialog, ExtractTree
4651
from vorta.views.source_tab import SizeItem
4752
from vorta.views.utils import get_colored_icon
4853

@@ -83,7 +88,6 @@ def __init__(self, parent=None, app=None, profile_provider=None):
8388
)
8489

8590
self.archive_mount = ArchiveMount(self)
86-
self.archive_extract = ArchiveExtract(self)
8791

8892
#: Tooltip dict to save the tooltips set in the designer
8993
self.tooltip_dict: Dict[QWidget, str] = {}
@@ -142,7 +146,7 @@ def __init__(self, parent=None, app=None, profile_provider=None):
142146
self.bRefreshArchive.clicked.connect(self.refresh_archive_info)
143147
self.bRename.clicked.connect(self.cell_double_clicked)
144148
self.bDelete.clicked.connect(self.delete_action)
145-
self.bExtract.clicked.connect(self.archive_extract.extract_action)
149+
self.bExtract.clicked.connect(self.extract_action)
146150
self.compactButton.clicked.connect(self.compact_action)
147151

148152
# other signals
@@ -212,7 +216,7 @@ def archiveitem_contextmenu(self, pos: QPoint):
212216
(self.bRefreshArchive, self.refresh_archive_info),
213217
(self.bDiff, self.diff_action),
214218
(self.bMountArchive, self.archive_mount.bmountarchive_clicked),
215-
(self.bExtract, self.archive_extract.extract_action),
219+
(self.bExtract, self.extract_action),
216220
(self.bRename, self.cell_double_clicked),
217221
(self.bDelete, self.delete_action),
218222
]
@@ -487,7 +491,8 @@ def check_action(self):
487491
archive_cell = self.archiveTable.item(row_selected[0].row(), 4)
488492
if archive_cell:
489493
archive_name = archive_cell.text()
490-
params['cmd'][-1] += f'::{archive_name}'
494+
cmd: list = params['cmd'] # type: ignore[index]
495+
cmd[-1] += f'::{archive_name}'
491496

492497
job = BorgCheckJob(params['cmd'], params, self.profile().repo.id)
493498
job.updated.connect(self._set_status)
@@ -591,6 +596,85 @@ def save_prune_setting(self, new_value=None):
591596
profile.prune_keep_within = self.prune_keep_within.text()
592597
profile.save()
593598

599+
def extract_action(self):
600+
"""
601+
Open a dialog for choosing what to extract from the selected archive.
602+
"""
603+
profile = self.profile()
604+
605+
row_selected = self.archiveTable.selectionModel().selectedRows()
606+
if row_selected:
607+
archive_cell = self.archiveTable.item(row_selected[0].row(), 4)
608+
if archive_cell:
609+
archive_name = archive_cell.text()
610+
params = BorgListArchiveJob.prepare(profile, archive_name)
611+
612+
if not params['ok']:
613+
self._set_status(params['message'])
614+
return
615+
self._set_status('')
616+
self._toggle_all_buttons(False)
617+
618+
job = BorgListArchiveJob(params['cmd'], params, self.profile().repo.id)
619+
job.updated.connect(self.mountErrors.setText)
620+
job.result.connect(self.extract_list_result)
621+
self.app.jobs_manager.add_job(job)
622+
return job
623+
else:
624+
self._set_status(self.tr('Select an archive to restore first.'))
625+
626+
def extract_list_result(self, result):
627+
"""Process the contents of the archive to extract."""
628+
self._set_status('')
629+
if result['returncode'] == 0:
630+
archive = ArchiveModel.get(name=result['params']['archive_name'])
631+
model = ExtractTree()
632+
633+
progress = QProgressDialog(self.tr("Processing archive contents…"), None, 0, 0, self)
634+
progress.setWindowTitle(self.tr("Please wait"))
635+
progress.setWindowModality(Qt.WindowModality.WindowModal)
636+
progress.setMinimumDuration(0)
637+
progress.setValue(0)
638+
self._extract_progress = progress
639+
640+
self._t = extract_dialog.ParseThread(result['data'], model)
641+
self._t.finished.connect(self._extract_progress.close)
642+
self._t.finished.connect(self._extract_progress.deleteLater)
643+
self._t.finished.connect(lambda: self.extract_show_dialog(archive, model))
644+
self._t.start()
645+
646+
def extract_show_dialog(self, archive, model):
647+
"""Show the dialog for choosing the archive contents to extract."""
648+
self._set_status('')
649+
650+
def process_result():
651+
def receive():
652+
extraction_folder = dialog.selectedFiles()
653+
if extraction_folder:
654+
params = BorgExtractJob.prepare(self.profile(), archive.name, model, extraction_folder[0])
655+
if params['ok']:
656+
self._toggle_all_buttons(False)
657+
job = BorgExtractJob(params['cmd'], params, self.profile().repo.id)
658+
job.updated.connect(self.mountErrors.setText)
659+
job.result.connect(self.extract_archive_result)
660+
self.app.jobs_manager.add_job(job)
661+
else:
662+
self._set_status(params['message'])
663+
664+
dialog = choose_file_dialog(self, self.tr("Choose Extraction Point"), want_folder=True)
665+
dialog.open(receive)
666+
667+
window = ExtractDialog(archive, model)
668+
self._toggle_all_buttons(True)
669+
window.setParent(self, QtCore.Qt.WindowType.Sheet)
670+
self._window = window # for testing
671+
window.show()
672+
window.accepted.connect(process_result)
673+
674+
def extract_archive_result(self, result):
675+
"""Finished extraction."""
676+
self._toggle_all_buttons(True)
677+
594678
def cell_double_clicked(self, row=None, column=None):
595679
if not self.bRename.isEnabled():
596680
return

tests/unit/test_archives.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import psutil
44
import pytest
55
from PyQt6 import QtCore
6-
from PyQt6.QtWidgets import QMenu
6+
from PyQt6.QtWidgets import QMenu, QProgressDialog
77
from test_constants import TEST_TEMP_DIR
88

99
import vorta.borg
@@ -124,7 +124,7 @@ def test_archive_extract(qapp, qtbot, mocker, borg_json_output, archive_env):
124124
stdout, stderr = borg_json_output('list_archive')
125125
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
126126
mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result)
127-
tab.archive_extract.extract_action()
127+
tab.extract_action()
128128

129129
qtbot.waitUntil(lambda: hasattr(tab, '_window'), **pytest._wait_defaults)
130130

@@ -133,6 +133,33 @@ def test_archive_extract(qapp, qtbot, mocker, borg_json_output, archive_env):
133133
assert 'test-archive, 2000' in tab._window.archiveNameLabel.text()
134134

135135

136+
def test_archive_extract_progress_dialog(qapp, qtbot, mocker, borg_json_output, archive_env):
137+
"""Progress dialog is shown while parsing archive contents and closed when done."""
138+
main, tab = archive_env
139+
tab.archiveTable.selectRow(0)
140+
stdout, stderr = borg_json_output('list_archive')
141+
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
142+
mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result)
143+
144+
# Spy on QProgressDialog.show to verify dialog is shown
145+
show_spy = mocker.spy(QProgressDialog, 'show')
146+
147+
tab.extract_action()
148+
149+
# Wait for progress dialog to appear
150+
qtbot.waitUntil(lambda: hasattr(tab, '_extract_progress'), **pytest._wait_defaults)
151+
progress = tab._extract_progress
152+
153+
# Wait for extraction to complete (window appears)
154+
qtbot.waitUntil(lambda: hasattr(tab, '_window'), **pytest._wait_defaults)
155+
156+
# Verify dialog was shown (spy caught the show call)
157+
assert show_spy.called, "QProgressDialog.show() was not called"
158+
159+
# Verify dialog is closed after extraction completes
160+
assert not progress.isVisible(), "Progress dialog should be closed after extraction"
161+
162+
136163
def test_archive_delete(qapp, qtbot, mocker, borg_json_output, archive_env):
137164
main, tab = archive_env
138165

0 commit comments

Comments
 (0)