chore: move signal emission into ServerTreeView

This commit is contained in:
aclist 2026-02-22 04:04:37 +09:00
parent 99ede36a32
commit 68cd567249
7 changed files with 90 additions and 78 deletions

View File

@ -112,6 +112,8 @@ class Controller(GObject.GObject):
self.prefs: UserPrefs
self.cleanup_func: StoredFunc = None
#self.config_man = ConfigManager()
self.emitter = Emitter()
self.emitter.connect("map_selection_changed", self._on_map_selection_changed)
self.emitter.connect("check_toggled", self._on_check_toggled)
@ -150,12 +152,15 @@ class Controller(GObject.GObject):
logger.critical(f"{attr} is not a valid AppNavigation attribute.")
def get_prefs(self) -> UserPrefs:
# return self.config_man.get_prefs()
return self.prefs
def set_prefs(self, prefs: UserPrefs) -> None:
# self.config_man.set_prefs()
self.prefs = prefs
def query_config(self, key: Preferences) -> str | bool | list:
# return self.config_man.lookup(key)
config = self.prefs.paths.config
return lookup(config, key)
@ -201,6 +206,7 @@ class Controller(GObject.GObject):
widget.handler_unblock_by_func(func)
def toggle_debug_mode(self) -> None:
# self.config_man().toggle_config(Preferences.DEBUG)
self.toggle_config(Preferences.DEBUG)
def get_active_context(self) -> NotebookPage:
@ -217,6 +223,7 @@ class Controller(GObject.GObject):
treeview = self.get_active_treeview()
columns = treeview.get_columns()
#columns_file = self.config_man.get_columns()
columns_file = self.prefs.paths.columns
try:
data = JSON.read_json(columns_file)
@ -242,6 +249,7 @@ class Controller(GObject.GObject):
w, h = self.mediator.window.get_size()
data = {"res": {"width": w, "height": h}}
#res_path = self.config_man.get_resolution()
res_path = self.prefs.paths.resolution
try:
write_json(data, res_path)
@ -279,6 +287,7 @@ class Controller(GObject.GObject):
if haversine is None:
dist = "Unknown"
else:
# self.config_man.use_miles()
if self.prefs.use_miles:
raw = round(haversine.as_miles())
separated = number(raw)
@ -651,25 +660,6 @@ class Controller(GObject.GObject):
model[path][4] = None
self.mediator.modtreeview.set_cursor(0)
def dump_test_2(self) -> None:
data = (
[
"BAR",
"chernarusplus",
"a",
"a",
1,
1,
1,
"185.207.214.16:2302",
1,
1,
"a",
False,
],
)
self.push_data_success(data, FilterMode.INITIAL)
def get_map_man(self) -> "MapManager":
return self.get_active_treeview().get_map_man()
@ -928,6 +918,7 @@ class Controller(GObject.GObject):
self.emitter.emit("load_maps", store)
def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None:
return
# NOTE: workaround for GTK bug where fullscreen causes headers to vanish when model is None
# TODO: this should be internal to servers page
state = self.has_server_model()

View File

@ -3,7 +3,7 @@ import logging
import threading
from functools import wraps
from typing import Callable
from typing import Callable, Optional
from dzgui.views.dialogs.generic import WaitDialog
@ -49,6 +49,7 @@ class ThreadingManager:
self.parent = parent
self.jobs = 1
self.cleanup_func = None
self.alternate_statusbar = None
def call_on_thread(self, dialog_str: str, func: StoredFunc) -> None:
def callback() -> None:
@ -69,6 +70,13 @@ class ThreadingManager:
def increment_dialog_with_str(self, text: str) -> None:
GLib.idle_add(lambda: self.wait_dialog.increment(text))
# TODO: this should not be delegated here
def set_alternate_statusbar(self, msg: str) -> None:
self.alternate_statusbar = msg
def get_alternate_statusbar(self) -> Optional[str]:
return self.alternate_statusbar
def set_cleanup_func(self, func: StoredFunc) -> None:
if type(func) not in (StoredFunc, type(None)):
msg = f"Callback function '{func}' is not of type StoredFunc or None"

