feat: dynamic haversine calc

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

No files matched your search

+42 -16
View File
@@ -38,9 +38,10 @@ from dzgui.config import update
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
from dzgui.controllers.model import ModelManager from dzgui.controllers.model import ModelManager
from dzgui.util.diag import write_diagnostic
from dzgui.util import cooldown, strings 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._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.open_links import open_workshop_page, open_user_workshop
from dzgui.util.format import format_mods, format_player_count, pluralize from dzgui.util.format import format_mods, format_player_count, pluralize
from dzgui.util.redact import redact_log 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__) logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.views.components.buttonbox import ContextualButton from dzgui.util.dist import Haversine
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.views.base import Notebook, Grid, OuterWindow 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.statusbar import Statusbar
from dzgui.views.components.right_panel import RightPanel from dzgui.views.components.right_panel import RightPanel
from dzgui.views.pages.servers import ServerNotebook 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: class AppNavigation:
window: "OuterWindow" window: "OuterWindow"
@@ -79,13 +81,11 @@ class AppNavigation:
saved: "ServerTreeView" saved: "ServerTreeView"
recent: "ServerTreeView" recent: "ServerTreeView"
lan: "ServerTreeView" lan: "ServerTreeView"
modtreeview: "ModTreeView"
logtreeview: "LogTreeView" logtreeview: "LogTreeView"
# TODO: most server contexts can be dropped from this struct
class Controller: class Controller:
def __init__(self) -> None: def __init__(self) -> None:
self.dist_cache: dict[str, "Haversine"] = {}
self.crumbs_cache = "" self.crumbs_cache = ""
self.mediator = AppNavigation() self.mediator = AppNavigation()
self.prefs: UserPrefs self.prefs: UserPrefs
@@ -238,10 +238,29 @@ class Controller:
Gtk.main_quit() Gtk.main_quit()
def get_statusbar(self) -> str: 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: 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: def delete_multiple_mods(self) -> None:
sel = self.mediator.modtreeview.get_selection() sel = self.mediator.modtreeview.get_selection()
@@ -327,7 +346,7 @@ class Controller:
return False return False
def set_statusbar_by_row(self, row: "RowType") -> None: 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: def toggle_server_panels(self, state: bool) -> None:
self.mediator.grid.toggle_filter_panel(state) self.mediator.grid.toggle_filter_panel(state)
@@ -357,7 +376,7 @@ class Controller:
case ButtonType.MODS: case ButtonType.MODS:
self.load_mods() self.load_mods()
case ButtonType.HELP: case ButtonType.HELP:
self.mediator.grid.statusbar.refresh(RowType.CHANGELOG) self.mediator.statusbar.refresh(RowType.CHANGELOG)
pass pass
case ButtonType.SERVERS: case ButtonType.SERVERS:
self.mediator.notebook.set_page_by_enum(button.opens) self.mediator.notebook.set_page_by_enum(button.opens)
@@ -432,7 +451,7 @@ class Controller:
def update_mod_statusbar(self) -> None: def update_mod_statusbar(self) -> None:
total_mods, total_size = self.calc_mod_size() total_mods, total_size = self.calc_mod_size()
msg = format_mods(total_size, total_mods) 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]: def calc_mod_size(self) -> tuple[int, int]:
model = self.model_manager.get_mod_store() model = self.model_manager.get_mod_store()
@@ -614,7 +633,8 @@ class Controller:
treeview = self.mediator.notebook.servers.get_active_treeview() treeview = self.mediator.notebook.servers.get_active_treeview()
model = treeview.get_model() model = treeview.get_model()
status = format_player_count(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: def propagate_column_width(self, col: Gtk.TreeViewColumn) -> None:
GLib.idle_add(self.mediator.servers.update_tab_widths, col) GLib.idle_add(self.mediator.servers.update_tab_widths, col)
@@ -651,10 +671,13 @@ class Controller:
self.mediator.right_panel.focus_button_box() self.mediator.right_panel.focus_button_box()
def present_servers(self) -> None: def present_servers(self) -> None:
tree = self.get_active_treeview()
# TODO: abstract
self.grab_active_treeview() self.grab_active_treeview()
self.update_server_status() self.update_server_status()
crumbs = self.mediator.servers.get_cached_label() crumbs = self.mediator.servers.get_cached_label()
self.set_crumbs(crumbs) self.set_crumbs(crumbs)
tree.emit("on_distcalc_started")
def toggle_check(self, event: Gdk.EventKey) -> None: def toggle_check(self, event: Gdk.EventKey) -> None:
keyname = Gdk.keyval_name(event.keyval) keyname = Gdk.keyval_name(event.keyval)
@@ -677,3 +700,6 @@ class Controller:
if len(fav) < 1: if len(fav) < 1:
return None return None
return fav return fav
def get_dist_cache(self) -> dict[str, "Haversine"]:
return self.dist_cache
+28 -39
View File
@@ -1,10 +1,9 @@
import logging
import multiprocessing import multiprocessing
from math import radians, cos, sin, asin, sqrt from math import radians, cos, sin, asin, sqrt
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dzgui.util.ip import GeolocationError, get_coords
import dzgui.util.ip as ip
from dzgui.util.ip import GeolocationError
from dzgui.util.localize import number from dzgui.util.localize import number
import gi import gi
@@ -13,6 +12,9 @@ from gi.repository import Gtk # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.util.ip import Coords from dzgui.util.ip import Coords
from dzgui.controllers.mc import Controller
logger = logging.getLogger(__name__)
class Haversine(): class Haversine():
def __init__(self, lat1: float, lon1: float, lat2: float, lon2: float) -> None: def __init__(self, lat1: float, lon1: float, lat2: float, lon2: float) -> None:
@@ -35,53 +37,40 @@ class Haversine():
def as_miles(self) -> float: def as_miles(self) -> float:
return self.dist / 1609.344 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): class CalcDist(multiprocessing.Process):
def __init__( def __init__(
self, self,
widget: Gtk.Widget,
addr: str, addr: str,
result_queue: multiprocessing.Queue, result_queue: multiprocessing.Queue,
cache: dict, controller: "Controller",
): ) -> None:
super().__init__() super().__init__()
self.controller = controller
self.result_queue = result_queue self.result_queue = result_queue
self.addr = addr self.addr = addr
self.ip = addr.split(":")[0] self.ip = addr.split(":")[0]
# TODO: pass controller correctly
def run(self) -> None: def run(self) -> None:
use_miles = MainController.query_config("use_miles") cache = self.controller.get_dist_cache()
fmt = "mi" if use_miles else "km"
# TODO: get cache accordingly
if self.addr in cache: if self.addr in cache:
if fmt in cache[self.addr]: logger.info(f"Address '{self.addr}' already in cache")
logger.info(f"Address '{self.addr}' already in cache") self.result_queue.put([self.addr, cache[self.addr]])
self.result_queue.put([self.addr, cache[self.addr]]) return
return
prefs = MainController.get_prefs() dist = self.compare(self.ip)
dist = compare(prefs.coords, prefs.paths.ips, self.ip, fmt) self.result_queue.put([self.addr, dist])
if dist is None:
dist_pretty = "Unknown" def compare(self, remote: str) -> int | None:
else: prefs = self.controller.get_prefs()
d = number(dist) local = prefs.coords
dist_pretty = f"{d} {fmt}" if local is None:
self.result_queue.put([self.addr, dist_pretty]) 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
+1
View File
@@ -64,6 +64,7 @@ def format_player_count(model: Gtk.TreeModel | None) -> str:
hits_pretty = pluralize("matches", hits) hits_pretty = pluralize("matches", hits)
suffix = distance_suffix suffix = distance_suffix
status = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}" status = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
return status
return f"{status} | {suffix}" return f"{status} | {suffix}"
+19 -18
View File
@@ -52,22 +52,22 @@ class ServerNotebook(Gtk.ScrolledWindow):
pass pass
#store.append(["BAR", "a", "a", "a", 1, 1, 1, "1:1", 0, 0, "a", False]) #store.append(["BAR", "a", "a", "a", 1, 1, 1, "1:1", 0, 0, "a", False])
else: 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, "189.127.165.207:2302", 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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "1:1", 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 tree.loaded = True
# TODO: set model on init of servertreeview # TODO: set model on init of servertreeview
tree.set_model(store) tree.set_model(store)
@@ -110,12 +110,13 @@ class ServerNotebook(Gtk.ScrolledWindow):
child.grab_focus() child.grab_focus()
# FIXME: doesn't fire on first run # 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() self.controller.populate_model()
# TODO: put in controller # TODO: put in controller
def query_test(self) -> None: 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 return data
def get_cached_label(self) -> str: def get_cached_label(self) -> str:
+15 -24
View File
@@ -92,7 +92,7 @@ class ServerTreeView(TreeView):
column.connect("notify::fixed-width", self._on_col_width_changed) column.connect("notify::fixed-width", self._on_col_width_changed)
self.append_column(column) 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("button-release-event", self._on_server_button_release)
self.connect("key-press-event", self._on_server_keypress) self.connect("key-press-event", self._on_server_keypress)
self.connect("generic_row_activated", self._parent_row_activated) 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(): if self.current_proc and self.current_proc.is_alive():
self.current_proc.terminate() self.current_proc.terminate()
def _on_calclat_started(self, treeview): def _on_distcalc_started(self, treeview):
server_tooltip[0] = format_tooltip() record = self.get_record()
server_tooltip[1] = server_tooltip[0] + "| Distance: calculating..." if record is None:
self.update_statusbar(server_tooltip[1]) 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]: def _check_result_queue(self) -> Literal[True]:
# TODO: trigger signal when changing page contexts
# TODO: delegate to controller
latest_result = None latest_result = None
while not self.queue.empty(): while not self.queue.empty():
latest_result = self.queue.get() latest_result = self.queue.get()
cache = self.controller.get_dist_cache()
if latest_result: if latest_result:
addr = latest_result[0] addr = latest_result[0]
km = latest_result[1] haversine = latest_result[1]
cache[addr] = km cache[addr] = haversine
# FIXME # FIXME
self.statusbar.append_distance(km) self.controller.set_statusbar_dist(haversine)
return True return True
def _on_server_keypress( def _on_server_keypress(
@@ -261,23 +267,8 @@ class ServerTreeView(TreeView):
print(self.get_value_at_index(0)) print(self.get_value_at_index(0))
def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection): def _parent_selection_changed(self, base_class: TreeView, sel: Gtk.TreeSelection):
self.terminate_process() self.terminate_process()
record = self.get_record() self.emit("on_distcalc_started")
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()
def get_record_string(self) -> str: def get_record_string(self) -> str:
addr = self.get_value_at_index(7) addr = self.get_value_at_index(7)