diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index acc9b96..1ff1f71 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -107,7 +107,7 @@ class MainWindow(QtWidgets.QMainWindow): self.toggle_updates_action.triggered.connect(self.toggle_updates_triggered) self.toggle_updates_action.setCheckable(True) self.toggle_updates_action.setChecked( - bool(self.dangerzone.settings.get("updater_check")) + bool(self.dangerzone.settings.get("updater_release_check")) ) # Add the "Exit" action @@ -184,8 +184,8 @@ class MainWindow(QtWidgets.QMainWindow): def show_update_success(self) -> None: """Inform the user about a new Dangerzone release.""" - version = self.dangerzone.settings.get("updater_latest_version") - changelog = self.dangerzone.settings.get("updater_latest_changelog") + version = self.dangerzone.settings.get("updater_release_latest_version") + changelog = self.dangerzone.settings.get("updater_release_latest_changelog") changelog_widget = CollapsibleBox("What's New?") changelog_layout = QtWidgets.QVBoxLayout() @@ -230,7 +230,7 @@ class MainWindow(QtWidgets.QMainWindow): def toggle_updates_triggered(self) -> None: """Change the underlying update check settings based on the user's choice.""" check = self.toggle_updates_action.isChecked() - self.dangerzone.settings.set("updater_check", check) + self.dangerzone.settings.set("updater_release_check", check) self.dangerzone.settings.save() def handle_updates(self, report: UpdateReport) -> None: @@ -274,8 +274,12 @@ class MainWindow(QtWidgets.QMainWindow): hamburger_menu.insertAction(sep, error_action) else: log.debug(f"Handling new version: {report.version}") - self.dangerzone.settings.set("updater_latest_version", report.version) - self.dangerzone.settings.set("updater_latest_changelog", report.changelog) + self.dangerzone.settings.set( + "updater_release_latest_version", report.version + ) + self.dangerzone.settings.set( + "updater_release_latest_changelog", report.changelog + ) self.dangerzone.settings.set("updater_errors", 0) # FIXME: Save the settings to the filesystem only when they have really changed, diff --git a/dangerzone/gui/updater.py b/dangerzone/gui/updater.py index 396de21..807d98d 100644 --- a/dangerzone/gui/updater.py +++ b/dangerzone/gui/updater.py @@ -126,11 +126,11 @@ class UpdaterThread(QtCore.QThread): @property def check(self) -> Optional[bool]: - return self.dangerzone.settings.get("updater_check") + return self.dangerzone.settings.get("updater_release_check") @check.setter def check(self, val: bool) -> None: - self.dangerzone.settings.set("updater_check", val, autosave=True) + self.dangerzone.settings.set("updater_release_check", val, autosave=True) def prompt_for_checks(self) -> Optional[bool]: """Ask the user if they want to be informed about Dangerzone updates.""" @@ -256,12 +256,14 @@ class UpdaterThread(QtCore.QThread): 2. In GitHub, by hitting the latest releases API. """ log.debug("Checking for Dangerzone updates") - latest_version = self.dangerzone.settings.get("updater_latest_version") + latest_version = self.dangerzone.settings.get("updater_release_latest_version") if version.parse(get_version()) < version.parse(latest_version): log.debug("Determined that there is an update due to cached results") return UpdateReport( version=latest_version, - changelog=self.dangerzone.settings.get("updater_latest_changelog"), + changelog=self.dangerzone.settings.get( + "updater_release_latest_changelog" + ), ) # If the previous check happened before the cooldown period expires, do not diff --git a/dangerzone/settings.py b/dangerzone/settings.py index 32026d6..0e13f0a 100644 --- a/dangerzone/settings.py +++ b/dangerzone/settings.py @@ -26,6 +26,8 @@ class Settings: ) self.default_settings: Dict[str, Any] = self.generate_default_settings() self.load() + self.migrate_settings() + self.save() @classmethod def generate_default_settings(cls) -> Dict[str, Any]: @@ -37,14 +39,33 @@ class Settings: "open": True, "open_app": None, "safe_extension": SAFE_EXTENSION, - "updater_check": None, - "updater_last_check": None, # last check in UNIX epoch (secs since 1970) # FIXME: How to invalidate those if they change upstream? - "updater_latest_version": get_version(), - "updater_latest_changelog": "", + "updater_last_check": None, # last check in UNIX epoch (secs since 1970) "updater_errors": 0, + "updater_release_check": None, + "updater_release_latest_version": get_version(), + "updater_release_latest_changelog": "", + "updater_docker_check": None, + "updater_docker_latest_version": None, + "updater_docker_latest_changelog": "", } + def migrate_settings(self): + # Backward compatibility layer for the settings. + name_replacements = ( + # Some `updater_*` settings have been replaced with their + # `updater_release_*` counterpart to better differenciate the type of + # updates they are tracking, (as we are also checking for Docker Desktop + # updates). + ("updater_check", "updater_release_check"), + ("updater_latest_version", "updater_release_latest_version"), + ("updater_latest_changelog", "updater_release_latest_changelog"), + ) + + for old_name, new_name in name_replacements: + if old_name in self.settings: + self.settings.set(new_name, self.settings.pop(old_name)) + def get(self, key: str) -> Any: return self.settings[key] @@ -75,7 +96,8 @@ class Settings: for key in self.default_settings: if key not in self.settings: self.settings[key] = self.default_settings[key] - elif key == "updater_latest_version": + elif key == "updater_release_latest_version": + # Update the version with the current one if needed if version.parse(get_version()) > version.parse(self.get(key)): self.set(key, get_version()) @@ -88,8 +110,6 @@ class Settings: log.info("Settings file doesn't exist, starting with default") self.settings = self.default_settings - self.save() - def save(self) -> None: os.makedirs(self.dangerzone.appdata_path, exist_ok=True) with open(self.settings_filename, "w") as settings_file: diff --git a/tests/gui/test_main_window.py b/tests/gui/test_main_window.py index 16f23fd..4795911 100644 --- a/tests/gui/test_main_window.py +++ b/tests/gui/test_main_window.py @@ -38,7 +38,7 @@ def test_default_menu( updater: UpdaterThread, ) -> None: """Check that the default menu entries are in order.""" - updater.dangerzone.settings.set("updater_check", True) + updater.dangerzone.settings.set("updater_release_check", True) window = MainWindow(updater.dangerzone) menu_actions = window.hamburger_button.menu().actions() @@ -56,7 +56,7 @@ def test_default_menu( toggle_updates_action.trigger() assert not toggle_updates_action.isChecked() - assert updater.dangerzone.settings.get("updater_check") is False + assert updater.dangerzone.settings.get("updater_release_check") is False def test_no_update( @@ -69,12 +69,12 @@ def test_no_update( # Check that when no update is detected, e.g., due to update cooldown, an empty # report is received that does not affect the menu entries. curtime = int(time.time()) - updater.dangerzone.settings.set("updater_check", True) + updater.dangerzone.settings.set("updater_release_check", True) updater.dangerzone.settings.set("updater_errors", 9) updater.dangerzone.settings.set("updater_last_check", curtime) expected_settings = default_updater_settings() - expected_settings["updater_check"] = True + expected_settings["updater_release_check"] = True expected_settings["updater_errors"] = 0 # errors must be cleared expected_settings["updater_last_check"] = curtime @@ -107,7 +107,7 @@ def test_update_detected( ) -> None: """Test that a newly detected version leads to a notification to the user.""" - qt_updater.dangerzone.settings.set("updater_check", True) + qt_updater.dangerzone.settings.set("updater_release_check", True) qt_updater.dangerzone.settings.set("updater_last_check", 0) qt_updater.dangerzone.settings.set("updater_errors", 9) @@ -138,12 +138,12 @@ def test_update_detected( # Check that the settings have been updated properly. expected_settings = default_updater_settings() - expected_settings["updater_check"] = True + expected_settings["updater_release_check"] = True expected_settings["updater_last_check"] = qt_updater.dangerzone.settings.get( "updater_last_check" ) - expected_settings["updater_latest_version"] = "99.9.9" - expected_settings["updater_latest_changelog"] = "

