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