Skip to content

Commit d90b48c

Browse files
committed
Allow user to select compression filter
Provide user a selection of default compression filters in addition to the available recognised filters. Allow user to save the filter. Requires: QubesOS/qubes-core-admin-client#346 Related: QubesOS/qubes-issues#8211 Related: QubesOS/qubes-issues#8291
1 parent 6d7ac61 commit d90b48c

File tree

3 files changed

+136
-18
lines changed

3 files changed

+136
-18
lines changed

qubesmanager/backup.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import signal
2424
from qubesadmin import exc
2525
from qubesadmin import utils as admin_utils
26+
from qubesadmin.backup.restore \
27+
import KNOWN_COMPRESSION_FILTERS, OPTIONAL_COMPRESSION_FILTERS
2628

2729
from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error
2830
from qubesmanager import ui_backupdlg # pylint: disable=no-name-in-module
@@ -129,6 +131,13 @@ def __init__(self, qt_app, qubes_app, dispatcher, parent=None):
129131
self.appvm_combobox.findText("dom0"))
130132

131133
self.unrecognized_config_label.setVisible(False)
134+
135+
self.compression_combobox.addItem("Default (gzip)")
136+
self.compression_combobox.addItems(KNOWN_COMPRESSION_FILTERS)
137+
self.compression_combobox.addItems(
138+
[c for c in OPTIONAL_COMPRESSION_FILTERS if shutil.which(c)]
139+
)
140+
self.compression_combobox.addItem("Disabled (uncompressed)")
132141
self.load_settings()
133142

134143
self.show_passwd_button.pressed.connect(self.show_hide_password)
@@ -234,7 +243,21 @@ def load_settings(self):
234243
self.save_passphrase_checkbox.setChecked(False)
235244

236245
if 'compression' in profile_data:
237-
self.compress_checkbox.setChecked(profile_data['compression'])
246+
if isinstance(profile_data["compression"], bool):
247+
if profile_data["compression"]:
248+
# Technically this is necessary as the default index is -1
249+
self.compression_combobox.setCurrentIndex(0)
250+
else:
251+
self.compression_combobox.setCurrentIndex(
252+
self.compression_combobox.count() - 1
253+
)
254+
else:
255+
for i in range(self.compression_combobox.count()):
256+
if profile_data[
257+
"compression"
258+
] == self.compression_combobox.itemText(i):
259+
self.compression_combobox.setCurrentIndex(i)
260+
break
238261

239262
def save_settings(self, use_temp, save_passphrase=True):
240263
"""
@@ -244,10 +267,20 @@ def save_settings(self, use_temp, save_passphrase=True):
244267
:param use_temp: whether to use temporary profile (True) or the default
245268
backup profile (False)
246269
"""
247-
settings = {'destination_vm': self.appvm_combobox.currentText(),
248-
'destination_path': self.dir_line_edit.text(),
249-
'include': [vm.name for vm in self.selected_vms],
250-
'compression': self.compress_checkbox.isChecked()}
270+
if self.compression_combobox.currentIndex() != -1:
271+
compression_filter = self.compression_combobox.currentText()
272+
if compression_filter.startswith("Default"):
273+
compression_filter = True
274+
elif compression_filter.startswith("Disabled"):
275+
compression_filter = False
276+
else:
277+
compression_filter = True
278+
settings = {
279+
"destination_vm": self.appvm_combobox.currentText(),
280+
"destination_path": self.dir_line_edit.text(),
281+
"include": [vm.name for vm in self.selected_vms],
282+
"compression": compression_filter
283+
}
251284

252285
if save_passphrase:
253286
settings['passphrase_text'] = self.passphrase_line_edit.text()

qubesmanager/tests/test_backup.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ def test_00_load_backup(backup_dlg):
7171

7272

7373
def test_01_correct_default(backup_dlg):
74-
# backup is compressed
75-
assert backup_dlg.compress_checkbox.isChecked()
74+
# backup compresssion is the default (no item selected or item 0)
75+
assert backup_dlg.compression_combobox.currentIndex() in [0, -1]
7676

7777
# passphrase is empty
7878
assert backup_dlg.passphrase_line_edit.text() == "", "Password non-empty"
@@ -175,7 +175,10 @@ def test_10_do_backup(mock_open, backup_dlg):
175175
backup_dlg.passphrase_line_edit_verify.setText("pass")
176176
backup_dlg.save_profile_checkbox.setChecked(False)
177177
backup_dlg.turn_off_checkbox.setChecked(False)
178-
backup_dlg.compress_checkbox.setChecked(False)
178+
backup_dlg.compression_combobox.addItem("Disabled (uncompressed")
179+
backup_dlg.compression_combobox.setCurrentIndex(
180+
backup_dlg.compression_combobox.count() - 1
181+
)
179182

180183
expected_call = ('dom0', 'admin.backup.Info', 'qubes-manager-backup-tmp',
181184
None)
@@ -233,7 +236,7 @@ def test_20_loading_settings(mock_load, test_qubes_app, qapp):
233236
"Passphrase not loaded"
234237
assert backup_dlg.passphrase_line_edit_verify.text() == "longerPassPhrase" \
235238
, "Passphrase verify not loaded"
236-
assert backup_dlg.compress_checkbox.isChecked()
239+
assert backup_dlg.compression_combobox.currentIndex() == 0
237240

