feat: dynamic haversine calc

This commit is contained in:
aclist 2026-01-09 22:49:12 +09:00
parent 775ca2a8d9
commit 10037b86d8
5 changed files with 105 additions and 97 deletions

View File

@ -38,9 +38,10 @@ from dzgui.config import update
from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs
from dzgui.controllers.model import ModelManager
from dzgui.util.diag import write_diagnostic
from dzgui.util import cooldown, strings
from dzgui.util.diag import write_diagnostic
from dzgui.util._json import read_json, write_json
from dzgui.util.localize import number
from dzgui.util.open_links import open_workshop_page, open_user_workshop
from dzgui.util.format import format_mods, format_player_count, pluralize
from dzgui.util.redact import redact_log
@ -55,16 +56,17 @@ from gi.repository import Gtk, Gdk, GLib, GObject # noqa E402
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from dzgui.views.components.buttonbox import ContextualButton
from dzgui.views.base import OuterWindow
from dzgui.views.trees.tree_base import TreeView
from dzgui.views.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView
from dzgui.views.trees.tree_log import LogTreeView
from dzgui.util.dist import Haversine
from dzgui.views.base import Notebook, Grid, OuterWindow
from dzgui.views.components.buttonbox import ContextualButton
from dzgui.views.components.statusbar import Statusbar
from dzgui.views.components.right_panel import RightPanel
from dzgui.views.pages.servers import ServerNotebook
from dzgui.views.trees.tree_base import TreeView
from dzgui.views.trees.tree_menu import MenuTreeView
from dzgui.views.trees.tree_mods import ModTreeView
from dzgui.views.trees.tree_servers import ServerTreeView
from dzgui.views.trees.tree_log import LogTreeView
class AppNavigation:
window: "OuterWindow"
@ -79,13 +81,11 @@ class AppNavigation:
saved: "ServerTreeView"
recent: "ServerTreeView"
lan: "ServerTreeView"
modtreeview: "ModTreeView"
logtreeview: "LogTreeView"
# TODO: most server contexts can be dropped from this struct
class Controller:
def __init__(self) -> None:
self.dist_cache: dict[str, "Haversine"] = {}
self.crumbs_cache = ""
self.mediator = AppNavigation()
self.prefs: UserPrefs
@ -238,10 +238,29 @@ class Controller:
Gtk.main_quit()
def get_statusbar(self) -> str:
return self.mediator.grid.statusbar.get_text()
return self.mediator.statusbar.get_text()
def set_statusbar(self, text: str) -> None:
self.mediator.grid.statusbar.set_text(text)
self.mediator.statusbar.set_text(text)
def set_statusbar_placeholder(self, text: str) -> None:
self.statusbar_placeholder = text
def set_statusbar_dist(self, haversine: "Haversine") -> None:
dist: str
if haversine is None:
dist = "Unknown"
else:
if self.query_config(Preferences.DIST) is True:
raw = round(haversine.as_miles())
separated = number(raw)
dist = str(separated) + " mi"
else:
raw = round(haversine.as_kilometers())
separated = number(raw)
dist = str(separated) + " km"
text = self.statusbar_placeholder
self.set_statusbar(f"{text} | Distance: {dist}")
def delete_multiple_mods(self) -> None:
sel = self.mediator.modtreeview.get_selection()
@ -327,7 +346,7 @@ class Controller:
return False
def set_statusbar_by_row(self, row: "RowType") -> None:
self.mediator.grid.statusbar.refresh(row)
self.mediator.statusbar.refresh(row)
def toggle_server_panels(self, state: bool) -> None:
self.mediator.grid.toggle_filter_panel(state)
@ -357,7 +376,7 @@ class Controller:
case ButtonType.MODS:
self.load_mods()
case ButtonType.HELP:
self.mediator.grid.statusbar.refresh(RowType.CHANGELOG)
self.mediator.statusbar.refresh(RowType.CHANGELOG)
pass
case ButtonType.SERVERS:
self.mediator.notebook.set_page_by_enum(button.opens)
@ -432,7 +451,7 @@ class Controller:
def update_mod_statusbar(self) -> None:
total_mods, total_size = self.calc_mod_size()
msg = format_mods(total_size, total_mods)
self.mediator.grid.statusbar.set_text(msg)
self.mediator.statusbar.set_text(msg)
def calc_mod_size(self) -> tuple[int, int]:
model = self.model_manager.get_mod_store()
@ -614,7 +633,8 @@ class Controller:
treeview = self.mediator.notebook.servers.get_active_treeview()
model = treeview.get_model()
status = format_player_count(model)
self.mediator.statusbar.set_text(status)
self.set_statusbar_placeholder(status)
self.set_statusbar(status + "| Calculating...")
def propagate_column_width(self, col: Gtk.TreeViewColumn) -> None:
GLib.idle_add(self.mediator.servers.update_tab_widths, col)
@ -651,10 +671,13 @@ class Controller:
self.mediator.right_panel.focus_button_box()
def present_servers(self) -> None:
tree = self.get_active_treeview()
# TODO: abstract
self.grab_active_treeview()
self.update_server_status()
crumbs = self.mediator.servers.get_cached_label()
self.set_crumbs(crumbs)
tree.emit("on_distcalc_started")
def toggle_check(self, event: Gdk.EventKey) -> None:
keyname = Gdk.keyval_name(event.keyval)
@ -677,3 +700,6 @@ class Controller:
if len(fav) < 1:
return None
return fav
def get_dist_cache(self) -> dict[str, "Haversine"]:
return self.dist_cache

