Compare commits

..
Author SHA1 Message Date
aclist 78144e9127 chore: update tests 2026-10-31 00:26:51 +09:00
aclist 33b8dfba70 change: simplify logic 2026-10-31 00:26:27 +09:00
aclist abf0060cac feat: import ipaddress 2026-10-31 00:26:08 +09:00
aclist 95daa9163b change: update paths 2026-10-31 00:25:32 +09:00
aclist ff068e5396 feat: marshal geo DB 2026-10-31 00:23:27 +09:00
aclist 46117ecdda Merge pull request #507 from aclist/prerelease/7.0.0b29-patch-3
fix: add path out of loop
2026-10-26 19:25:32 +09:00
aclist 30119fec46 fix: add path out of loop 2026-10-26 19:24:42 +09:00
aclist ac01818549 Merge pull request #506 from aclist/prerelease/7.0.0b29-patch-2
fix: add /tmp to wizard
2026-10-26 19:21:40 +09:00
aclist d486712a4c fix: add /tmp to wizard 2026-10-26 19:20:17 +09:00
aclist d116702145 Merge pull request #505 from aclist/prerelease/7.0.0b29-patch-1
Prerelease/7.0.0b29 patch 1
2026-10-26 19:11:15 +09:00
aclist 5bee3614bd fix: whitelist /tmp 2026-10-26 19:09:50 +09:00
aclist c73b07991f change: abstract lock file path 2026-10-26 19:09:32 +09:00
aclist 199cd07982 Merge pull request #504 from aclist/prerelease/7.0.0b29
chore: bump version
2026-10-26 18:05:52 +09:00
aclist e3d62c6749 chore: bump version 2026-10-26 17:59:18 +09:00
aclist 5134bf02fb Merge pull request #503 from aclist/fix/flatpak-fixes
fix: flatpak fixes
2026-10-26 17:22:58 +09:00
aclist f97be9352d fix: typo 2026-10-26 17:21:10 +09:00
aclist b092a809f4 fix: capitalization 2026-10-26 17:20:28 +09:00
aclist 2cc7710807 fix: handle sandboxed mode 2026-10-26 17:20:14 +09:00
aclist 5fb10d9758 change: suppress missing shortcuts message 2026-10-26 17:19:56 +09:00
aclist 100cb43afc Merge pull request #502 from aclist/fix/check-granular-client
fix: check granular client
2026-10-26 16:15:47 +09:00
aclist 7e2a585812 chore: drop unused imports 2026-10-26 16:14:57 +09:00
aclist c1181435b3 chore: formatting 2026-10-26 16:13:56 +09:00
aclist 22a7814ce4 fix: check actual client command 2026-10-26 16:13:42 +09:00
aclist b030da5c65 Merge pull request #498 from aclist/fix/refresh-on-timeout
fix: refresh on timeout
2026-10-26 15:56:51 +09:00
aclist 3c0acdd068 Merge pull request #501 from aclist/fix/symbolic-svg
fix: symbolic svg
2026-10-26 15:56:16 +09:00
aclist 25ae2052e4 Merge branch 'dzgui7' into fix/symbolic-svg
Mirror to Codeberg / mirror-to-codeberg (push) Canceled after 0s
2026-10-26 15:55:37 +09:00
aclist 1b1777117f Merge pull request #497 from aclist/feat/dynamic-flatpak-toggle
feat: dynamic flatpak toggle
2026-10-26 15:51:18 +09:00
aclist 97ee2b7307 chore: update attribution 2026-10-23 13:07:38 +09:00
aclist ff6edb6181 fix: light mode symbolic icon 2026-10-23 13:05:20 +09:00
aclist 4c22263fb5 Merge branch 'dzgui7' of github.com:aclist/dztui into dzgui7 2026-10-23 13:04:09 +09:00
aclist 141f96fb9f fix: allow using refresh button after timeout events (#484) 2026-10-22 15:59:05 +09:00
21 changed files with 223 additions and 94 deletions

No files matched your search

+3 -1
View File
@@ -17,7 +17,7 @@ DZGUI follows a specific set of guidelines, enumerated below:
- Provide a free and open-source tool under GPL - Provide a free and open-source tool under GPL
- Do not use undocumented, third-party servers to aggregate data; no central "DZGUI" server - Do not use undocumented, third-party servers to aggregate data; no central "DZGUI" server
- All connections go to Steam and DayZ servers directly; the tool should function in perpetuity without intermediary endpoints - All connections go to Steam and DayZ servers directly; the tool should function in perpetuity without intermediary endpoints
- Target Linux desktop and Steam deck specifically; not a cross-platform tool - Target Linux desktop and Steam Deck specifically; not a cross-platform tool
## Setup and usage ## Setup and usage
@@ -35,6 +35,8 @@ Geolocation records from [DB-IP](https://db-ip.com) under [CC 4.0 license](https
Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in. Executable versions of DZGUI published as release binaries ship with various runtime dependencies and the Python interpreter built in.
Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball. Users wishing to review the licenses to these components can inspect the `LICENSE` file located in the release tarball.
Steam and the Steam logo are trademarks and/or registered trademarks of Valve Corporation in the U.S. and/or other countries.
## Disclaimer ## Disclaimer
DZGUI is beta-quality software and is provided as-is. DZGUI is beta-quality software and is provided as-is.
+1
View File
@@ -173,6 +173,7 @@ class Shortcuts:
self.save_shortcuts() self.save_shortcuts()
def save_shortcuts(self) -> None: def save_shortcuts(self) -> None:
if self.shortcuts_path.is_file():
try: try:
backup = self.shortcuts_path.with_suffix(".vdf.bak") backup = self.shortcuts_path.with_suffix(".vdf.bak")
shutil.copy(self.shortcuts_path, backup) shutil.copy(self.shortcuts_path, backup)
+3 -5
View File
@@ -13,7 +13,7 @@ from warnings import deprecated
from dzgui.api.acf import ACF from dzgui.api.acf import ACF
from dzgui.init.prereqs import has_any_client from dzgui.init.prereqs import has_any_client
from dzgui.init.proc import has_cmd from dzgui.init.prereqs import has_flatpak_client, has_steam_client
from dzgui.const.constants import ( from dzgui.const.constants import (
APPID_DAYZ, APPID_DAYZ,
APPID_DAYZ_EXP, APPID_DAYZ_EXP,
@@ -23,11 +23,9 @@ from dzgui.const.constants import (
DEBIAN_STEAM_PATH, DEBIAN_STEAM_PATH,
DEFAULT_STEAM_PATH, DEFAULT_STEAM_PATH,
DOWNLOADS_PATH, DOWNLOADS_PATH,
FLATPAK_CMD,
FLATPAK_STEAM_PATH, FLATPAK_STEAM_PATH,
LIBRARYFOLDERS_PATH, LIBRARYFOLDERS_PATH,
REQUEST_TIMEOUT, REQUEST_TIMEOUT,
STEAM_CMD,
UBUNTU_STEAM_PATH, UBUNTU_STEAM_PATH,
VDF_PATH, VDF_PATH,
) )
@@ -85,9 +83,9 @@ def get_steam_paths() -> list[tuple[Path, str]]:
} }
for path in DEFAULT_PATH, FLATPAK_PATH, UBUNTU_PATH, DEBIAN_PATH: for path in DEFAULT_PATH, FLATPAK_PATH, UBUNTU_PATH, DEBIAN_PATH:
if path is FLATPAK_PATH and has_cmd(FLATPAK_CMD) is False: if path is FLATPAK_PATH and has_flatpak_client() is False:
continue continue
if path is not FLATPAK_PATH and has_cmd(STEAM_CMD) is False: if path is not FLATPAK_PATH and has_steam_client() is False:
continue continue
if path.joinpath(VDF_PATH).is_file(): if path.joinpath(VDF_PATH).is_file():
paths.append((path, human[path])) paths.append((path, human[path]))
+11 -15
View File
@@ -2,16 +2,16 @@ import logging
import os import os
import shutil import shutil
from importlib import resources
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from warnings import deprecated
from dzgui.const.constants import ( from dzgui.const.constants import (
APP_NAME, APP_NAME,
APP_NAME_LOWER,
FLATPAK_ROOT_PATH, FLATPAK_ROOT_PATH,
STEAM_ICON, ICONS_PATH,
STEAM_ICONS_PATH,
) )
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.config.query import lookup from dzgui.config.query import lookup
@@ -31,7 +31,7 @@ from dzgui.util.dirs import make_parents
from dzgui.util.localize import set_locale from dzgui.util.localize import set_locale
from dzgui.util.map_count import test_map_count from dzgui.util.map_count import test_map_count
from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS
from dzgui.util.strings import init, steam_icon_missing from dzgui.util.strings import init
from dzgui.views.base import App from dzgui.views.base import App
from dzgui.views.dialogs.boot import BootWindow from dzgui.views.dialogs.boot import BootWindow
@@ -104,26 +104,21 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]:
return (config_changed, state_changed) return (config_changed, state_changed)
@deprecated("Currently unused") def load_steam_icon() -> None:
def load_steam_icon(config: Path) -> None:
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa from gi.repository import Gtk # noqa
default = lookup(config, Preferences.DEFAULT)
icon_path = Path(default).joinpath(STEAM_ICONS_PATH)
try: try:
traversable = resources.files(APP_NAME_LOWER).joinpath(ICONS_PATH)
icons = Path(str(traversable))
theme = Gtk.IconTheme.get_default() theme = Gtk.IconTheme.get_default()
paths = theme.get_search_path() paths = theme.get_search_path()
theme.set_search_path(paths + [str(icon_path)]) theme.set_search_path(paths + [str(icons)])
paths = theme.get_search_path()
icons = theme.list_icons(None)
if STEAM_ICON not in icons:
logger.warning(steam_icon_missing)
except Exception as e: except Exception as e:
print(e) logger.critical(e)
logger.warning(e)
def load_gui(version: str, is_debug: bool) -> None: def load_gui(version: str, is_debug: bool) -> None:
@@ -189,5 +184,6 @@ def load_gui(version: str, is_debug: bool) -> None:
use_miles=use_miles, use_miles=use_miles,
) )
print(boot.all_ok) print(boot.all_ok)
load_steam_icon()
app = App(prefs) app = App(prefs)
app.run(None) app.run(None)
+102 -3
View File
@@ -3,6 +3,9 @@ import logging
import re import re
import requests import requests
import shutil import shutil
import socket
import sqlite3
import time
from pathlib import Path from pathlib import Path
@@ -22,15 +25,16 @@ def find_download_url(url: str) -> str | None:
return str(urls[0]) return str(urls[0])
def get_ipdb(ips_path: Path) -> None: def get_ipdb(db_path: Path) -> None:
try: try:
url = find_download_url(DB_IP) url = find_download_url(DB_IP)
if url is None: if url is None:
return return
ips_path = db_path.with_suffix(".csv")
month_file = db_path.parent / ".month"
date = find_date(url) date = find_date(url)
month_file = ips_path.parent / ".month" if db_path.exists() and month_file.exists():
if ips_path.exists() and month_file.exists():
old_date = month_file.read_text().rstrip("\n") old_date = month_file.read_text().rstrip("\n")
if old_date == date: if old_date == date:
logger.info(f"IP DB date matches: {date}") logger.info(f"IP DB date matches: {date}")
@@ -45,6 +49,9 @@ def get_ipdb(ips_path: Path) -> None:
logger.info("Stripping IPv6 records") logger.info("Stripping IPv6 records")
strip_ipv6(ips_path) strip_ipv6(ips_path)
create_table(db_path, ips_path)
ips_path.unlink()
with open(month_file, "w") as f: with open(month_file, "w") as f:
f.write(date) f.write(date)
logger.info(f"Wrote {date} to {month_file}") logger.info(f"Wrote {date} to {month_file}")
@@ -108,3 +115,95 @@ def strip_ipv6(path: Path) -> None:
out.write(merged) out.write(merged)
path.unlink() path.unlink()
alt_path.rename(path) alt_path.rename(path)
def ip_to_int(ip: str) -> int:
return int.from_bytes(socket.inet_aton(ip), "big")
def marshal_rows(ip_file: Path) -> list[tuple[int, int, str, str]]:
YIELD = 0.003
SLEEP = 0.001
with open(ip_file, "r") as f:
rows: list[tuple[int, int, str, str]] = []
# NOTE: yield to GIL periodically to reduce load on spinner
last_yield = time.monotonic()
for row in f:
start, end, lat, lon = row.rstrip("\n").split(",")
int_start = ip_to_int(start)
int_end = ip_to_int(end)
rows.append((int_start, int_end, lat, lon))
now = time.monotonic()
if (now - last_yield) >= YIELD:
time.sleep(SLEEP)
last_yield = time.monotonic()
return rows
def create_table(db_file: Path, ip_file: Path) -> None:
create = """
CREATE TABLE IF NOT EXISTS ips (
start_range INTEGER PRIMARY KEY,
end_range INTEGER,
latitude REAL,
longitude REAL
) WITHOUT ROWID;
"""
insert = """
INSERT INTO ips
(start_range, end_range, latitude, longitude)
values(?, ?, ?, ?)
"""
if db_file.exists():
db_file.unlink()
conn = sqlite3.connect(":memory:")
c = conn.cursor()
try:
c.execute(create)
rows = marshal_rows(ip_file)
c.executemany(insert, rows)
logger.info(f"Inserted {len(rows)} IP records into database")
conn.commit()
with sqlite3.connect(db_file) as dest:
conn.backup(dest)
logger.info(f"Serialized IP records to database: '{db_file}'")
conn.close()
except Exception as e:
logger.critical(e)
def read_from_table(db_path: Path, ip: str) -> tuple[float, float]:
if not db_path.exists():
raise FileNotFoundError(f"No file found at the location '{db_path}")
conn = sqlite3.connect(db_path)
c = conn.cursor()
int_ip = ip_to_int(ip)
query = """
SELECT end_range, latitude, longitude
FROM ips
WHERE start_range <= ?
ORDER BY start_range DESC
LIMIT 1
"""
index = """
CREATE INDEX IF NOT EXISTS start_index
ON ips(start_range);
"""
try:
c.execute(index)
conn.commit()
c.execute(query, (int_ip,))
row = c.fetchone()
end, lat, lon = row
conn.close()
return lat, lon
except Exception as e:
logger.critical(e)
raise e
+3 -3
View File
@@ -22,7 +22,7 @@ class Xdg:
version: Path version: Path
system: Path system: Path
debug: Path debug: Path
ips: Path db: Path
filters: Path filters: Path
shortcut: Path shortcut: Path
@@ -88,7 +88,7 @@ def parse_filepaths(xdg: dict) -> Xdg:
resolution = state / "dzg.res.json" resolution = state / "dzg.res.json"
history = state / "dzg.history" history = state / "dzg.history"
versions = state / "dzg.versions" versions = state / "dzg.versions"
ips = state / "ips.csv" db = state / "ips.db"
filters = state / "dzg.filters.json" filters = state / "dzg.filters.json"
shortcut = share / APP_NAME_LOWER shortcut = share / APP_NAME_LOWER
@@ -102,7 +102,7 @@ def parse_filepaths(xdg: dict) -> Xdg:
versions, versions,
system, system,
debug, debug,
ips, db,
filters, filters,
shortcut, shortcut,
) )
+3 -2
View File
@@ -51,7 +51,7 @@ INPUT_KEYBOARD = "input-keyboard-symbolic"
LIST_ADD = "list-add-symbolic" LIST_ADD = "list-add-symbolic"
REFRESH_ICON = "view-refresh-symbolic" REFRESH_ICON = "view-refresh-symbolic"
SEARCH_ICON = "system-search-symbolic" SEARCH_ICON = "system-search-symbolic"
STEAM_ICON = "steam_tray_mono" STEAM_ICON = "steam-tray-symbolic"
VIEW_CONCEAL = "view-conceal-symbolic" VIEW_CONCEAL = "view-conceal-symbolic"
VIEW_REVEAL = "view-reveal-symbolic" VIEW_REVEAL = "view-reveal-symbolic"
WARNING = "dialog-warning-symbolic" WARNING = "dialog-warning-symbolic"
@@ -80,6 +80,7 @@ DEBUG_LOG = f"{APP_NAME}_DEBUG.LOG"
SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG" SYSTEM_LOG = f"{APP_NAME}_SYSTEM.LOG"
CHANGELOG_PATH = "data/CHANGELOG.md" CHANGELOG_PATH = "data/CHANGELOG.md"
IMAGES_PATH = "data/images" IMAGES_PATH = "data/images"
ICONS_PATH = "data/images/gtk"
HERO_PATH = "data/images/_hero.png" HERO_PATH = "data/images/_hero.png"
CSS_PATH = "data/app.css" CSS_PATH = "data/app.css"
@@ -89,13 +90,13 @@ FLATPAK_ROOT_PATH = ".var/app/com.valvesoftware.Steam"
FLATPAK_STEAM_PATH = FLATPAK_ROOT_PATH + "/data/Steam" FLATPAK_STEAM_PATH = FLATPAK_ROOT_PATH + "/data/Steam"
UBUNTU_STEAM_PATH = ".steam/steam" UBUNTU_STEAM_PATH = ".steam/steam"
DEBIAN_STEAM_PATH = ".steam/debian-installation" DEBIAN_STEAM_PATH = ".steam/debian-installation"
STEAM_ICONS_PATH = "public"
LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG") LOG_FILTERS = ("CRITICAL", "WARNING", "INFO", "DEBUG")
TMP_PATH = "/tmp" TMP_PATH = "/tmp"
TMP_TARBALL = "/tmp/dzgui.tar.gz" TMP_TARBALL = "/tmp/dzgui.tar.gz"
TMP_EXE = "/tmp/dzgui/dzgui" TMP_EXE = "/tmp/dzgui/dzgui"
LOCK_FILE = "/tmp/dzgui.flock"
DAYZ_COMMUNITY_ROOT = "Missions" DAYZ_COMMUNITY_ROOT = "Missions"
+4
View File
@@ -111,6 +111,10 @@ class Emitter(GObject.GObject):
def check_toggled(self, label: str, state: bool) -> None: def check_toggled(self, label: str, state: bool) -> None:
pass pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def servers_timed_out(self) -> None:
pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,)) @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(object,))
def servers_loaded(self, tag: "ServerTab") -> None: def servers_loaded(self, tag: "ServerTab") -> None:
pass pass
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.3.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M568 320C568 457 456.8 568 319.6 568C205.8 568 110 491.7 80.6 387.6L175.8 426.9C182.2 459 210.7 483.3 244.7 483.3C283.9 483.3 316.6 450.9 314.9 409.8L399.4 349.6C451.5 350.9 495.2 308.7 495.2 256.1C495.2 204.5 453.2 162.6 401.5 162.6C349.8 162.6 307.8 204.6 307.8 256.1L307.8 257.3L248.6 343C233.1 342.1 217.9 346.4 205.1 355.1L72 300.1C82.2 172.4 189.1 72 319.6 72C456.8 72 568 183 568 320zM227.7 448.3L197.2 435.7C202.8 447.3 212.5 456.5 224.4 461.5C251.3 472.7 282.2 459.9 293.4 433.1C298.8 420.1 298.9 405.8 293.5 392.8C288.1 379.8 278 369.6 265 364.2C252.1 358.8 238.3 359 226.1 363.6L257.6 376.6C277.4 384.8 286.8 407.5 278.5 427.3C270.2 447.2 247.5 456.5 227.7 448.3zM401.5 193.8C435.9 193.8 463.8 221.7 463.8 256.1C463.8 290.5 435.9 318.4 401.5 318.4C367.1 318.4 339.2 290.5 339.2 256.1C339.2 221.7 367.1 193.8 401.5 193.8zM401.6 302.8C427.4 302.8 448.4 281.8 448.4 256C448.4 230.2 427.4 209.2 401.6 209.2C375.8 209.2 354.8 230.2 354.8 256C354.8 281.8 375.8 302.8 401.6 302.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+2 -2
View File
@@ -10,10 +10,10 @@ if TYPE_CHECKING:
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
def get_local_coords(path: "Path") -> Coords | None: def get_local_coords(db_path: "Path") -> Coords | None:
try: try:
my_ip = get_local_ip() my_ip = get_local_ip()
return get_coords(path, my_ip) return get_coords(db_path, my_ip)
except Exception as e: except Exception as e:
logger.warn(e) logger.warn(e)
return None return None
+3 -2
View File
@@ -5,8 +5,9 @@ import sys
from pathlib import Path from pathlib import Path
from typing import TextIO from typing import TextIO
from dzgui.views.dialogs.early_alert import EarlyAlertDialog from dzgui.const.constants import LOCK_FILE
from dzgui.util.strings import cannot_acquire_lock from dzgui.util.strings import cannot_acquire_lock
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
def lock_release(lock: TextIO) -> None: def lock_release(lock: TextIO) -> None:
@@ -15,7 +16,7 @@ def lock_release(lock: TextIO) -> None:
def lock_acquire() -> TextIO: def lock_acquire() -> TextIO:
lockfile = Path("/tmp/dzgui.flock") lockfile = Path(LOCK_FILE)
lock = open(lockfile, "w") lock = open(lockfile, "w")
try: try:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
+3
View File
@@ -3,11 +3,14 @@ import shutil
import subprocess import subprocess
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CMD from dzgui.const.constants import APP_NAME, FLATPAK_APPID, FLATPAK_CMD, STEAM_CMD
from dzgui.init.proc import is_sandboxed
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
def has_flatpak_client() -> bool: def has_flatpak_client() -> bool:
if is_sandboxed():
return True
try: try:
res = subprocess.run( res = subprocess.run(
[FLATPAK_CMD, "list", "--columns=application"], [FLATPAK_CMD, "list", "--columns=application"],
+3
View File
@@ -92,6 +92,7 @@ class ServerModelManager:
job = Servers.query_api job = Servers.query_api
params = Servers.params params = Servers.params
servers = [] servers = []
with ThreadPoolExecutor() as executor: with ThreadPoolExecutor() as executor:
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params] futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
for future in as_completed(futures): for future in as_completed(futures):
@@ -414,6 +415,8 @@ class ServerModelManager:
# TODO: disable map, keyword, and filter widgets if model is None # TODO: disable map, keyword, and filter widgets if model is None
# signal driven (servers_empty, servers_failed_to_load) # signal driven (servers_empty, servers_failed_to_load)
self.emitter.emit("servers_timed_out")
if self.preserve_on_fail is False: if self.preserve_on_fail is False:
self.tv.set_model(None) self.tv.set_model(None)
filter_man = self.tv.get_filter_man() filter_man = self.tv.get_filter_man()
+3 -2
View File
@@ -80,8 +80,9 @@ class CalcDist:
if local is None: if local is None:
return None return None
try: try:
remote = get_coords(prefs.paths.ips, remote_ip) remote = get_coords(prefs.paths.db, remote_ip)
except Exception: except Exception as e:
logger.critical(e)
return None return None
haversine = Haversine(local.lat, local.lon, remote.lat, remote.lon) haversine = Haversine(local.lat, local.lon, remote.lat, remote.lon)
+6 -2
View File
@@ -1,7 +1,7 @@
import logging import logging
import subprocess import subprocess
from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID from dzgui.const.constants import APP_NAME, FLATPAK_APPID, GTK_APP_ID, TMP_PATH
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
@@ -18,7 +18,11 @@ def apply_overrides(xdg: dict[str, str]) -> None:
] ]
subprocess.run([*args], capture_output=True) subprocess.run([*args], capture_output=True)
for path in xdg.values(): paths: list[str] = []
paths.extend([*xdg.values()])
paths.append(TMP_PATH)
for path in paths:
args = [ args = [
"flatpak", "flatpak",
"--user", "--user",
+12 -43
View File
@@ -1,4 +1,5 @@
import a2s import a2s
from ipaddress import ip_address
import requests import requests
import shutil import shutil
import subprocess import subprocess
@@ -7,6 +8,7 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from dzgui.api.servers import Record from dzgui.api.servers import Record
from dzgui.config.ipdb import read_from_table
from dzgui.const.constants import REQUEST_TIMEOUT from dzgui.const.constants import REQUEST_TIMEOUT
from dzgui.const.endpoints import IP_ECHO from dzgui.const.endpoints import IP_ECHO
@@ -33,53 +35,20 @@ def ip2list(ip: str) -> list:
return [int(digit) for digit in ip.split(".")] return [int(digit) for digit in ip.split(".")]
# TODO: tests def is_usable(ip: str) -> bool:
def is_reserved(digits: list) -> bool: address = ip_address(ip)
reserved = [0, 10, 127] if address.is_reserved or address.is_private:
if digits[0] in reserved:
return True
if digits[0] == 192 and digits[1] == 168:
return True
return False return False
return True
def get_coords(ips: "Path", ip: str) -> Coords: def get_coords(db_path: "Path", ip: str) -> Coords:
split = ip2list(ip) if not is_usable(ip):
if is_reserved(split):
raise ReservedIPError(f"{ip} is a reserved IP address.") raise ReservedIPError(f"{ip} is a reserved IP address.")
try:
prefix = f"^{split[0]}.{split[1]}." lat, lon = read_from_table(db_path, ip)
return Coords(lat, lon)
if shutil.which("rg") is not None: except Exception:
args = ["/usr/bin/rg", prefix, str(ips)]
else:
args = ["/usr/bin/grep", "-E", prefix, str(ips)]
proc = subprocess.run(args, capture_output=True, text=True)
if proc.returncode != 0:
raise GeolocationError("Failed to split records")
ip_list = proc.stdout.splitlines()
for address in ip_list:
fields = address.split(",")
upper = fields[1]
upper_digits = ip2list(upper)
if ip == upper:
return Coords(float(fields[-2]), float(fields[-1]))
if split[2] > upper_digits[2]:
continue
if split[2] == upper_digits[2]:
if split[3] > upper_digits[3]:
continue
if split[2] <= upper_digits[2]:
return Coords(float(fields[-2]), float(fields[-1]))
raise GeolocationError("No matching records found") raise GeolocationError("No matching records found")
+10 -4
View File
@@ -163,18 +163,24 @@ class RefreshButton(IconTextButton):
self.set_tooltip_text(atomic_buttons.refresh_tooltip) self.set_tooltip_text(atomic_buttons.refresh_tooltip)
self.connect("clicked", self._on_refresh_clicked) self.connect("clicked", self._on_refresh_clicked)
emitter.connect("servers_loaded", self.start_decrement) emitter.connect("servers_loaded", self._on_load)
emitter.connect("servers_timed_out", self._on_timeout)
def _on_timeout(self, emitter: "Emitter") -> None:
self.is_clicked = True
self.start_decrement()
def _on_load(self, emitter: "Emitter", tab: "ServerTab") -> None:
self.start_decrement()
def _on_refresh_clicked(self, button: Self) -> None: def _on_refresh_clicked(self, button: Self) -> None:
"""Spawned in a thread""" """Spawned in a thread"""
if self.controller.get_active_treeview().is_loaded() is False:
return
self.is_clicked = True self.is_clicked = True
self.controller.refresh_tree() self.controller.refresh_tree()
# TODO: get server tab enum # TODO: get server tab enum
# if LAN tab, reload existing entries in place # if LAN tab, reload existing entries in place
def start_decrement(self, emitter: "Emitter", tab: "ServerTab") -> None: def start_decrement(self) -> None:
if self.is_clicked is False: if self.is_clicked is False:
return return
if self.loading: if self.loading:
+2 -2
View File
@@ -132,9 +132,9 @@ class BootDialog(ColorAwareApp, Gtk.Dialog): # type: ignore
preboot.signatures, preboot.signatures,
False, False,
), ),
(StoredFunc(get_ipdb, self.xdg.ips), preboot.geo, False), (StoredFunc(get_ipdb, self.xdg.db), preboot.geo, False),
# TODO: handle IP DB failure and use coords fallback # TODO: handle IP DB failure and use coords fallback
(StoredFunc(get_local_coords, self.xdg.ips), preboot.coords, True), (StoredFunc(get_local_coords, self.xdg.db), preboot.coords, True),
(StoredFunc(check_updates, self.version), preboot.updates, True), (StoredFunc(check_updates, self.version), preboot.updates, True),
] ]
self.results: list[Any] = [] self.results: list[Any] = []
+3
View File
@@ -14,6 +14,7 @@ from dzgui.const.constants import (
HERO_PATH, HERO_PATH,
FLATPAK_ROOT_PATH, FLATPAK_ROOT_PATH,
LEGACY_CONFIG_PATH, LEGACY_CONFIG_PATH,
TMP_PATH
) )
from dzgui.const.boilerplate import config_boilerplate from dzgui.const.boilerplate import config_boilerplate
from dzgui.const.endpoints import STEAM_API_SETUP from dzgui.const.endpoints import STEAM_API_SETUP
@@ -432,6 +433,8 @@ class FlatpakOverridePage(EnumeratedWizardPage):
store = Gtk.ListStore(str) store = Gtk.ListStore(str)
for path in xdg_paths.values(): for path in xdg_paths.values():
store.append((str(path),)) store.append((str(path),))
store.append((TMP_PATH,))
tree = SimpleTree(store, wizard.tree_column) tree = SimpleTree(store, wizard.tree_column)
tree.set_halign(Gtk.Align.CENTER) tree.set_halign(Gtk.Align.CENTER)
+1 -1
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [ authors = [
{name = "aclist"} {name = "aclist"}
] ]
version = "7.0.0b28" version = "7.0.0b29"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"
+44 -7
View File
@@ -1,16 +1,40 @@
from dzgui.config.ipdb import get_ipdb
from pathlib import Path
import os import os
import sqlite3
import pytest import pytest
import tempfile import tempfile
from pathlib import Path
from dzgui.const.endpoints import DB_IP
from dzgui.config.ipdb import (
create_table,
find_download_url,
serialize,
strip_ipv6,
unzip,
)
def get_db_len(path: Path) -> int:
conn = sqlite3.connect(path)
c = conn.cursor()
c.execute("SELECT COUNT(*) from ips")
db_len = c.fetchone()[0]
return db_len
@pytest.fixture
def csv() -> Path:
csv = Path(tempfile.NamedTemporaryFile().name)
url = find_download_url(DB_IP)
tmp = serialize(url)
unzip(tmp, csv)
strip_ipv6(csv)
return csv
@pytest.mark.slow @pytest.mark.slow
def test_stripping(): def test_stripping(csv):
d = tempfile.TemporaryDirectory() with open(csv, "rb") as f:
path = Path(d.name).joinpath("ips.csv")
get_ipdb(path)
with open(path, "rb") as f:
first = f.readline().decode().rstrip() first = f.readline().decode().rstrip()
try: try:
f.seek(-2, os.SEEK_END) f.seek(-2, os.SEEK_END)
@@ -25,3 +49,16 @@ def test_stripping():
assert first_els[0] == "0.0.0.0" assert first_els[0] == "0.0.0.0"
assert last_els[0] == "224.0.0.0" assert last_els[0] == "224.0.0.0"
@pytest.mark.slow
def test_row_parity(csv) -> None:
rows = csv.read_text().splitlines()
db = Path(tempfile.NamedTemporaryFile().name)
create_table(db, csv)
csv_len = len(rows)
db_len = get_db_len(db)
assert csv_len == db_len