changelog

" + expected_settings["updater_release_latest_version"] = "99.9.9" + expected_settings["updater_release_latest_changelog"] = "

changelog

" expected_settings["updater_errors"] = 0 assert qt_updater.dangerzone.settings.get_updater_settings() == expected_settings @@ -219,7 +219,7 @@ def test_update_error( ) -> None: """Test that an error during an update check leads to a notification to the user.""" # Test 1 - Check that the first error does not notify the user. - qt_updater.dangerzone.settings.set("updater_check", True) + qt_updater.dangerzone.settings.set("updater_release_check", True) qt_updater.dangerzone.settings.set("updater_last_check", 0) qt_updater.dangerzone.settings.set("updater_errors", 0) @@ -246,7 +246,7 @@ def test_update_error( # Check that the settings have been updated properly. expected_settings = default_updater_settings() - expected_settings["updater_check"] = True + expected_settings["updater_release_check"] = True expected_settings["updater_last_check"] = qt_updater.dangerzone.settings.get( "updater_last_check" ) diff --git a/tests/gui/test_updater.py b/tests/gui/test_updater.py index 430a621..244958e 100644 --- a/tests/gui/test_updater.py +++ b/tests/gui/test_updater.py @@ -69,22 +69,24 @@ def test_post_0_4_2_settings( ) -> None: """Check settings of installations post-0.4.2. - Installations from 0.4.2 onwards will have a "updater_latest_version" field in their + Installations from 0.4.2 onwards will have a "updater_release_latest_version" field in their settings. When these installations get upgraded to a newer version, we must make sure that this field becomes equal to the new version, so that the user is not erroneously prompted to a version they already have. """ # Store the settings of Dangerzone 0.4.2 to the filesystem. old_settings = settings.Settings.generate_default_settings() - old_settings["updater_latest_version"] = "0.4.2" + old_settings["updater_release_latest_version"] = "0.4.2" save_settings(tmp_path, old_settings) # Mimic an upgrade to version 0.4.3, by making Dangerzone report that the current # version is 0.4.3. expected_settings = default_updater_settings() - expected_settings["updater_latest_version"] = "0.4.3" + expected_settings["updater_release_latest_version"] = "0.4.3" monkeypatch.setattr( - settings, "get_version", lambda: expected_settings["updater_latest_version"] + settings, + "get_version", + lambda: expected_settings["updater_release_latest_version"], ) # Ensure that the Settings class will correct the latest version field to 0.4.3. @@ -92,13 +94,14 @@ def test_post_0_4_2_settings( assert updater.dangerzone.settings.get_updater_settings() == expected_settings # Simulate an updater check that found a newer Dangerzone version (e.g., 0.4.4). - expected_settings["updater_latest_version"] = "0.4.4" + expected_settings["updater_release_latest_version"] = "0.4.4" updater.dangerzone.settings.set( - "updater_latest_version", expected_settings["updater_latest_version"] + "updater_release_latest_version", + expected_settings["updater_release_latest_version"], ) updater.dangerzone.settings.save() - # Ensure that the Settings class will leave the "updater_latest_version" field + # Ensure that the Settings class will leave the "updater_release_latest_version" field # intact the next time we reload the settings. updater.dangerzone.settings.load() assert updater.dangerzone.settings.get_updater_settings() == expected_settings @@ -108,7 +111,7 @@ def test_post_0_4_2_settings( def test_linux_no_check(updater: UpdaterThread, monkeypatch: MonkeyPatch) -> None: """Ensure that Dangerzone on Linux does not make any update check.""" expected_settings = default_updater_settings() - expected_settings["updater_check"] = False + expected_settings["updater_release_check"] = False expected_settings["updater_last_check"] = None # XXX: Simulate Dangerzone installed via package manager. @@ -127,7 +130,7 @@ def test_user_prompts( # When Dangerzone runs for the first time, users should not be asked to enable # updates. expected_settings = default_updater_settings() - expected_settings["updater_check"] = None + expected_settings["updater_release_check"] = None expected_settings["updater_last_check"] = 0 assert updater.should_check_for_updates() is False assert updater.dangerzone.settings.get_updater_settings() == expected_settings @@ -142,14 +145,14 @@ def test_user_prompts( # Check disabling update checks. prompt_mock().launch.return_value = False # type: ignore [attr-defined] - expected_settings["updater_check"] = False + expected_settings["updater_release_check"] = False assert updater.should_check_for_updates() is False assert updater.dangerzone.settings.get_updater_settings() == expected_settings - # Reset the "updater_check" field and check enabling update checks. - updater.dangerzone.settings.set("updater_check", None) + # Reset the "updater_release_check" field and check enabling update checks. + updater.dangerzone.settings.set("updater_release_check", None) prompt_mock().launch.return_value = True # type: ignore [attr-defined] - expected_settings["updater_check"] = True + expected_settings["updater_release_check"] = True assert updater.should_check_for_updates() is True assert updater.dangerzone.settings.get_updater_settings() == expected_settings @@ -159,7 +162,7 @@ def test_user_prompts( # checks. prompt_mock().side_effect = RuntimeError("Should not be called") # type: ignore [attr-defined] for check in [True, False]: - updater.dangerzone.settings.set("updater_check", check) + updater.dangerzone.settings.set("updater_release_check", check) assert updater.should_check_for_updates() == check @@ -200,8 +203,10 @@ def test_update_checks( assert_report_equal(report, UpdateReport(error=error_msg)) # Test 4 - Check that cached version/changelog info do not trigger an update check. - updater.dangerzone.settings.set("updater_latest_version", "99.9.9") - updater.dangerzone.settings.set("updater_latest_changelog", "

changelog

") + updater.dangerzone.settings.set("updater_release_latest_version", "99.9.9") + updater.dangerzone.settings.set( + "updater_release_latest_changelog", "

changelog

" + ) report = updater.check_for_updates() assert_report_equal( @@ -211,7 +216,7 @@ def test_update_checks( def test_update_checks_cooldown(updater: UpdaterThread, mocker: MockerFixture) -> None: """Make sure Dangerzone only checks for updates every X hours""" - updater.dangerzone.settings.set("updater_check", True) + updater.dangerzone.settings.set("updater_release_check", True) updater.dangerzone.settings.set("updater_last_check", 0) # Mock some functions before the tests start @@ -242,8 +247,8 @@ def test_update_checks_cooldown(updater: UpdaterThread, mocker: MockerFixture) - curtime += 1 timestamp_mock.return_value = curtime requests_mock.side_effect = Exception("failed") # type: ignore [attr-defined] - updater.dangerzone.settings.set("updater_latest_version", get_version()) - updater.dangerzone.settings.set("updater_latest_changelog", None) + updater.dangerzone.settings.set("updater_release_latest_version", get_version()) + updater.dangerzone.settings.set("updater_release_latest_changelog", None) report = updater.check_for_updates() assert cooldown_spy.spy_return is True @@ -264,8 +269,8 @@ def test_update_checks_cooldown(updater: UpdaterThread, mocker: MockerFixture) - # Test 4: Make Dangerzone check for updates again, but this time, it should # encounter an error while doing so. In that case, the last check timestamp # should be bumped, so that subsequent checks don't take place. - updater.dangerzone.settings.set("updater_latest_version", get_version()) - updater.dangerzone.settings.set("updater_latest_changelog", None) + updater.dangerzone.settings.set("updater_release_latest_version", get_version()) + updater.dangerzone.settings.set("updater_release_latest_changelog", None) curtime += updater_module.UPDATE_CHECK_COOLDOWN_SECS timestamp_mock.return_value = curtime