View File

@ -1,10 +1,9 @@
import logging
import multiprocessing
from math import radians, cos, sin, asin, sqrt
from typing import TYPE_CHECKING
import dzgui.util.ip as ip
from dzgui.util.ip import GeolocationError
from dzgui.util.ip import GeolocationError, get_coords
from dzgui.util.localize import number
import gi
@ -13,6 +12,9 @@ from gi.repository import Gtk # noqa E402
if TYPE_CHECKING:
from dzgui.util.ip import Coords
from dzgui.controllers.mc import Controller
logger = logging.getLogger(__name__)
class Haversine():
def __init__(self, lat1: float, lon1: float, lat2: float, lon2: float) -> None:
@ -35,53 +37,40 @@ class Haversine():
def as_miles(self) -> float:
return self.dist / 1609.344
def compare(local: "Coords", remote_c: str, fmt: str) -> int | None:
if local is None:
return None
try:
# FIXME: expects XDG.ips
remote = ip.get_coords(remote_c)
except GeolocationError:
return None
haversine = Haversine(local.lat, local.lon, remote.lat, remote.lon)
if fmt == "km":
dist = haversine.as_kilometers()
else:
dist = haversine.as_miles()
return round(dist)
class CalcDist(multiprocessing.Process):
def __init__(
self,
widget: Gtk.Widget,
addr: str,
result_queue: multiprocessing.Queue,
cache: dict,
):
controller: "Controller",
) -> None:
super().__init__()
self.controller = controller
self.result_queue = result_queue
self.addr = addr
self.ip = addr.split(":")[0]
# TODO: pass controller correctly
def run(self) -> None:
use_miles = MainController.query_config("use_miles")
fmt = "mi" if use_miles else "km"
# TODO: get cache accordingly
cache = self.controller.get_dist_cache()
if self.addr in cache:
if fmt in cache[self.addr]:
logger.info(f"Address '{self.addr}' already in cache")
self.result_queue.put([self.addr, cache[self.addr]])
return
logger.info(f"Address '{self.addr}' already in cache")
self.result_queue.put([self.addr, cache[self.addr]])
return
prefs = MainController.get_prefs()
dist = compare(prefs.coords, prefs.paths.ips, self.ip, fmt)
if dist is None:
dist_pretty = "Unknown"
else:
d = number(dist)
dist_pretty = f"{d} {fmt}"
self.result_queue.put([self.addr, dist_pretty])
dist = self.compare(self.ip)
self.result_queue.put([self.addr, dist])
def compare(self, remote: str) -> int | None:
prefs = self.controller.get_prefs()
local = prefs.coords
if local is None:
return None
try:
remote = get_coords(prefs.paths.ips, remote)
except GeolocationError:
return None
# TODO: handle failed remote dist
haversine = Haversine(local.lat, local.lon, remote.lat, remote.lon)
return haversine

