From c66511b0050ee490bd8b28c6581329bbc8c42567 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:41:40 +0900 Subject: [PATCH 01/10] change: marshal server row data --- dzgui/api/servers.py | 7 +- dzgui/model/proxy_model.py | 240 ++++++++++++++++++++---------- dzgui/model/servers.py | 14 +- dzgui/views/trees/tree_servers.py | 6 +- 4 files changed, 180 insertions(+), 87 deletions(-) diff --git a/dzgui/api/servers.py b/dzgui/api/servers.py index 8c331e4..ab48474 100644 --- a/dzgui/api/servers.py +++ b/dzgui/api/servers.py @@ -13,6 +13,7 @@ from typing import Any, Optional, TYPE_CHECKING, Union from dzgui.const.constants import APP_NAME, REQUEST_TIMEOUT from dzgui.const.endpoints import STEAM_SERVERS +from dzgui.model.proxy_model import ServerRow from dzgui.util.format import format_exception from dzgui.util import strings @@ -139,7 +140,9 @@ def sanitize(name: str) -> str: return name -def parse_json(json: list) -> list: +def parse_json( + json: list[dict[str, Any]], +) -> list[ServerRow]: """ Server metadata is underspecified and server operators tend to insert random garbage in the headers. In case @@ -224,7 +227,7 @@ def parse_json(json: list) -> list: mapname = row["map"].lower() players = row["players"] max_players = row["max_players"] - raw = ( + raw = ServerRow( name, mapname, view, diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index c892110..fb67861 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -1,6 +1,8 @@ import re -from typing import Any, TYPE_CHECKING, Union +from dataclasses import dataclass, fields +from operator import attrgetter +from typing import TYPE_CHECKING, Union from dzgui.const.enum import FilterMode from dzgui.model.model_factory import ModelFactory @@ -12,8 +14,118 @@ if TYPE_CHECKING: from dzgui.api.servers import Record from dzgui.model.servers import NewPlayerCount -DAY_REG = r"([0][6-9]|[1][0-7])" -NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])" + +@dataclass(slots=True, frozen=True) +class ServerRow: + name: str + mapname: str + view: str + time: str + players: int + max_players: int + queue: int + ip: str + qport: int + ping: str + provider: str + modded: bool + + # NOTE: construct getter once for fast lookup in lieu of field introspection + _to_tuple = attrgetter( + "name", + "mapname", + "view", + "time", + "players", + "max_players", + "queue", + "ip", + "qport", + "ping", + "provider", + "modded", + ) + + # TODO: tests for marshalled row insertion + @classmethod + def marshall_rows( + cls, rows: list["ServerRow"] + ) -> list[tuple[str, str, str, str, int, int, int, str, int, str, str, bool]]: + """Marshals data before passing into FastInsertListStore's extend() method""" + getter = cls._to_tuple + return [getter(row) for row in rows] + + +class FiltrationManager: + def __init__(self) -> None: + self.DAY_REG = r"([0][6-9]|[1][0-7])" + self.NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])" + + self.filter_map = { + strings.filter_3pp: self.filter_3pp, + strings.filter_1pp: self.filter_1pp, + strings.filter_official: self.filter_official, + strings.filter_unofficial: self.filter_unofficial, + strings.filter_empty: self.filter_empty, + strings.filter_full: self.filter_full, + strings.filter_duplicate: self.filter_duplicate, + strings.filter_day: self.filter_day, + strings.filter_night: self.filter_night, + strings.filter_nonascii: self.filter_nonascii, + strings.filter_lowpop: self.filter_lowpop, + strings.filter_modded: self.filter_modded, + strings.filter_unmodded: self.filter_unmodded, + } + + def filter_3pp(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.view != strings.filter_3pp] + + def filter_1pp(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.view != strings.filter_1pp] + + def filter_official(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.provider != strings.filter_official] + + def filter_unofficial(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.provider != strings.filter_unofficial] + + def filter_empty(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.players != 0] + + def filter_full(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.players != row.max_players] + + def filter_duplicate(self, rows: list[ServerRow]) -> list[ServerRow]: + seen = set() + final = [] + for row in rows: + if row.name not in seen: + seen.add(row.name) + final.append(row) + rows = final + return rows + + def filter_day(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if not re.match(self.DAY_REG, row.time)] + + def filter_night(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if not re.match(self.NIGHT_REG, row.time)] + + def filter_nonascii(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.name.isascii()] + + def filter_lowpop(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if (row.players / row.max_players * 100) > 30] + + def filter_modded(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if not row.modded] + + def filter_unmodded(self, rows: list[ServerRow]) -> list[ServerRow]: + return [row for row in rows if row.modded] + + def filter(self, filter_type: str, rows: list[ServerRow]) -> list[ServerRow]: + func = self.filter_map[filter_type] + return func(rows) class ProxyModelManager: @@ -30,20 +142,16 @@ class ProxyModelManager: def __init__(self, filter_man: "FilterManager") -> None: self.filter_cache: dict[ - tuple[str], - list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]], + tuple[str, ...], + list[ServerRow], ] = {} self.proxy_model: "FastInsertListStore" self.filter_man = filter_man + self.filtration_manager = FiltrationManager() - self.control_model: ( - list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]] - | None - ) = None - self.filtered: list[ - tuple[str, str, str, str, int, int, int, str, int, int, str, bool] - ] = [] + self.control_model: list[ServerRow] | None = None + self.filtered: list[ServerRow] = [] self.success = True def has_control_model(self) -> bool: @@ -54,9 +162,7 @@ class ProxyModelManager: def append_row(self, row: list) -> None: self.proxy_model.append(row) - def append_row_to_control( - self, row: tuple[str, str, str, str, int, int, int, str, int, int, str, bool] - ) -> None: + def append_row_to_control(self, row: ServerRow) -> None: if self.control_model is None: raise AttributeError("Trying to add rows to a non-existent model") self.control_model.append(row) @@ -64,22 +170,25 @@ class ProxyModelManager: def update_playercount(self, playercount: "NewPlayerCount") -> None: treeiter = playercount.treeiter - self.proxy_model[treeiter][4] = playercount.players - self.proxy_model[treeiter][6] = playercount.queue + field = [f.name for f in fields(ServerRow)] + index_players = field.index("players") + index_queue = field.index("queue") + self.proxy_model[treeiter][index_players] = playercount.players + self.proxy_model[treeiter][index_queue] = playercount.queue # TODO: use dataclass for record rows def append_row_to_history( self, - history: tuple[str, str, str, str, int, int, int, str, int, int, str, bool], + history: ServerRow, ) -> None: - addr = history[7] - qport = history[8] + addr = history.ip + qport = history.qport if self.control_model is None: raise AttributeError("Trying to append row to empty model") found = False for i, row in enumerate(self.control_model): - if addr == row[7] and qport == row[8]: + if addr == row.ip and qport == row.qport: item = self.control_model.pop(i) self.control_model.append(item) found = True @@ -88,6 +197,7 @@ class ProxyModelManager: if found is False: self.control_model.append(history) + # NOTE: truncate history to 10 records max if len(self.control_model) == 11: del self.control_model[0] @@ -99,7 +209,7 @@ class ProxyModelManager: addr = f"{record.ip}:{record.gameport}" qport = record.qport for row in self.control_model: - if addr == row[7] and qport == row[8]: + if addr == row.ip and qport == row.qport: self.control_model.remove(row) break self.wipe_cache() @@ -121,6 +231,9 @@ class ProxyModelManager: when running in the main loop with 40k+ records skip_cache: used when updating a record on Saved Servers/History and forcing a refilter + + NOTE: data must be marshalled from ServerRow format before constructing list stores + The "clone" is the proxy model (list of tuples) actually fed into Gtk.TreeView """ # TODO: filter cache: return a dataclass object with clearly enumerated map, keyword, and filter values # instead of just a serial list of strings @@ -129,8 +242,9 @@ class ProxyModelManager: if skip_cache is False: if filters in self.filter_cache: cached_rows = self.filter_cache[filters] + marshalled_rows = ServerRow.marshall_rows(cached_rows) clone = ModelFactory().make_server_store() - clone.extend(cached_rows) + clone.extend(marshalled_rows) self.set_proxy_model(clone) self.set_filtered(cached_rows) return None @@ -149,17 +263,18 @@ class ProxyModelManager: clone = ModelFactory().make_server_store() if len(rows) > 0: rows = self.sort_rows(rows) - clone.extend(rows) + marshalled_rows = ServerRow.marshall_rows(rows) + clone.extend(marshalled_rows) self.set_cache(filters, rows) self.set_proxy_model(clone) return clone - def sort_rows(self, rows: list) -> list: - rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x[0].lower())) + def sort_rows(self, rows: list[ServerRow]) -> list[ServerRow]: + rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x.name.lower())) return rows - def filter_initial(self, filters: tuple) -> list: + def filter_initial(self, filters: tuple[str, ...]) -> list[ServerRow]: """ Simply culls the control model of any disabled filters """ @@ -168,7 +283,7 @@ class ProxyModelManager: self.set_filtered(self.filter_toggle_off(filters, f)) return self.filtered - def filter_map(self, filters: tuple) -> list: + def filter_map(self, filters: tuple[str, ...]) -> list[ServerRow]: """ Multi-filtration for any context starts by narrowing by map """ @@ -178,10 +293,10 @@ class ProxyModelManager: if sel_map == strings.all_maps: return rows - rows = [row for row in rows if row[1] == sel_map] + rows = [row for row in rows if row.mapname == sel_map] return rows - def filter_keyword(self, filters: tuple) -> list: + def filter_keyword(self, filters: tuple[str, ...]) -> list[ServerRow]: keyword = self.filter_man.get_active_keyword() rows = self.filtered @@ -191,13 +306,15 @@ class ProxyModelManager: filtered = [ row for row in rows - if keyword in row[0].lower() - or keyword in row[1].lower() - or keyword in row[7].lower() + if keyword in row.name.lower() + or keyword in row.mapname.lower() + or keyword in row.ip.lower() ] return filtered - def filter_toggle_off(self, filters: tuple, filter_type: str) -> list: + def filter_toggle_off( + self, filters: tuple[str, ...], filter_type: str + ) -> list[ServerRow]: """ Sub-filtration of the current model """ @@ -211,43 +328,11 @@ class ProxyModelManager: self.set_filtered(None) return [] - rows = self.filtered - match filter_type: - case strings.filter_3pp: - rows = [row for row in rows if row[2] != strings.filter_3pp] - case strings.filter_1pp: - rows = [row for row in rows if row[2] != strings.filter_1pp] - case strings.filter_official: - rows = [row for row in rows if row[10] != strings.filter_official] - case strings.filter_unofficial: - rows = [row for row in rows if row[10] != strings.filter_unofficial] - case strings.filter_empty: - rows = [row for row in rows if row[4] != 0] - case strings.filter_full: - rows = [row for row in rows if row[4] != row[5]] - case strings.filter_duplicate: - seen = set() - final = [] - for row in rows: - if row[0] not in seen: - seen.add(row[0]) - final.append(row) - rows = final - case strings.filter_day: - rows = [row for row in rows if not re.match(DAY_REG, row[3])] - case strings.filter_night: - rows = [row for row in rows if not re.match(NIGHT_REG, row[3])] - case strings.filter_nonascii: - rows = [row for row in rows if row[0].isascii()] - case strings.filter_lowpop: - rows = [row for row in rows if (row[4] / row[5] * 100) > 30] - case strings.filter_modded: - rows = [row for row in rows if not row[11]] - case strings.filter_unmodded: - rows = [row for row in rows if row[11]] + # TODO: pack filter type enum in filter checkbox, rather than stringwise comparison + rows = self.filtration_manager.filter(filter_type, self.filtered) return rows - def filter_toggle_on(self, filters: tuple[str]) -> list: + def filter_toggle_on(self, filters: tuple[str, ...]) -> list[ServerRow]: """Effectively applies all filters""" self.set_filtered(self.control_model) self.set_filtered(self.filter_map(filters)) @@ -258,31 +343,36 @@ class ProxyModelManager: self.set_filtered(self.filter_toggle_off(filters, f)) return self.filtered - def set_cache(self, filters: tuple[str], filtered_rows: list[tuple]) -> None: + def set_cache( + self, filters: tuple[str, ...], filtered_rows: list[ServerRow] + ) -> None: self.filter_cache[filters] = filtered_rows - def set_filtered(self, rows: list | None) -> None: + def set_filtered(self, rows: list[ServerRow] | None) -> None: if rows is None: self.filtered = [] else: self.filtered = rows - def get_filtered(self) -> list: + def get_filtered(self) -> list[ServerRow]: return self.filtered def set_proxy_model(self, model: "FastInsertListStore") -> None: """ FastInsertListStore representation of the raw model after filtration + + This attribute is saved from within a thread so that it later be retrieved + and safely packed into the TreeView in the main loop, cf. get_proxy_model() """ self.proxy_model = model - def set_control(self, rows: list) -> None: + def set_control(self, rows: list[ServerRow]) -> None: """ Raw data model prior to filtration """ self.control_model = rows - def get_control(self) -> list[Any]: + def get_control(self) -> list[ServerRow]: if self.control_model is None: raise AttributeError( "Expected a populated control model, but it is Nonetype" @@ -293,7 +383,7 @@ class ProxyModelManager: self.filtered = [] self.filter_cache = {} - def push(self, data: list[Any]) -> None: + def push(self, data: list[ServerRow]) -> None: self.wipe_cache() self.set_control(data) cur_keyword = self.filter_man.get_active_keyword() diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index a1d3082..8c1b05e 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -26,7 +26,7 @@ from gi.repository import Gtk, GLib # noqa E402 if TYPE_CHECKING: from dzgui.api.servers import A2SInfo, Record from dzgui.controllers.mc import Controller - from dzgui.model.proxy_model import ProxyModelManager + from dzgui.model.proxy_model import ProxyModelManager, ServerRow from dzgui.views.trees.tree_servers import ServerTreeView logger = logging.getLogger(APP_NAME) @@ -241,10 +241,7 @@ class ServerModelManager: def update_history( self, - rows: ( - list[tuple[str, str, str, str, int, int, int, str, int, int, str, bool]] - | None - ) = None, + rows: list["ServerRow"] | None = None, ) -> None: proxy_man = self._get_proxy_man() if rows is not None: @@ -267,7 +264,6 @@ class ServerModelManager: self.first_iteration = False self.emitter.emit("load_maps", store) - # TODO: dataclass for record rows; check for other dict annotations def add_to_history(self, data: tuple[dict[str, Any], "Record"]) -> None: row, record = data proxy_man = self._get_proxy_man() @@ -453,13 +449,13 @@ class ServerModelManager: dialog.show_all() dialog.run() - def _push_data(self, data: list[Any]) -> None: + def _push_data(self, data: list["ServerRow"]) -> None: self._get_proxy_man().push(data) self._sort_unique_maps(data) self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success)) - def _sort_unique_maps(self, data: list) -> None: - u_maps = set([row[1] for row in data]) + def _sort_unique_maps(self, data: list[Any]) -> None: + u_maps = set([row.mapname for row in data]) self._set_new_maps(sorted(u_maps)) def _set_new_maps(self, maps: list[str]) -> None: diff --git a/dzgui/views/trees/tree_servers.py b/dzgui/views/trees/tree_servers.py index 3143c6d..e2b8a06 100644 --- a/dzgui/views/trees/tree_servers.py +++ b/dzgui/views/trees/tree_servers.py @@ -412,5 +412,9 @@ class ServerTreeView(ContextMixin, TreeView): # type: ignore if row_index >= start[0] <= end[0]: # NOTE: fetch raw data rows real_model = self.proxy_man.get_control() - value = real_model[row_index][col_index] + from dataclasses import fields + from dzgui.model.proxy_model import ServerRow + key = fields(ServerRow)[col_index].name + + value = getattr(real_model[row_index], key) cell.set_property("text", str(value)) From 504a2a724cf02aa113f65729fce080c8e2bf21b2 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:48:37 +0900 Subject: [PATCH 02/10] fix: typo --- dzgui/model/proxy_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index fb67861..ee6ad08 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -48,7 +48,7 @@ class ServerRow: # TODO: tests for marshalled row insertion @classmethod - def marshall_rows( + def marshal_rows( cls, rows: list["ServerRow"] ) -> list[tuple[str, str, str, str, int, int, int, str, int, str, str, bool]]: """Marshals data before passing into FastInsertListStore's extend() method""" @@ -242,7 +242,7 @@ class ProxyModelManager: if skip_cache is False: if filters in self.filter_cache: cached_rows = self.filter_cache[filters] - marshalled_rows = ServerRow.marshall_rows(cached_rows) + marshalled_rows = ServerRow.marshal_rows(cached_rows) clone = ModelFactory().make_server_store() clone.extend(marshalled_rows) self.set_proxy_model(clone) @@ -263,7 +263,7 @@ class ProxyModelManager: clone = ModelFactory().make_server_store() if len(rows) > 0: rows = self.sort_rows(rows) - marshalled_rows = ServerRow.marshall_rows(rows) + marshalled_rows = ServerRow.marshal_rows(rows) clone.extend(marshalled_rows) self.set_cache(filters, rows) From 7322970c540fec55d9bb34fdb558723fc877b55e Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:56:31 +0900 Subject: [PATCH 03/10] fix: define constants outside of FiltrationManager --- dzgui/model/proxy_model.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index ee6ad08..ab01a25 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -14,6 +14,9 @@ if TYPE_CHECKING: from dzgui.api.servers import Record from dzgui.model.servers import NewPlayerCount +DAY_REG = r"([0][6-9]|[1][0-7])" +NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])" + @dataclass(slots=True, frozen=True) class ServerRow: @@ -58,8 +61,6 @@ class ServerRow: class FiltrationManager: def __init__(self) -> None: - self.DAY_REG = r"([0][6-9]|[1][0-7])" - self.NIGHT_REG = r"([0][0-5]|[1][8-9]|[2][0-3])" self.filter_map = { strings.filter_3pp: self.filter_3pp, @@ -106,10 +107,10 @@ class FiltrationManager: return rows def filter_day(self, rows: list[ServerRow]) -> list[ServerRow]: - return [row for row in rows if not re.match(self.DAY_REG, row.time)] + return [row for row in rows if not re.match(DAY_REG, row.time)] def filter_night(self, rows: list[ServerRow]) -> list[ServerRow]: - return [row for row in rows if not re.match(self.NIGHT_REG, row.time)] + return [row for row in rows if not re.match(NIGHT_REG, row.time)] def filter_nonascii(self, rows: list[ServerRow]) -> list[ServerRow]: return [row for row in rows if row.name.isascii()] From 9b948271e1cf9206c933a5da7c4a5749289ae640 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:57:12 +0900 Subject: [PATCH 04/10] chore: add test --- tests/test_row_marshalling.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_row_marshalling.py diff --git a/tests/test_row_marshalling.py b/tests/test_row_marshalling.py new file mode 100644 index 0000000..7e59080 --- /dev/null +++ b/tests/test_row_marshalling.py @@ -0,0 +1,26 @@ +from dzgui.model.model_factory import ModelFactory +from dzgui.model.proxy_model import ServerRow + + +def test_row_conversion() -> None: + """Converted FastInsertListStore is expected to have identical elements""" + data = [ + "Test Server", + "chernarusplus", + "1PP", + "05:00", + 10, + 10, + 0, + "127.0.0.0:27016", + 27017, + 99, + "Unofficial", + False, + ] + rows = [ServerRow(*data)] + marshalled = ServerRow.marshal_rows(rows) + store = ModelFactory().make_server_store() + store.extend(marshalled) + converted = [[el for el in row] for row in store] + assert data == converted[0] From 91d164d6662004e1d8d6835fe3c4a3ab13ad606b Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 03:49:43 +0900 Subject: [PATCH 05/10] chore: drop unused method --- dzgui/model/proxy_model.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index 5975d14..1628ea7 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -161,9 +161,6 @@ class ProxyModelManager: return False return True - def append_row(self, row: list) -> None: - self.proxy_model.append(row) - def append_row_to_control(self, row: ServerRow) -> None: if self.control_model is None: raise AttributeError("Trying to add rows to a non-existent model") From 072927350312f9919367d0e93061839b2bdd55b3 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 03:51:09 +0900 Subject: [PATCH 06/10] change: drop Union --- dzgui/model/proxy_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index 1628ea7..ed96450 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -2,7 +2,7 @@ import re from dataclasses import dataclass, fields from operator import attrgetter -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING from dzgui.const.enum import FilterMode from dzgui.model.model_factory import ModelFactory @@ -216,14 +216,14 @@ class ProxyModelManager: def clear_proxy_model(self) -> None: self.proxy_model.clear() - def get_proxy_model(self) -> Union["FastInsertListStore", None]: + def get_proxy_model(self) -> "FastInsertListStore | None": if hasattr(self, "proxy_model"): return self.proxy_model return None def filter( self, mode: FilterMode, skip_cache: bool = False - ) -> Union["FastInsertListStore", None]: + ) -> "FastInsertListStore | None": """ Native Gtk.TreeView.refilter() method was not performant enough when running in the main loop with 40k+ records From 63316a01a9c28f6a8425705e21a80fc14e5e9fdb Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 03:57:51 +0900 Subject: [PATCH 07/10] fix: extraneous newlines in log --- dzgui/util/diag.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/dzgui/util/diag.py b/dzgui/util/diag.py index 97423da..56ce725 100644 --- a/dzgui/util/diag.py +++ b/dzgui/util/diag.py @@ -11,6 +11,7 @@ from dzgui.const.enum import Preferences from dzgui.config.query import lookup from dzgui.init.prefix import get_version from dzgui.util.redact import redact_home +from dzgui.util.strings import unknown logger = logging.getLogger(APP_NAME) @@ -27,28 +28,22 @@ def get_cpu_model() -> str: def print_servers(servers: list[str]) -> str: - s = "" - for server in servers: - s += server + "\n" - return s + return "\n".join(servers) def print_mods(mods: list[int]) -> str: - s = "" - for mod in mods: - s += str(mod) + "\n" - return s + return "\n".join(map(str, mods)) def write_diagnostic(config: Path, outfile: Path) -> None: date = datetime.now().isoformat() try: - # FIXME: ID_LIKE not available on CachyOS + # NOTE: ID_LIKE not available on CachyOS distro = platform.freedesktop_os_release()["ID_LIKE"] except Exception as e: logger.warn(e) - distro = "Unknown" + distro = unknown kernel = os.uname().release cpu = get_cpu_model() @@ -68,7 +63,6 @@ def write_diagnostic(config: Path, outfile: Path) -> None: servers = lookup(config, Preferences.IP_LIST) servers_pretty = print_servers(servers) - # FIXME: extraneous newlines in lists of mods template = f"""\ {APP_NAME} version {version} Date: {date} @@ -82,6 +76,7 @@ def write_diagnostic(config: Path, outfile: Path) -> None: Mods: {mods_pretty} + Servers: {servers_pretty} """ From e5c8f2a69cdf5fd9335bd22564448c7791bde056 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 04:11:23 +0900 Subject: [PATCH 08/10] chore: drop stale comment --- dzgui/model/servers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 9bb5068..4e4d24f 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -469,7 +469,6 @@ class ServerModelManager: @call_on_thread(dialog.filtering) def refilter(self, mode: FilterMode) -> None: - # FIXME: causes two wait dialogs when map selection change signal emits after loading servers self.first_iteration = False proxy_man = self._get_proxy_man() proxy_man.filter(mode) From 9e8e3730c73ea62f4d74e1eba46bb2e13e544f5e Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 04:30:31 +0900 Subject: [PATCH 09/10] fix: drop unused imports --- dzgui/api/mods.py | 1 - dzgui/views/components/buttons.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dzgui/api/mods.py b/dzgui/api/mods.py index 722e3dc..b8ed4b8 100644 --- a/dzgui/api/mods.py +++ b/dzgui/api/mods.py @@ -4,7 +4,6 @@ import re from concurrent.futures import wait from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass from pathlib import Path from dzgui.api.steam import get_app_path diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index c073059..7fc45b4 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -1,4 +1,4 @@ -from typing import Callable, Literal, Self, TYPE_CHECKING, Union +from typing import Callable, Literal, Self, TYPE_CHECKING from dzgui.util.clip import copy_clipboard from dzgui.strings import buttons From 89dc6aa8a8387fa8872d70fac92fbabc00334333 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 11 Nov 2026 04:52:50 +0900 Subject: [PATCH 10/10] chore: drop comment --- dzgui/model/proxy_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dzgui/model/proxy_model.py b/dzgui/model/proxy_model.py index ed96450..2cd9b24 100644 --- a/dzgui/model/proxy_model.py +++ b/dzgui/model/proxy_model.py @@ -50,7 +50,6 @@ class ServerRow: "modded", ) - # TODO: tests for marshalled row insertion @classmethod def marshal_rows( cls, rows: list["ServerRow"]