View File

@ -1,5 +1,6 @@
from datetime import datetime
import logging
import threading
from concurrent.futures import wait, as_completed
from concurrent.futures import ThreadPoolExecutor
@ -21,6 +22,8 @@ import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib # noqa E402
LAN_TIMEOUT = 0.5
API_TIMEOUT = 3
if TYPE_CHECKING:
from dzgui.controllers.mc import Controller
@ -29,6 +32,10 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
# TODO: failure: spawns error dialog
# TODO: non failure with empty model: updates statusbar
class ServerModelManager:
def __init__(self, controller: "Controller", tv: Gtk.TreeView, first_iteration=False) -> None:
@ -37,6 +44,7 @@ class ServerModelManager:
self.emitter = controller.get_emitter()
if tv.is_loaded():
self.emitter.emit("servers_loaded", enum)
return
self.tv = tv
self.jobs = 1
@ -61,7 +69,8 @@ class ServerModelManager:
# TODO: get row count
self._dump_history()
case ServerTab.LAN:
self._dump_lan()
return
#self._dump_lan()
@call_on_thread(dialog.fetching)
def _dump_api(self) -> None:
@ -74,7 +83,7 @@ class ServerModelManager:
for future in as_completed(futures):
try:
self.thread_man.increment_dialog()
res = future.result(timeout=3)
res = future.result(timeout=API_TIMEOUT)
if res.status != 200 or not res.parsed:
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure))
return
@ -92,15 +101,12 @@ class ServerModelManager:
j = res.json
servers += j["response"]["servers"]
# TODO: strings
#self.thread_man.increment_dialog_with_str("Unpacking servers")
# TODO: try/except
# TODO: try/except when parsing
parsed = Servers.parse_json(servers)
self._push_data_success(parsed, FilterMode.INITIAL)
self._push_data(parsed, FilterMode.INITIAL)
# TODO: strings
@call_on_thread("scanning LAN ports")
def _dump_lan(self, port: int, early_abort: bool) -> None:
@call_on_thread(dialog.scanning)
def dump_lan(self, port: int, early_abort: bool) -> None:
servers = []
ports = range(1, 256)
@ -111,24 +117,24 @@ class ServerModelManager:
]
for future in as_completed(futures):
try:
res = future.result(timeout=0.5)
res = future.result(timeout=LAN_TIMEOUT)
if res is not None and early_abort is True:
# NOTE: first non-empty hit, flag pending threads to close
event.set()
servers.append(res)
self.cleanup_func = StoredFunc(self._cleanup_on_success)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
return
if res is None:
continue
servers.append(res)
except Exception as e:
logger.critical(e)
self.cleanup_func = StoredFunc(self._cleanup_on_failure)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure))
if len(servers) == 0:
self.cleanup_func = StoredFunc(self._cleanup_on_failure)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure))
return
parsed = Servers.parse_json(servers)
self._push_data_success(parsed, FilterMode.INITIAL)
self._push_data(parsed, FilterMode.INITIAL)
# TODO: strings
@call_on_thread("dumping ips")
@ -136,10 +142,11 @@ class ServerModelManager:
# NOTE: block malformed records (TODO: add github issue no.)
# TODO: sanitize ip list at config time, drop this
ips = [ip for ip in ips if len(ip.split(":")) == 3 and ip.split(":")[2] != ""]
job = Servers.query_direct
with ThreadPoolExecutor() as executor:
futures = [
executor.submit(
Servers.query_direct,
job,
ip.split(":")[0],
int(ip.split(":")[2]),
)
@ -154,12 +161,11 @@ class ServerModelManager:
continue
servers.append(res)
if len(servers) == 0:
self.cleanup_func = StoredFunc(self._cleanup_on_failure)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure))
return
# NOTE: 1 extra progress bar pass for parsing
parsed = Servers.parse_json(servers)
self._push_data_success(parsed, FilterMode.INITIAL)
self._push_data(parsed, FilterMode.INITIAL)
def _query_ip_id(self, addr: str) -> None:
# NOTE: Battlemetrics
@ -177,7 +183,8 @@ class ServerModelManager:
# TODO: strings
@call_on_thread("querying address")
def _connect_by_id_or_ip(self, addr: str) -> None:
res = self.query_ip_id(addr)
res = self._query_ip_id(addr)
# TODO: unimplemented
@call_on_thread("querying address")
def _add_by_id_or_ip(self, addr: str) -> None:
@ -193,7 +200,8 @@ class ServerModelManager:
# NOTE: this can be called from other tabs--if current focus is not ServerTab.SAVED, update label only
# TODO: saved servers might not be loaded yet, in which case should just update local file only
# TODO: perform simple equality comparison of self.tv.get_enum() == ServerTab.SAVED
# FIXME: filter man is saved on a per tab basis, so this will mismatch
# FIXME: filter man is saved on a per tab basis, so this will mismatch
# check if servers.get_active_treeview() is same as self.tv
self.set_cleanup_func(StoredFunc(self._cleanup_on_insert))
def _dump_history(self) -> None:
@ -203,10 +211,10 @@ class ServerModelManager:
with open(history, "r") as f:
rows = [row.rstrip("\n") for row in f]
except OSError:
self.cleanup_func = StoredFunc(self._cleanup_on_failure, False)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure, False))
return
if len(rows) == 0:
self.cleanup_func = StoredFunc(self._cleanup_on_failure, False)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure, False))
return
self.thread_man.set_job_count(len(rows))
self._dump_ips(rows)
@ -218,7 +226,7 @@ class ServerModelManager:
if len(ips) == 0:
# FIXME: this is not a failure, just a quiet exit with custom statusbar
# TODO: add custom statusbar parameters
self.cleanup_func = StoredFunc(self._cleanup_on_failure, False)
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure, False))
return
self._dump_ips(ips)
@ -243,17 +251,10 @@ class ServerModelManager:
self.emitter.emit("servers_loaded", context)
def _cleanup_on_success(self) -> None:
self.pending_jobs = 1
self.tv.set_loaded(True)
self.tv.set_model(self.to_insert)
# TODO: this will allow history and saved tab to emit signals to statusbar
# CHORE: test if treeview's sort method inserts row at the correct index
# inserting a row serializes file on disk, updates control model for that tab, and
# reapplies filters to ephemeral model; since filters are applied, in-situ insertion might not be necessary
# TODO: will be inserted out of order
#self.to_insert.connect("row-inserted", lambda *args: print("row inserted into model"))
# 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
@ -262,7 +263,8 @@ class ServerModelManager:
context = self.tv.get_enum()
self.emitter.emit("servers_loaded", context)
# CHORE: this is placeholder logic
# CHORE: this is placeholder logic,
# refills map combo with new maps
if self.first_iteration:
map_man = self.tv.get_map_man()
map_man.set_unique_maps(self.new_maps)
@ -270,14 +272,11 @@ class ServerModelManager:
self.first_iteration = False
self.new_maps = None
self.tv.grab_focus()
def _cleanup_on_failure(self, show_dialog=True) -> None:
self.treeview.set_loaded(True)
map_man = self.treeview.get_map_man()
# TODO: disable map, keyword, and filter widgets if model is None
# -> signal driven (servers_empty)
# -> signal driven (servers_empty, servers_failed_to_load)
# TODO: what if refresh action occurred and failed, and the old model is still valid?
# skip the step below if refresh action failed
# do not wipe control model in this case
@ -286,8 +285,6 @@ class ServerModelManager:
# wipe refresh state to False
self.treeview.set_model(None)
self.treeview.grab_focus()
map_man.set_unique_maps(None)
context = self.treeview.get_enum()
@ -300,7 +297,7 @@ class ServerModelManager:
# TODO: break into initial dump and refilter modes, can drop filtermode kwarg
# and stop pushing empty data
def _push_data_success(self, data: tuple, mode: Optional[FilterMode]) -> None:
def _push_data(self, data: tuple, mode: Optional[FilterMode]) -> None:
# FIXME: calls treeview read methods in thread
# treeview = self.get_active_treeview()
# manager = treeview.get_filter_man()
@ -313,7 +310,7 @@ class ServerModelManager:
manager.set_control(data)
manager.filter(mode)
self.to_insert = manager.get_proxy_model()
# TODO: pre parse maps
u_maps = set([row[1] for row in data])
self.new_maps = sorted(u_maps)

