From b42aea1e0211ec8df7fe241e2e8a3f496ac35cc2 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 6 May 2026 02:09:40 +0900 Subject: [PATCH] feat: preconnect dialog (WIP) --- dzgui/api/mods.py | 2 +- dzgui/api/steam.py | 56 ++++++++++++++++++++++++++- dzgui/const/endpoints.py | 1 + dzgui/managers/connection.py | 51 +++++++++++++++++------- dzgui/views/base.py | 6 +-- dzgui/views/components/entry.py | 1 - dzgui/views/dialogs/generic.py | 7 ---- dzgui/views/mixins/help_menu_mixin.py | 2 - dzgui/views/pages/preconnect.py | 14 +------ 9 files changed, 99 insertions(+), 41 deletions(-) diff --git a/dzgui/api/mods.py b/dzgui/api/mods.py index 29c154d..63015c0 100644 --- a/dzgui/api/mods.py +++ b/dzgui/api/mods.py @@ -81,7 +81,7 @@ def get_mod_size(path: Path) -> float: s = 0 for f in path.rglob("*"): s += f.stat().st_size - size = round(s / (1024 * 1024), 3) + size = round(s / (1024**2), 3) return size diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index bbab06f..7fb7c72 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -1,11 +1,13 @@ import json import logging +import requests import subprocess from shlex import shlex from pathlib import Path from dzgui.const.constants import APP_NAME +from dzgui.const.endpoints import STEAM_PUBLISHED_FILES from dzgui.util.bash import concat_bash_args @@ -49,15 +51,65 @@ def query_defunct() -> None: # query_defunct "3576065083" -def concat_mods(mods: list[str]) -> str: +def concat_mods(mods: list[int]) -> str: for mod in mods: mods[mod] = f"@{mod}" return ";".join(mods) +def get_local_signatures(version_file: Path) -> dict[str, int]: + hashes: dict[str, int] = {} + lines = version_file.read_text().splitlines() + for line in lines: + line = line.split(",") + _id = line[0] + _hash = line[1] + hashes[_id] = _hash + return hashes + + +def get_needs_update( + version_file: Path, remote_hashes: list[tuple[str, int, str]] +) -> list[tuple[str, int, str]]: + local_hashes = get_local_signatures(version_file) + needs_update: list[tuple[str, str]] = [] + for _id, _hash, size in remote_hashes: + if _id not in local_hashes: + needs_update.append((_id, _hash, size)) + elif _hash != local_hashes[_id]: + needs_update.append((_id, _hash, size)) + else: + continue + return needs_update + + +def get_remote_signatures(mods: list[str]) -> list[tuple[str, int]]: + payload: dict[str, str] = {} + payload["itemcount"] = len(mods) + for i, mod in enumerate(mods): + payload[f"publishedfileids[{i}]"] = mod + try: + r = requests.post(STEAM_PUBLISHED_FILES, payload) + except Exception as e: + logger.critical(e) + return [] + if r.status_code != 200: + return [] + + hashes: list[tuple[str, int, str]] = [] + j = r.json() + rows = j["response"]["publishedfiledetails"] + for row in rows: + _id = row["publishedfileid"] + time = row["time_updated"] + size = row["file_size"] + hashes.append((_id, time, size)) + return hashes + + # TEST: set config to name=user, use official server and no mods, # ensure that formatted string is identical to fixture -def connect(addr: str, appid: int, name: str, mods: list) -> None: +def connect(addr: str, appid: int, name: str, mods: list[int]) -> None: # TODO: get name from configs # TODO: concat_mods(mods): # @;@; diff --git a/dzgui/const/endpoints.py b/dzgui/const/endpoints.py index b27e921..cb1ca1c 100644 --- a/dzgui/const/endpoints.py +++ b/dzgui/const/endpoints.py @@ -1,4 +1,5 @@ # internal +STEAM_PUBLISHED_FILES = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json" STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1/?" BM_SERVERS = "https://api.battlemetrics.com/servers?" GITHUB = "https://github.com/aclist" diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index 0715568..f58d815 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -1,3 +1,4 @@ +import logging import shutil from pathlib import Path @@ -5,8 +6,10 @@ from typing import Union, TYPE_CHECKING import dzgui.api.pefile as PeFile import dzgui.api.servers as Servers +from dzgui.api.steam import get_remote_signatures, get_needs_update from dzgui.api.mods import get_local_mod_ids +from dzgui.const.constants import APP_NAME from dzgui.const.enum import Preferences from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.util.strings import dialog, server_timeout, checkmark @@ -22,6 +25,8 @@ if TYPE_CHECKING: from dzgui.api.servers import PreReqs from dzgui.controllers.mc import Controller +logger = logging.getLogger(APP_NAME) + class ConnectionManager: def __init__(self, controller: "Controller") -> None: @@ -46,35 +51,55 @@ class ConnectionManager: def _prepare_connection(self, res: Union["PreReqs", None]) -> None: failure_func = StoredFunc(self._server_timeout) - if res is None: self.thread_man.set_cleanup_func(failure_func, destroy_first=True) return record = res.record - info = res.source - try: - mods = self._query_modlist(record) - except Exception: + remote_mods = self._query_modlist(record) + remote_mod_ids = [mod[1] for mod in remote_mods] + except Exception as e: + print(e) self.thread_man.set_cleanup_func(failure_func, destroy_first=True) return - # TODO: get missing mod diff - # TODO: get missing mod sizes + steam_path = Path(self.controller.query_config(Preferences.DEFAULT)) + hashes = get_remote_signatures(remote_mod_ids) + version_file = self.controller.get_prefs().paths.version + needs_update = get_needs_update(version_file, hashes) + + # TODO: populate version file at boot if nonexistent (after symlinking) + # missing mods should be the totality of all mods with no signature + # missing = get_missing_mods(local_mod_ids, remote_mod_ids) + # print(missing) + + # TODO: when updating, create symlinks of everything + # contains id and signature + + # TODO: get missing mod sizes, warn if not enough space + info = res.source try: - path = Path(self.controller.query_config(Preferences.DEFAULT)) - dayz_path = PeFile.get_pefile_path(path, info.game_id) + dayz_path = PeFile.get_pefile_path(steam_path, info.game_id) total, used, free = shutil.disk_usage(dayz_path) - free_mib = free / (1024**2) - print(free_mib) + if len(needs_update) > 0: + # TODO: generic mib function + required_size = sum(int(row[2]) for row in needs_update) + required_mib = round(required_size / (1024**2), 3) + free_mib = round(free / (1024**2), 3) + print(required_mib) + print(free_mib) except Exception: - # TODO: if this fails, need to show missing build warning + # TODO: if this fails, need to show missing build warning, not failure func + # build up list of warnings/errors # logger.warning(e) self.thread_man.set_cleanup_func(failure_func, destroy_first=True) - func = StoredFunc(self.controller.open_connection_assistant, res, mods) + # TODO: number separator func + # TODO: pack a final PreReq struct with pre-process values + + func = StoredFunc(self.controller.open_connection_assistant, res, remote_mods) self.thread_man.set_cleanup_func(func, destroy_first=True) @call_on_thread(dialog.querying) diff --git a/dzgui/views/base.py b/dzgui/views/base.py index 899a715..35119c6 100644 --- a/dzgui/views/base.py +++ b/dzgui/views/base.py @@ -27,15 +27,15 @@ from dzgui.views.pages.preconnect import PreConnectionAssistant from dzgui.views.pages.servers import ServerNotebook from dzgui.views.pages.thanks import Thanks -if TYPE_CHECKING: - from dzgui.config.userprefs import UserPrefs - import gi gi.require_version("Gtk", "3.0") gi.require_version("GLibUnix", "2.0") from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore # noqa E402 +if TYPE_CHECKING: + from dzgui.config.userprefs import UserPrefs + logger = logging.getLogger(APP_NAME) # TODO: drop diff --git a/dzgui/views/components/entry.py b/dzgui/views/components/entry.py index 3f0e592..8dcd3f3 100644 --- a/dzgui/views/components/entry.py +++ b/dzgui/views/components/entry.py @@ -43,7 +43,6 @@ class ValidatedEntry(Gtk.Entry): placeholder_text: str = "", tooltip_text: str = "", ) -> None: - # TODO: tooltip text should not be hardcoded super().__init__( hexpand=True, placeholder_text=placeholder_text, tooltip_text=tooltip_text ) diff --git a/dzgui/views/dialogs/generic.py b/dzgui/views/dialogs/generic.py index 0fd2816..17029c1 100644 --- a/dzgui/views/dialogs/generic.py +++ b/dzgui/views/dialogs/generic.py @@ -12,13 +12,6 @@ from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402 if TYPE_CHECKING: from dzgui.controllers.mc import Controller -# TODO: reimplement as standalone dialogs -# NOTE: steam deck prints <2> if dialog title is same as window title -# case Popup.MODLIST: -# dialog_type = Gtk.MessageType.INFO -# button_type = Gtk.ButtonsType.OK -# header_text = strings.modlist - class GenericDialog(Gtk.MessageDialog): def __init__( diff --git a/dzgui/views/mixins/help_menu_mixin.py b/dzgui/views/mixins/help_menu_mixin.py index 85a5361..d287bb7 100644 --- a/dzgui/views/mixins/help_menu_mixin.py +++ b/dzgui/views/mixins/help_menu_mixin.py @@ -1,5 +1,3 @@ -from typing import Literal - import gi gi.require_version("Gtk", "3.0") diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index a0ad525..2ae6668 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -52,9 +52,6 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): spacing=10, ) - # self.rules: dict[Any] - # self.mods: list["DayzMod"] - self.controller.register_widget("preconnect", self) # TODO: strings @@ -154,24 +151,17 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): prefix = preconnect.total_mods self.mod_count.set_text(f"{prefix}{str(total)}") - # TODO: check which mods need updating - # steam_path = self.controller.get_config_man().lookup(Preferences.DEFAULT) - # dayz_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ) - # dayz_exp_version = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP) + # TODO: reset warning and error dialogs def download_mods(self) -> None: pass def connect_server(self) -> None: # TODO: add to history file and list store + # TODO: concat mods """ spawn dialog in thread watch for subprocess return to prior page when finished """ - self.back.emit("clicked") pass - - # TODO: icon for mod signature issue - # or "Update mods and connect" - # also handle servers with no mods; do not show tree