From e3c1ef59ee2d1dcdf180ed8d456c7f660c49ed21 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:42:06 +0900 Subject: [PATCH 1/5] feat: get last updated timestamp --- dzgui/api/acf.py | 23 +++++++++++++++++++++++ dzgui/api/steam.py | 11 +++++++++++ dzgui/lib/dayzquery | 2 +- dzgui/views/pages/options.py | 15 +++++++++++++-- tests/test_app_download_settings.py | 10 +++++++++- 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/dzgui/api/acf.py b/dzgui/api/acf.py index 9d34909..284f710 100644 --- a/dzgui/api/acf.py +++ b/dzgui/api/acf.py @@ -2,6 +2,7 @@ import logging import re from collections.abc import Iterator +from datetime import date, datetime from pathlib import Path from typing import Any from warnings import deprecated @@ -69,6 +70,28 @@ class ACF: except StopIteration: return acf + def search_key(self, key: str) -> str: + return self.dict["AppState"][key] + + def date_to_str(self, epoch: int) -> str | None: + if epoch == 0: + return None + return datetime.fromtimestamp(epoch).isoformat(" ") + + def get_date_by_key(self, key: str) -> str | None: + try: + stamp = int(self.search_key(key)) + return self.date_to_str(stamp) + except Exception as e: + logger.critical(e) + return None + + def get_last_updated(self) -> str | None: + return self.get_date_by_key("LastUpdated") + + def get_last_played(self) -> str: + return self.get_date_by_key("LastPlayed") + def get_allows_downloads(self) -> int | None: try: # TODO: consider coercing strs to ints on initial import diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index be691b4..a7f8732 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -389,6 +389,17 @@ def get_running_app() -> int | None: return None +def get_last_updated(path: Path, appid: int) -> str: + root_path = get_app_path(path, appid) + acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf") + return ACF(acf).get_last_updated() + +def get_last_played(path: Path, appid: int) -> str: + root_path = get_app_path(path, appid) + acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf") + # TODO: make tests for this, use fixtures + return ACF(acf).get_last_played() + def get_app_allows_downloads(path: Path, appid: int) -> bool: try: root_path = get_app_path(path, appid) diff --git a/dzgui/lib/dayzquery b/dzgui/lib/dayzquery index 07483b8..fe413df 160000 --- a/dzgui/lib/dayzquery +++ b/dzgui/lib/dayzquery @@ -1 +1 @@ -Subproject commit 07483b88ed096327ebca752f5e177011b604d7fe +Subproject commit fe413df8e8f35cfb9302a657b5ec0b6ede0d4c2d diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index ebce129..eab871e 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import Callable, TYPE_CHECKING from dzgui.api import pefile as PeFile +from dzgui.api.steam import get_last_played, get_last_updated from dzgui.config import query from dzgui.const.constants import ( @@ -340,8 +341,10 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore [LeftLabel(options.flatpak_label), self.flatpak_toggle], ) - self.dayz_version_label = Gtk.Label(label=strings.null) - self.dayz_exp_version_label = Gtk.Label(label=strings.null) + self.dayz_version_label = Gtk.Label(label=strings.null, halign=Gtk.Align.START) + self.dayz_exp_version_label = Gtk.Label( + label=strings.null, halign=Gtk.Align.START + ) version_rows = [ [LeftLabel(APPNAME_DAYZ), self.dayz_version_label], @@ -467,10 +470,18 @@ class Options(ScrollableMixin, Gtk.ScrolledWindow): # type: ignore dayz_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ) if dayz_version is None: dayz_version = strings.null + else: + last_updated = get_last_updated(steam_path, APPID_DAYZ) + if last_updated is not None: + dayz_version += f" (updated at {last_updated})" dayz_exp_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP) if dayz_exp_version is None: dayz_exp_version = strings.null + else: + last_updated = get_last_updated(steam_path, APPID_DAYZ_EXP) + if last_updated is not None: + dayz_exp_version += f" (updated at {last_updated})" self.dayz_version_label.set_text(dayz_version) self.dayz_exp_version_label.set_text(dayz_exp_version) diff --git a/tests/test_app_download_settings.py b/tests/test_app_download_settings.py index 957fd2a..ad2639b 100644 --- a/tests/test_app_download_settings.py +++ b/tests/test_app_download_settings.py @@ -3,7 +3,7 @@ import pytest from _pytest.monkeypatch import MonkeyPatch from pathlib import Path -from dzgui.api.steam import get_app_allows_downloads +from dzgui.api.steam import get_app_allows_downloads, get_last_updated from tests.fixtures import fixture_path pytestmark = pytest.mark.apitest @@ -43,6 +43,14 @@ def patch_api() -> None: mp.undo() +def test_no_last_update(steam_path) -> None: + assert get_last_updated(steam_path, 555) is None + + +def test_last_update(steam_path) -> None: + assert get_last_updated(steam_path, 444) == "2026-10-22 00:09:33" + + def test_app_allows_downloads(steam_path) -> None: assert get_app_allows_downloads(steam_path, 111) is True From 0cf6439538917a96da967363ba86f300d822e7a1 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:43:12 +0900 Subject: [PATCH 2/5] chore: remove unused imports --- dzgui/api/acf.py | 2 +- dzgui/views/pages/options.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dzgui/api/acf.py b/dzgui/api/acf.py index 284f710..24816a0 100644 --- a/dzgui/api/acf.py +++ b/dzgui/api/acf.py @@ -2,7 +2,7 @@ import logging import re from collections.abc import Iterator -from datetime import date, datetime +from datetime import datetime from pathlib import Path from typing import Any from warnings import deprecated diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index eab871e..b5633ca 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Callable, TYPE_CHECKING from dzgui.api import pefile as PeFile -from dzgui.api.steam import get_last_played, get_last_updated +from dzgui.api.steam import get_last_updated from dzgui.config import query from dzgui.const.constants import ( From 0ec1c157f4b5adc48f9c8474165ec63cf81e8dc2 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sun, 1 Nov 2026 23:45:01 +0900 Subject: [PATCH 3/5] chore: clear typehinting errors --- dzgui/api/acf.py | 4 ++-- dzgui/api/steam.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dzgui/api/acf.py b/dzgui/api/acf.py index 24816a0..ab35b3b 100644 --- a/dzgui/api/acf.py +++ b/dzgui/api/acf.py @@ -71,7 +71,7 @@ class ACF: return acf def search_key(self, key: str) -> str: - return self.dict["AppState"][key] + return str(self.dict["AppState"][key]) def date_to_str(self, epoch: int) -> str | None: if epoch == 0: @@ -89,7 +89,7 @@ class ACF: def get_last_updated(self) -> str | None: return self.get_date_by_key("LastUpdated") - def get_last_played(self) -> str: + def get_last_played(self) -> str | None: return self.get_date_by_key("LastPlayed") def get_allows_downloads(self) -> int | None: diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index a7f8732..35061c6 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -389,17 +389,19 @@ def get_running_app() -> int | None: return None -def get_last_updated(path: Path, appid: int) -> str: +def get_last_updated(path: Path, appid: int) -> str | None: root_path = get_app_path(path, appid) acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf") return ACF(acf).get_last_updated() -def get_last_played(path: Path, appid: int) -> str: + +def get_last_played(path: Path, appid: int) -> str | None: root_path = get_app_path(path, appid) acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf") # TODO: make tests for this, use fixtures return ACF(acf).get_last_played() + def get_app_allows_downloads(path: Path, appid: int) -> bool: try: root_path = get_app_path(path, appid) From 90ef6cbdbb7173de8451b02dfbe5c9cb7abe218b Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 2 Nov 2026 12:17:04 +0900 Subject: [PATCH 4/5] chore: add tests --- .../api/steamapps/appmanifest_444.acf | 53 +++++++++++++++++++ .../api/steamapps/appmanifest_555.acf | 53 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tests/fixtures/api/steamapps/appmanifest_444.acf create mode 100644 tests/fixtures/api/steamapps/appmanifest_555.acf diff --git a/tests/fixtures/api/steamapps/appmanifest_444.acf b/tests/fixtures/api/steamapps/appmanifest_444.acf new file mode 100644 index 0000000..d13db8f --- /dev/null +++ b/tests/fixtures/api/steamapps/appmanifest_444.acf @@ -0,0 +1,53 @@ +"AppState" +{ + "appid" "444" + "Universe" "1" + "name" "TEST APP" + "StateFlags" "4" + "installdir" "TESTAPP" + "LastUpdated" "1792595373" + "LastPlayed" "0" + "SizeOnDisk" "0" + "StagingSize" "0" + "buildid" "0" + "LastOwner" "0" + "DownloadType" "2" + "UpdateResult" "0" + "BytesToDownload" "0" + "BytesDownloaded" "0" + "BytesToStage" "0" + "BytesStaged" "0" + "TargetBuildID" "0" + "AutoUpdateBehavior" "0" + "AllowOtherDownloadsWhileRunning" "0" + "ScheduledAutoUpdate" "0" + "FullValidateAfterNextUpdate" "1" + "InstalledDepots" + { + "3330" + { + "manifest" "0" + "size" "0" + } + } + "SharedDepots" + { + "228983" "228980" + "228985" "228980" + "228988" "228980" + "228990" "228980" + "229004" "228980" + } + "UserConfig" + { + "language" "english" + "platform_override_dest" "linux" + "platform_override_source" "windows" + } + "MountedConfig" + { + "language" "english" + "platform_override_dest" "linux" + "platform_override_source" "windows" + } +} diff --git a/tests/fixtures/api/steamapps/appmanifest_555.acf b/tests/fixtures/api/steamapps/appmanifest_555.acf new file mode 100644 index 0000000..5cbcda9 --- /dev/null +++ b/tests/fixtures/api/steamapps/appmanifest_555.acf @@ -0,0 +1,53 @@ +"AppState" +{ + "appid" "555" + "Universe" "1" + "name" "TEST APP" + "StateFlags" "4" + "installdir" "TESTAPP" + "LastUpdated" "0" + "LastPlayed" "0" + "SizeOnDisk" "0" + "StagingSize" "0" + "buildid" "0" + "LastOwner" "0" + "DownloadType" "2" + "UpdateResult" "0" + "BytesToDownload" "0" + "BytesDownloaded" "0" + "BytesToStage" "0" + "BytesStaged" "0" + "TargetBuildID" "0" + "AutoUpdateBehavior" "0" + "AllowOtherDownloadsWhileRunning" "0" + "ScheduledAutoUpdate" "0" + "FullValidateAfterNextUpdate" "1" + "InstalledDepots" + { + "3330" + { + "manifest" "0" + "size" "0" + } + } + "SharedDepots" + { + "228983" "228980" + "228985" "228980" + "228988" "228980" + "228990" "228980" + "229004" "228980" + } + "UserConfig" + { + "language" "english" + "platform_override_dest" "linux" + "platform_override_source" "windows" + } + "MountedConfig" + { + "language" "english" + "platform_override_dest" "linux" + "platform_override_source" "windows" + } +} From e7cef215c690e60993126b56815c7c35d9750326 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Mon, 2 Nov 2026 12:19:10 +0900 Subject: [PATCH 5/5] drop: resolved comment --- dzgui/api/steam.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index 35061c6..ab4649c 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -398,7 +398,6 @@ def get_last_updated(path: Path, appid: int) -> str | None: def get_last_played(path: Path, appid: int) -> str | None: root_path = get_app_path(path, appid) acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf") - # TODO: make tests for this, use fixtures return ACF(acf).get_last_played()