View File

@ -64,6 +64,7 @@ def format_player_count(model: Gtk.TreeModel | None) -> str:
hits_pretty = pluralize("matches", hits)
suffix = distance_suffix
status = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
return status
return f"{status} | {suffix}"

View File

@ -52,22 +52,22 @@ class ServerNotebook(Gtk.ScrolledWindow):
pass
#store.append(["BAR", "a", "a", "a", 1, 1, 1, "1:1", 0, 0, "a", False])
else:
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "1:1", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
store.append(["BAR", "a", "a", "a", 0, 0, 0, "189.127.165.207:2302", 0, 0, "a", False])
tree.loaded = True
# TODO: set model on init of servertreeview
tree.set_model(store)
@ -110,12 +110,13 @@ class ServerNotebook(Gtk.ScrolledWindow):
child.grab_focus()
# FIXME: doesn't fire on first run
self.controller.update_server_status()
#self.controller.update_server_status()
self.controller.present_servers()
self.controller.populate_model()
# TODO: put in controller
def query_test(self) -> None:
data = (["BAR", "a", "a", "a", 1, 1, 1, "1:1", 0, 0, "a", False])
data = (["BAR", "a", "a", "a", 1, 1, 1, "185.207.214.16:2302", 0, 0, "a", False])
return data
def get_cached_label(self) -> str:

View File

@ -92,7 +92,7 @@ class ServerTreeView(TreeView):
column.connect("notify::fixed-width", self._on_col_width_changed)
self.append_column(column)
self.connect("on_distcalc_started", self._on_calclat_started)
self.connect("on_distcalc_started", self._on_distcalc_started)
self.connect("button-release-event", self._on_server_button_release)
self.connect("key-press-event", self._on_server_keypress)
self.connect("generic_row_activated", self._parent_row_activated)
@ -150,22 +150,28 @@ class ServerTreeView(TreeView):
if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate()
def _on_calclat_started(self, treeview):
server_tooltip[0] = format_tooltip()
server_tooltip[1] = server_tooltip[0] + "| Distance: calculating..."
self.update_statusbar(server_tooltip[1])
def _on_distcalc_started(self, treeview):
record = self.get_record()
if record is None:
return
ip = record.ip
self.current_proc = CalcDist(record.ip, self.queue, self.controller)
self.current_proc.start()
def _check_result_queue(self) -> Literal[True]:
# TODO: trigger signal when changing page contexts
# TODO: delegate to controller
latest_result = None
while not self.queue.empty():
latest_result = self.queue.get()
cache = self.controller.get_dist_cache()
if latest_result:
addr = latest_result[0]
km = latest_result[1]
cache[addr] = km
haversine = latest_result[1]
cache[addr] = haversine
# FIXME
self.statusbar.append_distance(km)
self.controller.set_statusbar_dist(haversine)
return True
def _on_server_keypress(
@ -261,23 +267,8 @@ class ServerTreeView(TreeView):
print(self.get_value_at_index(0))
def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection):
self.terminate_process()
record = self.get_record()
if record is None:
return
#model = self.get_model()
#if record is None:
# self.controller.update_server_status(model)
# return
ip = record.ip
# TODO
#self.emit("on_distcalc_started")
#self.current_proc = CalcDist(self, record.ip, self.queue, cache)
#self.current_proc.start()
self.emit("on_distcalc_started")
def get_record_string(self) -> str:
addr = self.get_value_at_index(7)