diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py
index 8629951c..4168dd59 100644
--- a/qubesmanager/backup.py
+++ b/qubesmanager/backup.py
@@ -23,6 +23,8 @@
import signal
from qubesadmin import exc
from qubesadmin import utils as admin_utils
+from qubesadmin.backup.restore \
+ import KNOWN_COMPRESSION_FILTERS, OPTIONAL_COMPRESSION_FILTERS
from PyQt6 import QtCore, QtWidgets, QtGui # pylint: disable=import-error
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):
self.appvm_combobox.findText("dom0"))
self.unrecognized_config_label.setVisible(False)
+
+ self.compression_combobox.addItem("Default (gzip)")
+ self.compression_combobox.addItems(KNOWN_COMPRESSION_FILTERS)
+ self.compression_combobox.addItems(
+ [c for c in OPTIONAL_COMPRESSION_FILTERS if shutil.which(c)]
+ )
+ self.compression_combobox.addItem("Disabled (uncompressed)")
self.load_settings()
self.show_passwd_button.pressed.connect(self.show_hide_password)
@@ -234,7 +243,21 @@ def load_settings(self):
self.save_passphrase_checkbox.setChecked(False)
if 'compression' in profile_data:
- self.compress_checkbox.setChecked(profile_data['compression'])
+ if isinstance(profile_data["compression"], bool):
+ if profile_data["compression"]:
+ # Technically this is necessary as the default index is -1
+ self.compression_combobox.setCurrentIndex(0)
+ else:
+ self.compression_combobox.setCurrentIndex(
+ self.compression_combobox.count() - 1
+ )
+ else:
+ for i in range(self.compression_combobox.count()):
+ if profile_data[
+ "compression"
+ ] == self.compression_combobox.itemText(i):
+ self.compression_combobox.setCurrentIndex(i)
+ break
def save_settings(self, use_temp, save_passphrase=True):
"""
@@ -244,10 +267,20 @@ def save_settings(self, use_temp, save_passphrase=True):
:param use_temp: whether to use temporary profile (True) or the default
backup profile (False)
"""
- settings = {'destination_vm': self.appvm_combobox.currentText(),
- 'destination_path': self.dir_line_edit.text(),
- 'include': [vm.name for vm in self.selected_vms],
- 'compression': self.compress_checkbox.isChecked()}
+ if self.compression_combobox.currentIndex() != -1:
+ compression_filter = self.compression_combobox.currentText()
+ if compression_filter.startswith("Default"):
+ compression_filter = True
+ elif compression_filter.startswith("Disabled"):
+ compression_filter = False
+ else:
+ compression_filter = True
+ settings = {
+ "destination_vm": self.appvm_combobox.currentText(),
+ "destination_path": self.dir_line_edit.text(),
+ "include": [vm.name for vm in self.selected_vms],
+ "compression": compression_filter
+ }
if save_passphrase:
settings['passphrase_text'] = self.passphrase_line_edit.text()
diff --git a/qubesmanager/tests/test_backup.py b/qubesmanager/tests/test_backup.py
index 1028655b..700eece2 100644
--- a/qubesmanager/tests/test_backup.py
+++ b/qubesmanager/tests/test_backup.py
@@ -71,8 +71,8 @@ def test_00_load_backup(backup_dlg):
def test_01_correct_default(backup_dlg):
- # backup is compressed
- assert backup_dlg.compress_checkbox.isChecked()
+ # backup compresssion is the default (no item selected or item 0)
+ assert backup_dlg.compression_combobox.currentIndex() in [0, -1]
# passphrase is empty
assert backup_dlg.passphrase_line_edit.text() == "", "Password non-empty"
@@ -175,7 +175,10 @@ def test_10_do_backup(mock_open, backup_dlg):
backup_dlg.passphrase_line_edit_verify.setText("pass")
backup_dlg.save_profile_checkbox.setChecked(False)
backup_dlg.turn_off_checkbox.setChecked(False)
- backup_dlg.compress_checkbox.setChecked(False)
+ backup_dlg.compression_combobox.addItem("Disabled (uncompressed")
+ backup_dlg.compression_combobox.setCurrentIndex(
+ backup_dlg.compression_combobox.count() - 1
+ )
expected_call = ('dom0', 'admin.backup.Info', 'qubes-manager-backup-tmp',
None)
@@ -233,7 +236,7 @@ def test_20_loading_settings(mock_load, test_qubes_app, qapp):
"Passphrase not loaded"
assert backup_dlg.passphrase_line_edit_verify.text() == "longerPassPhrase" \
, "Passphrase verify not loaded"
- assert backup_dlg.compress_checkbox.isChecked()
+ assert backup_dlg.compression_combobox.currentIndex() == 0
# check that 'include' vms were not pre-selected
include_in_backups_no = len(
@@ -247,6 +250,67 @@ def test_20_loading_settings(mock_load, test_qubes_app, qapp):
assert not backup_dlg.unrecognized_config_label.isVisible()
+@mock.patch('qubesmanager.backup_utils.load_backup_profile')
+def test_20_loading_settings_nocomp(mock_load, test_qubes_app, qapp):
+
+ mock_load.return_value = {
+ 'destination_vm': 'test-blue',
+ 'destination_path': "/home",
+ 'include': ['dom0', 'test-red', 'sys-net'],
+ 'passphrase_text': "longerPassPhrase",
+ 'compression': False
+ }
+
+ dispatcher = MockAsyncDispatcher(test_qubes_app)
+ backup_dlg = backup.BackupVMsWindow(qapp, test_qubes_app, dispatcher)
+ # needed because otherwise the wizard will not test correctly
+ backup_dlg.show()
+
+ # check if last compression filter (Disabled) is selected
+ assert backup_dlg.compression_combobox.currentIndex() == \
+ backup_dlg.compression_combobox.count() - 1
+
+
+@mock.patch('qubesmanager.backup_utils.load_backup_profile')
+def test_20_loading_settings_bzip2(mock_load, test_qubes_app, qapp):
+
+ mock_load.return_value = {
+ 'destination_vm': 'test-blue',
+ 'destination_path': "/home",
+ 'include': ['dom0', 'test-red', 'sys-net'],
+ 'passphrase_text': "longerPassPhrase",
+ 'compression': "bzip2"
+ }
+
+ dispatcher = MockAsyncDispatcher(test_qubes_app)
+ backup_dlg = backup.BackupVMsWindow(qapp, test_qubes_app, dispatcher)
+ # needed because otherwise the wizard will not test correctly
+ backup_dlg.show()
+
+ # check if the right compression filter is selected
+ assert backup_dlg.compression_combobox.currentText() == "bzip2"
+
+
+@mock.patch('qubesmanager.backup_utils.load_backup_profile')
+def test_20_loading_settings_pkzip(mock_load, test_qubes_app, qapp):
+
+ mock_load.return_value = {
+ 'destination_vm': 'test-blue',
+ 'destination_path': "/home",
+ 'include': ['dom0', 'test-red', 'sys-net'],
+ 'passphrase_text': "longerPassPhrase",
+ 'compression': "pkzip"
+ }
+
+ dispatcher = MockAsyncDispatcher(test_qubes_app)
+ backup_dlg = backup.BackupVMsWindow(qapp, test_qubes_app, dispatcher)
+ # needed because otherwise the wizard will not test correctly
+ backup_dlg.show()
+
+ # check if the compression filter reverts to the default
+ assert backup_dlg.compression_combobox.currentIndex() == 0
+
+
@mock.patch('qubesmanager.backup_utils.load_backup_profile')
def test_21_loading_settings_error(mock_load, test_qubes_app, qapp):
mock_load.return_value = {
diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui
index 4b8402e8..991c56c9 100644
--- a/ui/backupdlg.ui
+++ b/ui/backupdlg.ui
@@ -81,14 +81,35 @@
-
-
-
- Compress backup
-
-
- true
-
-
+
+
-
+
+
-
+
+
+ Compression filter:
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
-
@@ -452,7 +473,7 @@ li.checked::marker { content: "\2612"; }
- compress_checkbox
+ compression_combobox
appvm_combobox
dir_line_edit
select_path_button