View File

@ -234,6 +234,7 @@ class Dialog:
modlist: str
working: str
updating_mods: str
scanning: str
@dataclass(slots=True, frozen=True)
@ -429,6 +430,7 @@ dialog = Dialog(
modlist="Fetching modlist",
working="Working",
updating_mods="Updating mods",
scanning="Scanning LAN ports",
)
buttons = Button(

View File

@ -1,5 +1,6 @@
from typing import TYPE_CHECKING
from dzgui.model.servers import ServerModelManager
from dzgui.util.strings import connect_panel, fav_panel, lan_panel
from dzgui.views.components.buttons import (
AddButton,
@ -75,7 +76,9 @@ class LanPanel(Gtk.Frame):
else:
port = self.entry.get_text()
abort = self.early_abort.get_active()
self.controller.dump_lan(int(port), abort)
smm = ServerModelManager(self.controller, self.controller.get_active_treeview())
smm.dump_lan(int(port), abort)
#self.controller.dump_lan(int(port), abort)
def _on_radio_toggled(self, button: Gtk.RadioButton) -> None:
state = button.get_active()

View File

@ -55,6 +55,18 @@ class ServerNotebook(Gtk.ScrolledWindow):
self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap)
self.emitter.connect("servers_loaded", self._on_servers_loaded)
def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None:
# NOTE: workaround for GTK bug where fullscreen causes headers to vanish when model is None
# TODO: this should be internal to servers page
tv = self.get_active_treeview()
state = False if tv.get_model() is None else True
tv.set_headers_visible(state)
tv.set_headers_clickable(state)
tv.set_loaded(True)
tv.grab_focus()
def _on_map(self, widget: Self) -> None:
self.emitter.emit("server_page_toggled", True)
# FIXME: only applies to server notebook, not atomic page
@ -137,5 +149,17 @@ class ServerNotebook(Gtk.ScrolledWindow):
tab, col, "_on_col_width_changed", False
)
def get_tabs(self) -> tuple:
return (self.browser, self.saved, self.recent, self.lan)
# def get_tabs(self) -> tuple:
# return (self.browser, self.saved, self.recent, self.lan)
def get_browser(self) -> ServerTreeView:
return self.browser
def get_saved(self) -> ServerTreeView:
return self.saved
def get_recent(self) -> ServerTreeView:
return self.recent
def get_lan(self) -> ServerTreeView:
return self.lan

