From e1f54bd75db495284a6f13850ac669a608691a84 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 18 Apr 2026 18:15:27 +0900 Subject: [PATCH] feat: refresh player count --- dzgui/api/servers.py | 15 ++++++- dzgui/controllers/mc.py | 9 +++++ dzgui/managers/config.py | 5 +++ dzgui/managers/contextmenu.py | 10 +++-- dzgui/model/proxy_model.py | 23 +++++++++++ dzgui/model/servers.py | 75 ++++++++++++++++++++++------------- 6 files changed, 105 insertions(+), 32 deletions(-) diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index 4886118..7aae579 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -453,11 +453,24 @@ def query_by_id(addr: str, key: str) -> Optional[dict]: return None +def query_playercount(record: Record) -> Optional[tuple[int]]: + try: + res = query_direct(record.ip, record.qport) + players = int(res["players"]) + r = res["gametype"].split("lqs") + try: + queue = int(r[1].split(",")[0]) + except IndexError: + queue = 0 + return (players, queue) + except Exception as e: + logger.critical(e) + return None + def query_by_ip(addr: str) -> Optional[dict]: record = short_ip_to_record(addr) return query_by_record(record) - def query_by_record(record: Record) -> Optional[dict]: try: return query_direct(record.ip, record.qport) diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index 165dbd7..73116f0 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -604,6 +604,10 @@ class Controller(GObject.GObject): saved_tree = self.get_servers().get_saved() ServerModelManager(self, saved_tree).add_by_record(record) + def remove_by_record(self, record: "Record") -> None: + saved_tree = self.get_servers().get_saved() + ServerModelManager(self, saved_tree).remove_by_record(record) + def connect_by_str(self, addr: str) -> None: if addr.isdigit(): config_man = self.get_config_man() @@ -652,3 +656,8 @@ class Controller(GObject.GObject): tv = self.get_active_treeview() record = tv.get_record_string() self.notes_man.delete_note(record) + + def refresh_players(self, record: "Record") -> None: + treeview = self.get_active_treeview() + model, treeiter = treeview.get_selection().get_selected() + ServerModelManager(self, treeview).update_playercount(treeiter, record) diff --git a/dzgui/managers/config.py b/dzgui/managers/config.py index 82cf8cd..679a39d 100644 --- a/dzgui/managers/config.py +++ b/dzgui/managers/config.py @@ -48,6 +48,11 @@ class ConfigManager: ips.append(record) self.update_config(Preferences.IP_LIST, ips) + def remove_saved_server(self, record: str) -> None: + ips = self.lookup(Preferences.IP_LIST) + ips.remove(record) + self.update_config(Preferences.IP_LIST, ips) + # TODO: drop/reroute def update_config(self, key: Preferences, value: str) -> None: self.write_config(key, value) diff --git a/dzgui/managers/contextmenu.py b/dzgui/managers/contextmenu.py index 1f698ba..3a654b9 100644 --- a/dzgui/managers/contextmenu.py +++ b/dzgui/managers/contextmenu.py @@ -66,7 +66,10 @@ class ContextMenuManager: case ContextMenu.REFRESH_PLAYERS: print("DEBUG: unimplemented") - # get record + record = self.treeview.get_record() + self.controller.refresh_players(record) + + # TODO: just updates proxy model, this info is ephemeral # call a2s on thread pass case ContextMenu.REMOVE_HISTORY: @@ -75,9 +78,8 @@ class ContextMenuManager: # see dq.py pass case ContextMenu.REMOVE_SERVER: - print("DEBUG: unimplemented") - # reverse of ADD_SERVER - pass + record = self.treeview.get_record() + self.controller.remove_by_record(record) case ContextMenu.SHOW_DETAILS: record = self.treeview.get_record() self.controller.get_details(record) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index cf4d092..d5c9ac7 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -10,7 +10,12 @@ from dzgui.util import strings if TYPE_CHECKING: from dzgui.managers.filter_man import FilterManager from dzgui.model.model_factory import FastInsertListStore + from dzgui.model.servers import NewPlayerCount +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository.Gtk import TreeIter class ProxyModelManager: """ @@ -35,9 +40,27 @@ class ProxyModelManager: self.filtered: list = None self.success = True + def has_control_model(self) -> bool: + pass + def append_row(self, row: list) -> None: self.proxy_model.append(row) + def append_row_to_control(self, row: list) -> None: + self.control_model.append(row) + self.filter(FilterMode.INITIAL, skip_cache=True) + + def remove_row_from_control(self, row: list) -> None: + for record in self.control_model: + if row[7] == record[7] and row[8] == record[8]: + self.control_model.remove(record) + self.filter(FilterMode.INITIAL, skip_cache=True) + + def update_playercount(self, playercount: "NewPlayerCount") -> None: + treeiter = playercount.treeiter + self.proxy_model[treeiter][4] = playercount.players + self.proxy_model[treeiter][6] = playercount.queue + def clear_proxy_model(self) -> None: self.proxy_model.clear() diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index fbaf3dd..5b13b86 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -1,7 +1,9 @@ import logging import threading + from concurrent.futures import as_completed from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass from typing import TYPE_CHECKING import dzgui.api.servers as Servers @@ -29,9 +31,15 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -# TODO: failure: spawns error dialog +# TODO: failure should spawn error dialog # TODO: non failure with empty model: updates statusbar with help text +@dataclass +class NewPlayerCount: + treeiter: Gtk.TreeIter + players: int + queue: int + class ServerModelManager: def __init__(self, controller: "Controller", tv: Gtk.TreeView) -> None: @@ -207,50 +215,65 @@ class ServerModelManager: # FIXME: use "adding server" string @call_on_thread(dialog.querying) def add_by_record(self, record: Servers.Record) -> None: + """ + Record as shown in server browser may resolve to a different IP + """ res = Servers.query_by_record(record) self._parse_single_record(res) + @call_on_thread(dialog.querying) + def remove_by_record(self, record: Servers.Record) -> None: + res = Servers.query_by_record(record) + self._parse_single_record(res, delete=True) + def add_by_str(self, addr: str) -> None: if addr.isdigit(): self.add_by_id(addr) else: self.add_by_ip(addr) - def _parse_single_record(self, response: dict) -> None: + @call_on_thread(dialog.querying) + def update_playercount(self, treeiter: Gtk.TreeIter, record: Servers.Record) -> None: + proxy_man = self._get_proxy_man() + res = Servers.query_playercount(record) + if res is None: + return + players, queue = res + + self.playercount = NewPlayerCount(treeiter, players, queue) + self.thread_man.set_cleanup_func(StoredFunc(self._push_playercount)) + + def _push_playercount(self) -> None: + proxy_man = self._get_proxy_man() + proxy_man.update_playercount(self.playercount) + + def _parse_single_record(self, response: dict, delete: bool = False) -> None: if response 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]) record = records[0] proxy_man = self._get_proxy_man() - raw_model = proxy_man.get_control() - fqip = Servers.response_to_fq_ip(response) config_man = self.controller.get_config_man() - config_man.add_saved_server(fqip) - # NOTE: abort early if Saved Servers tab was not loaded yet - if raw_model is None: - return + if delete: + # NOTE: abort early if Saved Servers tab was not loaded yet + config_man.remove_saved_server(fqip) + if proxy_man.has_control_model() is False: + return + proxy_man.remove_row_from_control(record) + else: + config_man.add_saved_server(fqip) + if proxy_man.has_control_model() is False: + return + proxy_man.append_row_to_control(record) - # NOTE: expected to only contain one item - raw_model.append(record) - - # TODO: if all filters are already applied, strange behavior may occur - # -> need to insert and reupdate tree per current filters - # for example, non-empty will only show up in empty because it is not cached - - # FIXME: new results are not being shown when tabbing over to Saved Servers - proxy_man.filter(FilterMode.INITIAL, skip_cache=True) - - filter_man = self.tv.get_filter_man() - old_maps = filter_man.get_unique_maps() - cur_map = record[1] - if cur_map not in old_maps: - old_maps.append(cur_map) - self._set_new_maps(old_maps) + control_model = proxy_man.get_control() + self._sort_unique_maps(control_model) self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_single_ip)) def _dump_history(self) -> None: @@ -295,8 +318,8 @@ class ServerModelManager: # TODO: animate saved servers tab if we are on other tab self.emitter.emit("servers_loaded", self.enum) - # TODO: consolidate methods and handle multi/single map addition filter_man = self.tv.get_filter_man() + # NOTE: maps are set outside of thread because it triggers map changed signals maps = self._get_new_maps() filter_man.set_unique_maps(maps) @@ -314,8 +337,6 @@ class ServerModelManager: self.tv.set_model(proxy) # TODO: make sure control model len is N + 1 - # inserting a row serializes file on disk, updates control model for that tab, and updates model - # NOTE: when inserting new rows, the entire control model is wiped and rebuilt, then proxy model is swapped in # TODO: signals or other approach to deferring map # model insertion after thread closes # cf. servers_loaded signal