mirror of
https://github.com/aclist/dztui.git
synced 2026-08-27 10:17:31 +02:00
feat: add ConnectionManager
This commit is contained in:
parent
e56b89fd64
commit
6bbe0d6882
@ -1,14 +1,18 @@
|
||||
import logging
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from dzgui.api.servers import Record
|
||||
from dzgui.config.query import lookup
|
||||
from dzgui.const.endpoints import BM_SERVERS
|
||||
from dzgui.const.enum import Preferences
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.api.servers import Record
|
||||
|
||||
|
||||
def get_attributes(config: Path, uid: int) -> str:
|
||||
# TODO: handle if key is not set
|
||||
# TODO: tests for malformed IDs/values
|
||||
@ -18,7 +22,7 @@ def get_attributes(config: Path, uid: int) -> str:
|
||||
payload: dict[str, str] = {
|
||||
"filter[game]": "dayz",
|
||||
"sort": "-players",
|
||||
"filter[ids][whitelist]": str(uid)
|
||||
"filter[ids][whitelist]": str(uid),
|
||||
}
|
||||
res = requests.get(BM_SERVERS, params=payload, headers=hdr)
|
||||
res.raise_for_status()
|
||||
@ -26,13 +30,14 @@ def get_attributes(config: Path, uid: int) -> str:
|
||||
return j
|
||||
|
||||
|
||||
def map_id_to_record(config: Path, uid: int) -> Record | None:
|
||||
def map_id_to_record(config: Path, uid: int) -> Optional["Record"]:
|
||||
from dzgui.api.servers import Record
|
||||
|
||||
try:
|
||||
record = get_attributes(config, uid)
|
||||
ip = record["ip"]
|
||||
port = record["port"]
|
||||
qport = record["portQuery"]
|
||||
return Record(ip, port, qport)
|
||||
except Exception as e:
|
||||
logger.warn(e)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@ -12,6 +12,7 @@ import typing # noqa
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
from dzgui.api.bm import map_id_to_record
|
||||
from dzgui.const.constants import REQUEST_TIMEOUT
|
||||
from dzgui.const.endpoints import STEAM_SERVERS
|
||||
from dzgui.util import strings
|
||||
@ -451,3 +452,27 @@ def get_rules(ip: str, qport: int) -> list[int]:
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
return []
|
||||
|
||||
|
||||
def response_to_fq_ip(res: dict) -> str:
|
||||
ip = res["addr"].split(":")[0]
|
||||
gameport = res["gameport"]
|
||||
qport = res["addr"].split(":")[1]
|
||||
return f"{ip}:{gameport}:{qport}"
|
||||
|
||||
|
||||
def query_id_or_ip(addr: str) -> None:
|
||||
# NOTE: Battlemetrics
|
||||
if addr.isdigit():
|
||||
try:
|
||||
config = self.controller.get_prefs().paths.config
|
||||
resolved = map_id_to_record(config, addr)
|
||||
res = query_direct(resolved.ip, resolved.qport)
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
return None
|
||||
else:
|
||||
record = addr.split(":")
|
||||
ip, qport = record[0], record[1]
|
||||
res = query_direct(ip, int(qport))
|
||||
return res
|
||||
|
||||
@ -113,8 +113,8 @@ class Controller(GObject.GObject):
|
||||
self.emitter = Emitter()
|
||||
self.emitter.connect("map_selection_changed", self._on_map_selection_changed)
|
||||
self.emitter.connect("check_toggled", self._on_check_toggled)
|
||||
self.emitter.connect("servers_loaded", self._on_servers_loaded)
|
||||
self.emitter.connect("servers_loaded_init", self._on_servers_loaded_init)
|
||||
#self.emitter.connect("servers_loaded", self._on_servers_loaded)
|
||||
|
||||
# NOTE: suppress requests until entire UI is loaded
|
||||
self.loaded = False
|
||||
@ -861,7 +861,11 @@ class Controller(GObject.GObject):
|
||||
self.filter_man = filter_man
|
||||
|
||||
def populate_model(self, tv: Gtk.TreeView) -> None:
|
||||
ServerModelManager(self, tv, first_iteration=True)
|
||||
# NOTE: skip on previously loaded tabs
|
||||
if tv.is_loaded():
|
||||
self.emitter.emit("servers_loaded", tv.get_enum())
|
||||
return
|
||||
ServerModelManager(self, tv, first_iteration=True).load()
|
||||
|
||||
def get_favorite(self) -> tuple[str, str] | tuple[None, None]:
|
||||
fav = str(self.query_config(Preferences.FAV_LBL))
|
||||
@ -913,14 +917,14 @@ class Controller(GObject.GObject):
|
||||
store = self.get_map_store()
|
||||
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()
|
||||
tv = self.get_active_treeview()
|
||||
tv.set_headers_visible(state)
|
||||
tv.set_headers_clickable(state)
|
||||
#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()
|
||||
# tv = self.get_active_treeview()
|
||||
# tv.set_headers_visible(state)
|
||||
# tv.set_headers_clickable(state)
|
||||
|
||||
def has_server_model(self) -> bool:
|
||||
treeview = self.get_active_treeview()
|
||||
@ -946,6 +950,9 @@ class Controller(GObject.GObject):
|
||||
return self.mediator.notebook
|
||||
|
||||
def get_servers(self) -> "Notebook":
|
||||
return self.mediator.servers
|
||||
|
||||
def get_server_notebook(self) -> "Notebook":
|
||||
return self.mediator.servers.notebook
|
||||
|
||||
def get_window(self) -> "OuterWindow":
|
||||
|
||||
35
dzgui/managers/connection.py
Normal file
35
dzgui/managers/connection.py
Normal file
@ -0,0 +1,35 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import dzgui.api.servers as Servers
|
||||
from dzgui.managers.thread_man import call_on_thread, StoredFunc, ThreadingManager
|
||||
from dzgui.util.strings import api_warn_msg, dialog
|
||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk # noqa E402
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self, controller: "Controller") -> None:
|
||||
|
||||
self.controller = controller
|
||||
self.thread_man = ThreadingManager(parent=controller)
|
||||
|
||||
@call_on_thread(dialog.querying)
|
||||
def _connect_by_id_or_ip(self, addr: str) -> None:
|
||||
res = Servers.query_id_or_ip(addr)
|
||||
if res is None:
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._connection_failure))
|
||||
# TODO: add to history if successful
|
||||
print(res)
|
||||
|
||||
def _connection_failure(self) -> None:
|
||||
# TODO: more explicit warning message, not necessarily API failure?
|
||||
dialog = ExceptionDialog(self.controller, api_warn_msg)
|
||||
dialog.run()
|
||||
@ -21,7 +21,6 @@ def call_on_thread(dialog_str: str) -> Callable:
|
||||
def wrapper(*args, **kwargs):
|
||||
self = args[0]
|
||||
stored = StoredFunc(func, *args, **kwargs)
|
||||
print("SELF", self)
|
||||
if not hasattr(self, "thread_man"):
|
||||
raise AttributeError
|
||||
if type(self.thread_man) is not ThreadingManager:
|
||||
|
||||
@ -1,40 +1,36 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from concurrent.futures import wait, as_completed
|
||||
from concurrent.futures import as_completed
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
import dzgui.api.servers as Servers
|
||||
from dzgui.const.enum import FilterMode, Preferences, ServerTab
|
||||
from dzgui.const.constants import (
|
||||
APPID_DAYZ,
|
||||
APPID_DAYZ_EXP,
|
||||
)
|
||||
from dzgui.const.enum import FilterMode, Preferences, ServerTab
|
||||
from dzgui.managers.thread_man import call_on_thread, StoredFunc, ThreadingManager
|
||||
from dzgui.util.strings import api_warn_msg, dialog
|
||||
from dzgui.views.dialogs.generic import ExceptionDialog
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib # noqa E402
|
||||
from gi.repository import Gtk # noqa E402
|
||||
|
||||
LAN_TIMEOUT = 0.5
|
||||
API_TIMEOUT = 3
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
from dzgui.controllers.emitter import Emitter
|
||||
from dzgui.model.filtered_model import FilteredModelManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO: failure: spawns error dialog
|
||||
# TODO: non failure with empty model: updates statusbar
|
||||
# TODO: non failure with empty model: updates statusbar with help text
|
||||
|
||||
|
||||
class ServerModelManager:
|
||||
@ -42,28 +38,32 @@ class ServerModelManager:
|
||||
self, controller: "Controller", tv: Gtk.TreeView, first_iteration=False
|
||||
) -> None:
|
||||
|
||||
enum = tv.get_enum()
|
||||
self.tv = tv
|
||||
self.enum = tv.get_enum()
|
||||
self.controller = controller
|
||||
self.emitter = controller.get_emitter()
|
||||
if tv.is_loaded():
|
||||
self.emitter.emit("servers_loaded", enum)
|
||||
return
|
||||
|
||||
self.tv = tv
|
||||
self.jobs = 1
|
||||
|
||||
# NOTE: store filter man for access inside thread
|
||||
self.filter_man = tv.get_filter_man()
|
||||
|
||||
# FIXME: change WaitDialog to use parent window only
|
||||
self.thread_man = ThreadingManager(parent=controller)
|
||||
|
||||
# TODO: if first iteration, clear filter man control model
|
||||
# literal first load: iteration 1
|
||||
# refresh: iteration 1 (wipe model)
|
||||
# filter: iteration N+1
|
||||
# TODO: can drop first iteration arg and process in methods
|
||||
self.first_iteration = first_iteration
|
||||
|
||||
# TODO: pass servers.saved treeview when using conpan regardless of current context
|
||||
|
||||
match enum:
|
||||
def load(self) -> None:
|
||||
"""
|
||||
There may be cases where you want to instantiate this class without dumping servers,
|
||||
e.g., adding saved servers from another tab
|
||||
"""
|
||||
match self.enum:
|
||||
case ServerTab.BROWSER:
|
||||
# NOTE: extra DAYZ_EXP param
|
||||
self.thread_man.set_job_count(len(Servers.params) + 1)
|
||||
@ -71,14 +71,12 @@ class ServerModelManager:
|
||||
case ServerTab.SAVED:
|
||||
self._dump_favorites()
|
||||
case ServerTab.RECENT:
|
||||
# TODO: get row count
|
||||
self._dump_history()
|
||||
case ServerTab.LAN:
|
||||
# NOTE: LAN tab is only loaded on demand
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
# self._dump_lan()
|
||||
|
||||
@call_on_thread(dialog.fetching)
|
||||
def _dump_api(self) -> None:
|
||||
@ -113,7 +111,6 @@ class ServerModelManager:
|
||||
j = res.json
|
||||
servers += j["response"]["servers"]
|
||||
|
||||
# TODO: try/except when parsing
|
||||
parsed = Servers.parse_json(servers)
|
||||
self._push_data(parsed, FilterMode.INITIAL)
|
||||
|
||||
@ -150,11 +147,10 @@ class ServerModelManager:
|
||||
parsed = Servers.parse_json(servers)
|
||||
self._push_data(parsed, FilterMode.INITIAL)
|
||||
|
||||
# TODO: strings
|
||||
@call_on_thread("dumping ips")
|
||||
@call_on_thread(dialog.fetching)
|
||||
def _dump_ips(self, ips: list[str]) -> None:
|
||||
# NOTE: block malformed records (TODO: add github issue no.)
|
||||
# TODO: sanitize ip list at config time, drop this
|
||||
# TODO: sanitize ip list at config time and 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:
|
||||
@ -183,42 +179,32 @@ class ServerModelManager:
|
||||
parsed = Servers.parse_json(servers)
|
||||
self._push_data(parsed, FilterMode.INITIAL)
|
||||
|
||||
def _query_ip_id(self, addr: str) -> None:
|
||||
# NOTE: Battlemetrics
|
||||
if addr.isdigit():
|
||||
# FIXME:
|
||||
config = self.controller.get_prefs().paths.config
|
||||
resolved = map_id_to_record(config, addr)
|
||||
res = Servers.query_direct(resolved.ip, resolved.qport)
|
||||
else:
|
||||
record = addr.split(":")
|
||||
ip, qport = record[0], record[1]
|
||||
res = Servers.query_direct(ip, int(qport))
|
||||
return res
|
||||
|
||||
# TODO: strings
|
||||
@call_on_thread("querying address")
|
||||
def _connect_by_id_or_ip(self, addr: str) -> None:
|
||||
res = self._query_ip_id(addr)
|
||||
# TODO: unimplemented
|
||||
|
||||
@call_on_thread("querying address")
|
||||
@call_on_thread(dialog.querying)
|
||||
def _add_by_id_or_ip(self, addr: str) -> None:
|
||||
res = self.query_ip_id(addr)
|
||||
# TODO: investigate this
|
||||
res = Servers.query_id_or_ip(addr)
|
||||
if res is None:
|
||||
self.set_cleanup_func(StoredFunc(self._cleanup_on_failure))
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_failure))
|
||||
return
|
||||
# NOTE: single record insertion
|
||||
self.insert_record = Servers.parse_json([res])
|
||||
# TODO: add into saved servers file
|
||||
# TODO: update saved servers model
|
||||
# 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
|
||||
# check if servers.get_active_treeview() is same as self.tv
|
||||
self.set_cleanup_func(StoredFunc(self._cleanup_on_insert))
|
||||
|
||||
record = Servers.parse_json([res])
|
||||
|
||||
filter_man = self._get_filter_man()
|
||||
model = filter_man.get_control()
|
||||
|
||||
# TODO: make this a method of ConfigManager
|
||||
fqip = Servers.response_to_fq_ip(res)
|
||||
ips = self.controller.query_config(Preferences.IP_LIST)
|
||||
ips.append(fqip)
|
||||
self.controller.update_config(Preferences.IP_LIST, ips)
|
||||
|
||||
if model is not None:
|
||||
# NOTE: single record insertion
|
||||
model.append(record[0])
|
||||
# TODO: if all filters are already applied, strange behavior may occur
|
||||
# -> need to insert and update per current filters
|
||||
filter_man.filter(FilterMode.INITIAL)
|
||||
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_single_ip))
|
||||
|
||||
def _dump_history(self) -> None:
|
||||
history = self.controller.get_prefs().paths.history
|
||||
@ -242,6 +228,7 @@ class ServerModelManager:
|
||||
def _dump_favorites(self) -> None:
|
||||
ips = self.controller.query_config(Preferences.IP_LIST)
|
||||
self.thread_man.set_job_count(len(ips))
|
||||
|
||||
# TODO: customize statusbar to mention how records can be added via contextmenu
|
||||
if len(ips) == 0:
|
||||
# FIXME: this is not a failure, just a quiet exit with custom statusbar
|
||||
@ -252,25 +239,20 @@ class ServerModelManager:
|
||||
return
|
||||
self._dump_ips(ips)
|
||||
|
||||
def _cleanup_on_insert(self) -> None:
|
||||
filter_man = self.get_filter_man()
|
||||
model = filter_man.get_control()
|
||||
|
||||
# FIXME: this is a single row insertion,
|
||||
# but refiltration should occur in thread for consistency/scalability
|
||||
model.append(self.insert_record[0])
|
||||
proxy = filter_man.filter(FilterMode.INITIAL)
|
||||
|
||||
# TODO: get proxy model out of thread
|
||||
proxy = self.get_filter_man().get_proxy_model()
|
||||
def _cleanup_single_ip(self) -> None:
|
||||
proxy = self._get_filter_man().get_proxy_model()
|
||||
self.tv.set_model(proxy)
|
||||
|
||||
# TODO: update statusbar
|
||||
context = self.tv.get_enum()
|
||||
# TODO: if current tab != self.saved, add label
|
||||
# TODO: adding a row may update available maps
|
||||
# TODO: if all filters are already applied, strange behavior may occur
|
||||
# -> need to insert and update per current filters
|
||||
self.emitter.emit("servers_loaded", context)
|
||||
self.emitter.emit("servers_loaded", self.enum)
|
||||
self._update_maps()
|
||||
|
||||
def _update_maps(self) -> None:
|
||||
map_man = self.tv.get_map_man()
|
||||
map_man.set_unique_maps(self._get_new_maps())
|
||||
self.emitter.emit("servers_loaded_init")
|
||||
self.first_iteration = False
|
||||
|
||||
def _cleanup_on_success(self) -> None:
|
||||
self.tv.set_model(self.to_insert)
|
||||
@ -285,14 +267,8 @@ class ServerModelManager:
|
||||
context = self.tv.get_enum()
|
||||
self.emitter.emit("servers_loaded", context)
|
||||
|
||||
# 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)
|
||||
self.emitter.emit("servers_loaded_init")
|
||||
self.first_iteration = False
|
||||
self.new_maps = None
|
||||
self._update_maps()
|
||||
|
||||
def _cleanup_on_failure(self, show_dialog=True) -> None:
|
||||
map_man = self.tv.get_map_man()
|
||||
@ -334,10 +310,16 @@ class ServerModelManager:
|
||||
self.to_insert = manager.get_proxy_model()
|
||||
|
||||
u_maps = set([row[1] for row in data])
|
||||
self.new_maps = sorted(u_maps)
|
||||
self._set_new_maps(sorted(u_maps))
|
||||
|
||||
self.thread_man.set_cleanup_func(StoredFunc(self._cleanup_on_success))
|
||||
|
||||
def _set_new_maps(self, maps: list[str]) -> None:
|
||||
self.new_maps = maps
|
||||
|
||||
def _get_new_maps(self) -> list[str]:
|
||||
return self.new_maps
|
||||
|
||||
def _get_filter_man(self) -> "FilteredModelManager":
|
||||
return self.filter_man
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dzgui.managers.connection import ConnectionManager
|
||||
from dzgui.model.servers import ServerModelManager
|
||||
from dzgui.util.strings import connect_panel, fav_panel, lan_panel
|
||||
from dzgui.views.components.buttons import (
|
||||
@ -178,6 +179,7 @@ class AddPanel(Gtk.Frame):
|
||||
self.add_server.connect("clicked", self._on_add_clicked)
|
||||
|
||||
self.conn_server = SteamConnectButton()
|
||||
self.conn_server.connect("clicked", self._on_connect_clicked)
|
||||
self.conn_server.set_sensitive(False)
|
||||
self.add_server.set_sensitive(False)
|
||||
|
||||
@ -199,11 +201,14 @@ class AddPanel(Gtk.Frame):
|
||||
|
||||
self.add(self.grid)
|
||||
|
||||
def _on_connect_clicked(self, button: Gtk.Button) -> None:
|
||||
text = self.entry.get_text()
|
||||
ConnectionManager(self.controller)._connect_by_id_or_ip(text)
|
||||
|
||||
def _submit_query(self) -> None:
|
||||
text = self.entry.get_text()
|
||||
# TODO: call servermodelmanager
|
||||
ServerModelManager().add_by_id_or_ip(text)
|
||||
# self.controller.add_by_id_or_ip(text)
|
||||
saved = self.controller.get_servers().get_saved()
|
||||
ServerModelManager(self.controller, saved)._add_by_id_or_ip(text)
|
||||
|
||||
def _on_activate(self, entry: Gtk.Entry) -> None:
|
||||
if not self.add_server.is_sensitive():
|
||||
|
||||
@ -22,7 +22,7 @@ class Breadcrumbs(Gtk.Label):
|
||||
self.set_markup(crumbs)
|
||||
|
||||
notebook = controller.get_notebook()
|
||||
servers = controller.get_servers()
|
||||
servers = controller.get_server_notebook()
|
||||
notebook.connect_after("switch-page", self._on_notebook_page_changed)
|
||||
servers.connect_after("switch-page", self._on_server_tab_changed)
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ import logging
|
||||
|
||||
from typing import Optional, Self, TYPE_CHECKING
|
||||
|
||||
from dzgui.controllers.mc import StoredFunc
|
||||
from dzgui.const.enum import ContextMenuGroup, ServerTab
|
||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||
from dzgui.util.strings import server_labels
|
||||
@ -16,6 +15,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
from dzgui.controllers.mc import Emitter
|
||||
|
||||
|
||||
class ServerNotebook(Gtk.ScrolledWindow):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user