View File

@ -45,7 +45,6 @@ class ServerTreeView(ContextMixin, TreeView):
self.enum = enum
self.loaded = False
self.query_func: Callable = None
self.filter_man = FilteredModelManager(controller)
model = self.filter_man.get_proxy_model()
@ -59,12 +58,7 @@ class ServerTreeView(ContextMixin, TreeView):
self.queue_id: int
self.handler_id: int
self.query_func_jobs = 1
self.current_proc = None
self.queue = Queue()
# self.queue = multiprocessing.Queue()
prefs = self.controller.get_prefs()
columns = prefs.paths.columns
@ -129,6 +123,7 @@ class ServerTreeView(ContextMixin, TreeView):
self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap)
# TODO: why is this being saved?
self.thread = None
def start_timeout(self) -> None:
@ -184,14 +179,6 @@ class ServerTreeView(ContextMixin, TreeView):
if self.get_enum() is ServerTab.LAN:
self.emitter.emit("lan_tab_toggled", False)
def set_query_func(self, func: "StoredFunc", jobs: int = 1) -> None:
self.query_func = func
self.query_func_jobs = jobs
# TODO: possibly split this up
def get_query_func(self) -> tuple["StoredFunc", int]:
return self.query_func, self.query_func_jobs
def _on_col_width_changed(
self, col: Gtk.TreeViewColumn, width: GObject.ParamSpecInt
) -> None: