feat: preconnect dialog (WIP)

This commit is contained in:
aclist committed 2026-05-06 02:09:40 +09:00
1 parent 25640751a5
commit b42aea1e02
9 files changed
+99 -41

No files matched your search

+1 -1
View File
@@ -81,7 +81,7 @@ def get_mod_size(path: Path) -> float:
s = 0 s = 0
for f in path.rglob("*"): for f in path.rglob("*"):
s += f.stat().st_size s += f.stat().st_size
size = round(s / (1024 * 1024), 3) size = round(s / (1024**2), 3)
return size return size
+54 -2
View File
@@ -1,11 +1,13 @@
import json import json
import logging import logging
import requests
import subprocess import subprocess
from shlex import shlex from shlex import shlex
from pathlib import Path from pathlib import Path
from dzgui.const.constants import APP_NAME from dzgui.const.constants import APP_NAME
from dzgui.const.endpoints import STEAM_PUBLISHED_FILES
from dzgui.util.bash import concat_bash_args from dzgui.util.bash import concat_bash_args
@@ -49,15 +51,65 @@ def query_defunct() -> None:
# query_defunct "3576065083" # query_defunct "3576065083"
def concat_mods(mods: list[str]) -> str: def concat_mods(mods: list[int]) -> str:
for mod in mods: for mod in mods:
mods[mod] = f"@{mod}" mods[mod] = f"@{mod}"
return ";".join(mods) 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, # TEST: set config to name=user, use official server and no mods,
# ensure that formatted string is identical to fixture # 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: get name from configs
# TODO: concat_mods(mods): # TODO: concat_mods(mods):
# @<mod>;@<mod>; # @<mod>;@<mod>;
+1
View File
@@ -1,4 +1,5 @@
# internal # internal
STEAM_PUBLISHED_FILES = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json"
STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1/?" STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1/?"
BM_SERVERS = "https://api.battlemetrics.com/servers?" BM_SERVERS = "https://api.battlemetrics.com/servers?"
GITHUB = "https://github.com/aclist" GITHUB = "https://github.com/aclist"
+38 -13
View File
@@ -1,3 +1,4 @@
import logging
import shutil import shutil
from pathlib import Path from pathlib import Path
@@ -5,8 +6,10 @@ from typing import Union, TYPE_CHECKING
import dzgui.api.pefile as PeFile import dzgui.api.pefile as PeFile
import dzgui.api.servers as Servers 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.api.mods import get_local_mod_ids
from dzgui.const.constants import APP_NAME
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
from dzgui.util.strings import dialog, server_timeout, checkmark from dzgui.util.strings import dialog, server_timeout, checkmark
@@ -22,6 +25,8 @@ if TYPE_CHECKING:
from dzgui.api.servers import PreReqs from dzgui.api.servers import PreReqs
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
logger = logging.getLogger(APP_NAME)
class ConnectionManager: class ConnectionManager:
def __init__(self, controller: "Controller") -> None: def __init__(self, controller: "Controller") -> None:
@@ -46,35 +51,55 @@ class ConnectionManager:
def _prepare_connection(self, res: Union["PreReqs", None]) -> None: def _prepare_connection(self, res: Union["PreReqs", None]) -> None:
failure_func = StoredFunc(self._server_timeout) failure_func = StoredFunc(self._server_timeout)
if res is None: if res is None:
self.thread_man.set_cleanup_func(failure_func, destroy_first=True) self.thread_man.set_cleanup_func(failure_func, destroy_first=True)
return return
record = res.record record = res.record
info = res.source
try: try:
mods = self._query_modlist(record) remote_mods = self._query_modlist(record)
except Exception: 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) self.thread_man.set_cleanup_func(failure_func, destroy_first=True)
return return
# TODO: get missing mod diff steam_path = Path(self.controller.query_config(Preferences.DEFAULT))
# TODO: get missing mod sizes
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: try:
path = Path(self.controller.query_config(Preferences.DEFAULT)) dayz_path = PeFile.get_pefile_path(steam_path, info.game_id)
dayz_path = PeFile.get_pefile_path(path, info.game_id)
total, used, free = shutil.disk_usage(dayz_path) total, used, free = shutil.disk_usage(dayz_path)
free_mib = free / (1024**2) if len(needs_update) > 0:
print(free_mib) # 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: 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) # logger.warning(e)
self.thread_man.set_cleanup_func(failure_func, destroy_first=True) 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) self.thread_man.set_cleanup_func(func, destroy_first=True)
@call_on_thread(dialog.querying) @call_on_thread(dialog.querying)
+3 -3
View File
@@ -27,15 +27,15 @@ from dzgui.views.pages.preconnect import PreConnectionAssistant
from dzgui.views.pages.servers import ServerNotebook from dzgui.views.pages.servers import ServerNotebook
from dzgui.views.pages.thanks import Thanks from dzgui.views.pages.thanks import Thanks
if TYPE_CHECKING:
from dzgui.config.userprefs import UserPrefs
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
gi.require_version("GLibUnix", "2.0") gi.require_version("GLibUnix", "2.0")
from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore # noqa E402 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) logger = logging.getLogger(APP_NAME)
# TODO: drop # TODO: drop
-1
View File
@@ -43,7 +43,6 @@ class ValidatedEntry(Gtk.Entry):
placeholder_text: str = "", placeholder_text: str = "",
tooltip_text: str = "", tooltip_text: str = "",
) -> None: ) -> None:
# TODO: tooltip text should not be hardcoded
super().__init__( super().__init__(
hexpand=True, placeholder_text=placeholder_text, tooltip_text=tooltip_text hexpand=True, placeholder_text=placeholder_text, tooltip_text=tooltip_text
) )
-7
View File
@@ -12,13 +12,6 @@ from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.controllers.mc import Controller 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): class GenericDialog(Gtk.MessageDialog):
def __init__( def __init__(
-2
View File
@@ -1,5 +1,3 @@
from typing import Literal
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
+2 -12
View File
@@ -52,9 +52,6 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
spacing=10, spacing=10,
) )
# self.rules: dict[Any]
# self.mods: list["DayzMod"]
self.controller.register_widget("preconnect", self) self.controller.register_widget("preconnect", self)
# TODO: strings # TODO: strings
@@ -154,24 +151,17 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
prefix = preconnect.total_mods prefix = preconnect.total_mods
self.mod_count.set_text(f"{prefix}{str(total)}") self.mod_count.set_text(f"{prefix}{str(total)}")
# TODO: check which mods need updating # TODO: reset warning and error dialogs
# 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)
def download_mods(self) -> None: def download_mods(self) -> None:
pass pass
def connect_server(self) -> None: def connect_server(self) -> None:
# TODO: add to history file and list store # TODO: add to history file and list store
# TODO: concat mods
""" """
spawn dialog in thread spawn dialog in thread
watch for subprocess watch for subprocess
return to prior page when finished return to prior page when finished
""" """
self.back.emit("clicked")
pass pass
# TODO: icon for mod signature issue
# or "Update mods and connect"
# also handle servers with no mods; do not show tree