feat(updater): Introduce new settings and rename old ones

This commit is contained in:
Alexis Métaireau 2024-06-12 10:26:54 +02:00
parent 3ba9181888
commit 4a12853f46
No known key found for this signature in database
GPG key ID: C65C7A89A8FFC56E
5 changed files with 79 additions and 48 deletions

View file

@ -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,

View file

@ -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

View file

@ -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:

View file

@ -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"] = "<p>changelog</p>"
expected_settings["updater_release_latest_version"] = "99.9.9"
expected_settings["updater_release_latest_changelog"] = "<p>changelog</p>"
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"
)

View file

@ -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", "<p>changelog</p>")
updater.dangerzone.settings.set("updater_release_latest_version", "99.9.9")
updater.dangerzone.settings.set(
"updater_release_latest_changelog", "<p>changelog</p>"
)
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