238241
# check that 'include' vms were not pre-selected
239242
include_in_backups_no = len(
@@ -247,6 +250,67 @@ def test_20_loading_settings(mock_load, test_qubes_app, qapp):
247250
assert not backup_dlg.unrecognized_config_label.isVisible()
248251

249252

253+
@mock.patch('qubesmanager.backup_utils.load_backup_profile')
254+
def test_20_loading_settings_nocomp(mock_load, test_qubes_app, qapp):
255+
256+
mock_load.return_value = {
257+
'destination_vm': 'test-blue',
258+
'destination_path': "/home",
259+
'include': ['dom0', 'test-red', 'sys-net'],
260+
'passphrase_text': "longerPassPhrase",
261+
'compression': False
262+
}
263+
264+
dispatcher = MockAsyncDispatcher(test_qubes_app)
265+
backup_dlg = backup.BackupVMsWindow(qapp, test_qubes_app, dispatcher)
266+
# needed because otherwise the wizard will not test correctly
267+
backup_dlg.show()
268+
269+
# check if last compression filter (Disabled) is selected
270+
assert backup_dlg.compression_combobox.currentIndex() == \
271+
backup_dlg.compression_combobox.count() - 1
272+
273+
274+
@mock.patch('qubesmanager.backup_utils.load_backup_profile')
275+
def test_20_loading_settings_bzip2(mock_load, test_qubes_app, qapp):
276+
277+
mock_load.return_value = {
278+
'destination_vm': 'test-blue',
279+
'destination_path': "/home",
280+
'include': ['dom0', 'test-red', 'sys-net'],
281+
'passphrase_text': "longerPassPhrase",
282+
'compression': "bzip2"
283+
}
284+
285+
dispatcher = MockAsyncDispatcher(test_qubes_app)
286+
backup_dlg = backup.BackupVMsWindow(qapp, test_qubes_app, dispatcher)
287+
# needed because otherwise the wizard will not test correctly
288+
backup_dlg.show()
289+
290+
# check if the right compression filter is selected
291+
assert backup_dlg.compression_combobox.currentText() == "bzip2"
292+
293+
294+
@mock.patch('qubesmanager.backup_utils.load_backup_profile')
295+
def test_20_loading_settings_pkzip(mock_load, test_qubes_app, qapp):
296+
297+
mock_load.return_value = {
298+
'destination_vm': 'test-blue',
299+
'destination_path': "/home",
300+
'include': ['dom0', 'test-red', 'sys-net'],
301+
'passphrase_text': "longerPassPhrase",
302+
'compression': "pkzip"
303+
}
304+
305+
dispatcher = MockAsyncDispatcher(test_qubes_app)
306+
backup_dlg = backup.BackupVMsWindow(qapp, test_qubes_app, dispatcher)
307+
# needed because otherwise the wizard will not test correctly
308+
backup_dlg.show()
309+
310+
# check if the compression filter reverts to the default
311+
assert backup_dlg.compression_combobox.currentIndex() == 0
312+
313+
250314
@mock.patch('qubesmanager.backup_utils.load_backup_profile')
251315
def test_21_loading_settings_error(mock_load, test_qubes_app, qapp):
252316
mock_load.return_value = {

ui/backupdlg.ui

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,35 @@
8181
</layout>
8282
</item>
8383
<item>
84-
<widget class="QCheckBox" name="compress_checkbox">
85-
<property name="text">
86-
<string>Compress backup</string>
87-
</property>
88-
<property name="checked">
89-
<bool>true</bool>
90-
</property>
91-
</widget>
84+
<layout class="QGridLayout" name="gridLayout_compression">
85+
<item row="0" column="1">
86+
<layout class="QHBoxLayout" name="horizontalLayout_compression">
87+
<item>
88+
<widget class="QLabel" name="label_compression">
89+
<property name="text">
90+
<string>Compression filter:</string>
91+
</property>
92+
</widget>
93+
</item>
94+
<item>
95+
<widget class="QComboBox" name="compression_combobox"/>
96+
</item>
97+
<item>
98+
<spacer name="horizontalSpacer_compression">
99+
<property name="orientation">
100+
<enum>Qt::Horizontal</enum>
101+
</property>
102+
<property name="sizeHint" stdset="0">
103+
<size>
104+
<width>40</width>
105+
<height>20</height>
106+
</size>
107+
</property>
108+
</spacer>
109+
</item>
110+
</layout>
111+
</item>
112+
</layout>
92113
</item>
93114
<item>
94115
<widget class="QLabel" name="metadata_warning_label">
@@ -452,7 +473,7 @@ li.checked::marker { content: &quot;\2612&quot;; }
452473
</widget>
453474
</widget>
454475
<tabstops>
455-
<tabstop>compress_checkbox</tabstop>
476+
<tabstop>compression_combobox</tabstop>
456477
<tabstop>appvm_combobox</tabstop>
457478
<tabstop>dir_line_edit</tabstop>
458479
<tabstop>select_path_button</tabstop>

0 commit comments

Comments
 (0)