From 7523bffbd8ba7312d326f1c450d5be8c9e8ac05b Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 7 May 2026 00:19:01 +0900 Subject: [PATCH] change: simplify routes between query API --- dzgui/api/servers.py | 156 ++++++++++++++------------------ dzgui/controllers/mc.py | 4 +- dzgui/managers/connection.py | 18 ++-- dzgui/model/servers.py | 13 ++- dzgui/util/strings.py | 2 +- dzgui/views/pages/preconnect.py | 9 +- dzgui/views/trees/tree_base.py | 2 +- 7 files changed, 94 insertions(+), 110 deletions(-) diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index 552a8ef..8da3a24 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -20,6 +20,7 @@ import a2s import dayzquery if TYPE_CHECKING: + from a2s import SourceInfo from dayzquery import DayzMod logger = logging.getLogger(APP_NAME) @@ -40,18 +41,54 @@ params = [ ] -#class BmAPIError(Exception): -# pass -# -# -#class BmIdError(Exception): -# pass - - class InvalidIpError(Exception): pass +@dataclass(slots=True, frozen=True) +class Res: + status: int + parsed: bool + json: Union[dict[str, Any], None] + + +@dataclass(slots=True, frozen=True) +class Details: + data: Union[list, None] + description: str + success: bool + + +@dataclass(slots=True) +class Record: + """ + The gameport field is manipulated by the RowType.CONN_BY_IP method + """ + + ip: str + gameport: int + qport: int + + +class A2SInfo: + def __init__(self, record: Record, info: a2s.SourceInfo | None) -> None: + self.record = record + self.info = info + + def get_record(self) -> Record: + return self.record + + def get_info(self) -> a2s.SourceInfo | None: + return self.info + + def as_row(self) -> dict[str, Any] | None: + if self.info is None: + return None + ip = self.record.ip + qport = self.record.qport + return source_info_to_dict(ip, qport, self.info) + + def get_netmask() -> str: hostname = os.uname()[1] i = socket.gethostbyname(hostname) @@ -191,11 +228,8 @@ def parse_json(json: list) -> list: return rows - -def query_direct(ip: str, qport: int, timeout: float = 3.0) -> dict | None: +def source_info_to_dict(ip: str, qport: int, info: "SourceInfo") -> dict[str, Any]: try: - info = a2s.info((ip, qport), timeout) - name = info.server_name mapname = info.map_name address = ip + ":" + str(qport) @@ -221,61 +255,18 @@ def query_direct(ip: str, qport: int, timeout: float = 3.0) -> dict | None: res["ping"] = ping return res except Exception as e: + # TODO: generalized function logger.critical(f"{type(e).__name__}: {e} ({ip}:{qport})") return None -@dataclass(slots=True, frozen=True) -class Res: - status: int - parsed: bool - json: Union[dict[str, Any], None] - - -@dataclass(slots=True, frozen=True) -class Details: - data: Union[list, None] - description: str - success: bool - - -#@dataclass(slots=True, frozen=True) -#class Prereqs: -# password: bool -# gameport: int -# appid: Union[int, None] -# version: Union[str, None] - - -@dataclass(slots=True) -class Record: - """ - The gameport field is manipulated by the RowType.CONN_BY_IP method - """ - - ip: str - gameport: int - qport: int - -@dataclass(frozen=True, slots=True) -class PreReqs: - record: Record - source: a2s.SourceInfo - - - -#def get_prereqs(ip: str, qport: int) -> Prereqs: -# try: -# info = a2s.info((ip, qport)) -# except TimeoutError: -# return Prereqs(False, 0, None, None) -# -# gameport = getattr(info, "port", 0) -# is_password = getattr(info, "password_protected", False) -# appid = getattr(info, "game_id", None) -# version = getattr(info, "version", None) -# -# return Prereqs(is_password, gameport, appid, version) +def query_direct(ip: str, qport: int, timeout: float = 3.0) -> dict[str, Any] | None: + try: + info = a2s.info((ip, qport), timeout) + return source_info_to_dict(ip, qport, info) + except Exception as e: + logger.critical(e) + return None def get_details(record: Record) -> Details: @@ -443,18 +434,6 @@ def get_rules(record: Record) -> list["DayzMod"]: return [mod for mod in mods] -def query_by_id(server_id: int, key: str, full: bool = False) -> dict[Any] | None: - """ - Used with numeric Battlemetrics IDs - """ - try: - resolved = map_id_to_record(key, server_id) - return query_direct(resolved.ip, resolved.qport, full) - except Exception as e: - logger.critical(e) - return None - - def query_playercount(record: Record) -> tuple[int, int] | None: try: res = query_direct(record.ip, record.qport) @@ -470,24 +449,25 @@ def query_playercount(record: Record) -> tuple[int, int] | None: return None -def query_by_ip(addr: str, full: bool = False) -> dict[str, Any] | PreReqs | None: +def query_by_ip(addr: str) -> A2SInfo: record = short_ip_to_record(addr) - return query_by_record(record, full) + return query_by_record(record) -def query_by_record(record: Record, full: bool = False) -> dict[str, Any] | PreReqs | None: - if full: - try: - info = a2s.info((record.ip, record.qport), 3.0) - return PreReqs(record, info) - except Exception as e: - logger.critical(e) - return None +def query_by_id(server_id: int, key: str) -> dict[Any] | None: + """ + Used with numeric Battlemetrics IDs + """ + record = map_id_to_record(key, server_id) + return query_by_record(record) + +def query_by_record(record: Record) -> A2SInfo: try: - return query_direct(record.ip, record.qport) + info = a2s.info((record.ip, record.qport), 3.0) + return A2SInfo(record, info) except Exception as e: - logger.critical(e) - return None + logger.warning(e) + return A2SInfo(record, None) def short_ip_to_record(addr: str) -> Optional[Record]: diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index eabdeff..45c5871 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -37,7 +37,7 @@ logger = logging.getLogger(APP_NAME) if TYPE_CHECKING: from dayzquery import DayzMod - from dzgui.api.servers import PreReqs, Record + from dzgui.api.servers import A2SInfo, Record from dzgui.const.enum import ServerTab from dzgui.managers.filter import FilterManager from dzgui.util.dist import Haversine @@ -458,7 +458,7 @@ class Controller(GObject.GObject): def set_exit_event(self) -> None: self.exit_event.set() - def open_connection_assistant(self, res: "PreReqs", mods: list["DayzMod"]) -> None: + def open_connection_assistant(self, res: "A2SInfo", mods: list["DayzMod"]) -> None: self.open_page(NotebookPage.CONNECTION) self.mediator.preconnect.populate(res, mods) diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index 85df3e8..fa00f8e 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -2,7 +2,7 @@ import logging import shutil from pathlib import Path -from typing import Union, TYPE_CHECKING +from typing import TYPE_CHECKING import dzgui.api.pefile as PeFile import dzgui.api.servers as Servers @@ -22,7 +22,7 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 if TYPE_CHECKING: - from dzgui.api.servers import PreReqs + from dzgui.api.servers import A2SInfo from dzgui.controllers.mc import Controller logger = logging.getLogger(APP_NAME) @@ -36,26 +36,26 @@ class ConnectionManager: @call_on_thread(dialog.querying) def connect_by_id(self, _id: int, key: str) -> None: - res = Servers.query_by_id(_id, key, full=True) + res = Servers.query_by_id(_id, key) self._prepare_connection(res) @call_on_thread(dialog.querying) def connect_by_ip(self, addr: str) -> None: - res = Servers.query_by_ip(addr, full=True) + res = Servers.query_by_ip(addr) self._prepare_connection(res) @call_on_thread(dialog.querying) def connect_by_record(self, record: Servers.Record) -> None: - res = Servers.query_by_record(record, full=True) + res = Servers.query_by_record(record) self._prepare_connection(res) - def _prepare_connection(self, res: Union["PreReqs", None]) -> None: + def _prepare_connection(self, res: "A2SInfo") -> None: failure_func = StoredFunc(self._server_timeout) - if res is None: + if res.get_info() is None: self.thread_man.set_cleanup_func(failure_func, destroy_first=True) return - record = res.record + record = res.get_record() try: remote_mods = self._query_modlist(record) remote_mod_ids = [mod[1] for mod in remote_mods] @@ -79,7 +79,7 @@ class ConnectionManager: # TODO: when downloading mods, create symlinks if missing # TODO: get missing mod sizes, warn if not enough space - info = res.source + info = res.get_info() try: dayz_path = PeFile.get_pefile_path(steam_path, info.game_id) # TODO: handle missing path; do not calculate size if appid is missing diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index a88502a..4341936 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -23,6 +23,7 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 if TYPE_CHECKING: + from dzgui.api.servers import A2SInfo from dzgui.controllers.mc import Controller from dzgui.model.proxy_model import ProxyModelManager from dzgui.views.trees.tree_servers import ServerTreeView @@ -39,6 +40,7 @@ class NewPlayerCount: queue: int + class ServerModelManager: def __init__(self, controller: "Controller", tv: "ServerTreeView") -> None: @@ -256,23 +258,24 @@ class ServerModelManager: proxy_man.update_playercount(self.playercount) def _parse_single_record( - self, response: dict[Any, Any] | None, delete: bool = False + self, response: "A2SInfo", delete: bool = False ) -> None: self.preserve_on_fail = True - if response is None: + row = response.as_row() + if row is None: self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure)) return # NOTE: expected to only contain one item - records = Servers.parse_json([response]) + records = Servers.parse_json([row]) server = records[0] if server is None: return proxy_man = self._get_proxy_man() config_man = self.controller.get_config_man() - fqip = Servers.response_to_fqip(response) - record = Servers.response_to_record(response) + fqip = Servers.response_to_fqip(row) + record = response.get_record() #Servers.response_to_record(row) # TODO: less convoluted if delete: diff --git a/dzgui/util/strings.py b/dzgui/util/strings.py index 82853eb..95ee9c3 100644 --- a/dzgui/util/strings.py +++ b/dzgui/util/strings.py @@ -57,7 +57,7 @@ copy_ip = "Copy IP to clipboard" copy_log = "Copy record(s) to clipboard" add = "Add to Saved Servers" add_fav = "Set as favorite" -remove = "Remove from my servers" +remove = "Remove from Saved Servers" remove_history = "Remove from history" connect = "Connect" diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index 9cae71d..b9a0870 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -22,7 +22,7 @@ from gi.repository import Gdk, Gtk # type: ignore # noqa E402 if TYPE_CHECKING: - from dzgui.api.servers import PreReqs + from dzgui.api.servers import A2SInfo from dzgui.controllers.mc import Controller @@ -142,7 +142,7 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): self.add(self.box) - # self.connect("key-press-event", self._on_keypress) + self.connect("key-press-event", self._on_keypress) self.connect("map", self._on_map) def _on_map(self, widget: Self) -> None: @@ -162,13 +162,14 @@ class PreConnectionAssistant(Gtk.ScrolledWindow): page = self.controller.get_prior_page() self.controller.open_page(page) - def populate(self, res: "PreReqs", mods: list["DayzMod"]) -> None: + def populate(self, res: "A2SInfo", mods: list["DayzMod"]) -> None: self.tree.populate(mods) total = len(mods) self._set_warnings() - name = res.source.server_name + info = res.get_info() + name = info.server_name self.title.set_text(name) if total < 1: self.tree_frame.set_visible(False) diff --git a/dzgui/views/trees/tree_base.py b/dzgui/views/trees/tree_base.py index 7f00d99..9603137 100644 --- a/dzgui/views/trees/tree_base.py +++ b/dzgui/views/trees/tree_base.py @@ -144,7 +144,7 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore path = pathlist[0] tree_iter = model.get_iter(path) value = model.get_value(tree_iter, index) - return str(value) + return value def get_name(self) -> str: name = self.get_value_at_index(0)