From 4face6f11517f02e3775729908a90d85cae43270 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 03:59:57 +0900 Subject: [PATCH 01/18] feat: preboot dialog (WIP) --- dzgui/const/constants.py | 1 + dzgui/main.py | 37 ++--- dzgui/managers/threading.py | 4 +- dzgui/views/dialogs/boot.py | 282 ++++++++++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+), 20 deletions(-) create mode 100644 dzgui/views/dialogs/boot.py diff --git a/dzgui/const/constants.py b/dzgui/const/constants.py index 6818fef..7620bc8 100644 --- a/dzgui/const/constants.py +++ b/dzgui/const/constants.py @@ -28,6 +28,7 @@ APP_NAME = "DZGUI" APP_NAME_LOWER = "dzgui" APP_NAME_ABBR = "dzg" +HEX_GREEN = "#32CD32" HEX_RED = "#FF0000" HEX_ORANGE = "#FFAC1C" diff --git a/dzgui/main.py b/dzgui/main.py index c5d74d4..59e19b9 100644 --- a/dzgui/main.py +++ b/dzgui/main.py @@ -6,14 +6,11 @@ import warnings from typing import TYPE_CHECKING -from dzgui.api.mods import remove_stale_signatures from dzgui.const.constants import APP_NAME from dzgui.const.enum import Preferences -from dzgui.config.ipdb import get_ipdb from dzgui.config.query import lookup from dzgui.config.userprefs import UserPrefs from dzgui.config.xdg import get_xdg_paths, parse_filepaths -from dzgui.init.coords import get_local_coords from dzgui.init.dayz import is_dayz_installed from dzgui.init.flock import lock_acquire from dzgui.init.migrate import ( @@ -24,17 +21,16 @@ from dzgui.init.migrate import ( from dzgui.init.prefix import get_version from dzgui.init.prereqs import has_steam_client from dzgui.strings import boot -from dzgui.init.update import check_updates # from dzgui.util.map_count import get_map_count from dzgui.util.deck import is_steam_deck, is_game_mode from dzgui.util.localize import set_locale -from dzgui.util.symlink import rebuild_symlinks from dzgui.util.strings import init, flags from dzgui.views.base import App -from dzgui.views.dialogs.wizard import SetupWizard +from dzgui.views.dialogs.boot import BootWindow from dzgui.views.dialogs.early_alert import EarlyAlertDialog +from dzgui.views.dialogs.wizard import SetupWizard if TYPE_CHECKING: from pathlib import Path @@ -47,6 +43,11 @@ parser.add_argument("-u", "--uninstall", action="store_true", help=flags.uninsta parser.add_argument("-d", "--debug", action="store_true", help=flags.debug) args = parser.parse_args() +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib, Gdk # noqa + # TODO: profile load time def make_parents(path: "Path") -> None: @@ -130,20 +131,20 @@ def main() -> None: is_dayz_installed(XDG.config) - # TODO: slow running procs here--needs dialog - # NOTE: clears versions file of unlinked mods - rebuild_symlinks(XDG.config) - remove_stale_signatures(XDG.config, XDG.version) + bootwin = BootWindow(XDG, version) + local_coords, latest_release = bootwin.get_results() + + # rebuild_symlinks(XDG.config) + # remove_stale_signatures(XDG.config, XDG.version) + + ## TODO: handle IP DB failure and use coords fallback + ## TODO: drop this after dialog is complete + # print("Fetching geolocation data, may take some time...") + # get_ipdb(XDG.ips) + # local_coords = get_local_coords(XDG.ips) + # latest_release = check_updates(version) - # TODO: handle IP DB failure and use coords fallback - # TODO: drop this after dialog is complete - print("Fetching geolocation data, may take some time...") - get_ipdb(XDG.ips) - local_coords = get_local_coords(XDG.ips) use_miles = lookup(XDG.config, Preferences.DIST) - - latest_release = check_updates(version) - prefs = UserPrefs( is_steam_deck=_is_steam_deck, is_game_mode=_is_game_mode, diff --git a/dzgui/managers/threading.py b/dzgui/managers/threading.py index 91db2e9..d972487 100644 --- a/dzgui/managers/threading.py +++ b/dzgui/managers/threading.py @@ -48,7 +48,7 @@ class StoredFunc: self.bindings = sig.bind(*args, **kwargs) def call(self) -> None: - self.func(*self.bindings.args, *self.bindings.kwargs) + return self.func(*self.bindings.args, *self.bindings.kwargs) class ThreadingManager: @@ -113,8 +113,8 @@ class ThreadingManager: func = self.get_cleanup_func() if func is not None: - func.call() self.set_cleanup_func(None) + func.call() if not self.destroy_first: self.destroy_dialog() diff --git a/dzgui/views/dialogs/boot.py b/dzgui/views/dialogs/boot.py new file mode 100644 index 0000000..39b5142 --- /dev/null +++ b/dzgui/views/dialogs/boot.py @@ -0,0 +1,282 @@ +import sys +import time +from enum import Enum +from typing import Any, Self, TYPE_CHECKING + +# TODO: import dialog titles +from dzgui.api.mods import remove_stale_signatures +from dzgui.config.ipdb import get_ipdb +from dzgui.const.constants import HEX_GREEN, HEX_RED +from dzgui.init.coords import get_local_coords +from dzgui.init.update import check_updates +from dzgui.const.constants import EXPAND, FILL +from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager +from dzgui.util.symlink import rebuild_symlinks +from dzgui.views.components.buttons import ClipboardButton + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib, Gdk # noqa + +if TYPE_CHECKING: + from dzgui.config.xdg import Xdg + + +class Success(Enum): + OK = 1 + FAIL = 2 + + +# TODO: add margins to tree +class BootDialog(Gtk.Dialog): + def __init__(self, parent: Gtk.Widget, xdg: "Xdg", version: str) -> None: + super().__init__( + # text="TEST", + # buttons=Gtk.ButtonsType.NONE, + # message_type=Gtk.MessageType.INFO, + # secondary_text="BOOTING UP", + # TODO: strings + title="TEST TITLE", + parent=parent, + flags=Gtk.DialogFlags.MODAL, + ) + + self.parent = parent + self.set_modal(True) + self.xdg = xdg + self.version = version + + self.thread_man = ThreadingManager(None) + self.set_size_request(700, 500) + + self.store = Gtk.ListStore(str, str, bool, int) + + self.view = Gtk.TreeView(enable_search=False, headers_visible=False) + self.view.set_model(self.store) + # self.view.set_fixed_height_mode(True) + + for i, column_title in enumerate(["Task", "State"]): # , "Spinner"]): + renderer = Gtk.CellRendererText() + column = Gtk.TreeViewColumn(column_title, renderer, text=i) + column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) + column.set_resizable(False) + # column.set_expand(True) + column.set_sort_column_id(i) + column.set_cell_data_func(renderer, self._format_color, func_data=None) + self.view.append_column(column) + if i == 0: + column.set_fixed_width(300) + + self.spinner_renderer = Gtk.CellRendererSpinner() + self.spinner_renderer.set_property("size", Gtk.IconSize.LARGE_TOOLBAR) + col_bool = Gtk.TreeViewColumn("Spinner", self.spinner_renderer, active=3) + col_bool.set_alignment(0.0) + col_bool.set_cell_data_func( + self.spinner_renderer, self._set_spinner_vis, func_data=None + ) + self.view.append_column(col_bool) + + col_int = Gtk.TreeViewColumn("Int", Gtk.CellRendererText(), text=3) + col_int.set_visible(False) + self.view.append_column(col_int) + + self.view.get_selection().set_mode(Gtk.SelectionMode.NONE) + self.connect("response", self._on_response) + self.connect("delete-event", self._on_delete) + + self.scrollable_tree = Gtk.ScrolledWindow(overlay_scrolling=False) + self.scrollable_tree.add(self.view) + self.scrollable_tree.set_size_request(700, 400) + + # TODO: pack spinner into dedicated box + self.loading_label = Gtk.Label(label="Loading") + self.spinner = Gtk.Spinner() + self.spinner.start() + + self.error_box = Gtk.Box( + halign=Gtk.Align.CENTER, + orientation=Gtk.Orientation.VERTICAL, + spacing=20, + margin_top=30, + margin_bottom=30, + ) + self.error_label = Gtk.Label() + + # TODO: recycle ClipboardButton from generics + # TODO: abstract class + # self.copy_button = ClipboardButton() + self.copy_button = Gtk.Button(label="Copy") + self.button_hbox = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.CENTER, spacing=10 + ) + self.exit_button = Gtk.Button(label="Exit", halign=Gtk.Align.CENTER) + self.exit_button.connect("clicked", lambda _: sys.exit(1)) + self.button_hbox.add(self.copy_button) + self.button_hbox.add(self.exit_button) + + self.error_box.add(self.error_label) + self.error_box.add(self.button_hbox) + + self.content = self.get_content_area() + self.content.pack_start(self.scrollable_tree, EXPAND, FILL, 0) + self.content.pack_start(self.error_box, EXPAND, FILL, 0) + # self.content.pack_start(self.loading_label, EXPAND, FILL, 0) + # self.content.pack_start(self.spinner, EXPAND, FILL, 0) + self.show_all() + + self.error_box.hide() + + symlinks = "Rebuilding symlinks" + signatures = "Updating mod signatures" + geo = "Checking geolocation records" + coords = "Checking local coordinates" + updates = "Checking for updates" + + steps = [ + (StoredFunc(rebuild_symlinks, self.xdg.config), symlinks, False), + ( + StoredFunc(remove_stale_signatures, self.xdg.config, self.xdg.version), + signatures, + False, + ), + (StoredFunc(get_ipdb, self.xdg.ips), geo, False), + (StoredFunc(get_local_coords, self.xdg.ips), coords, True), + (StoredFunc(check_updates, self.version), updates, True), + # (StoredFunc(time.sleep, 5.1), "Sleeping", True), + # (StoredFunc(time.sleep, 0.1), "Sleeping", True), + # (StoredFunc(time.sleep, 0.1), "Sleeping", False), + # (StoredFunc(lambda: 1 / 0), "Broken function", False), + ] + self.results: list[Any] = [] + self.failed = False + self.steps = iter(steps) + + def pulse_spinner(self) -> None: + for row in self.store: + if row[2]: + if row[3] == 150: + row[3] = 0 + else: + row[3] += 1 + # if self.pulse == 100: + # self.pulse = 0 + # else: + # self.pulse += 1 + self.spinner_renderer.set_property("pulse", row[3]) + return True + + def run(self) -> None: + GLib.timeout_add(100, self.pulse_spinner) + self.iter_step() + + def _on_delete(self, widget: Self, event: Gdk.Event) -> None: + return True + + def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None: + print("caught response") + if response == Gtk.ResponseType.DELETE_EVENT: + return True + if response == Gtk.ResponseType.CLOSE: + return True + + def iter_step(self) -> None: + try: + step, label, store_output = next(self.steps) + self.update_task(label) + self.background(step, store_output) + except StopIteration: + if self.failed: + # TODO: abstract into method + self.error_box.show() + self.error_label.set_text(self.exception) + self.spinner.stop() + self.loading_label.hide() + else: + self.parent.set_results(self.results) + self.destroy() + + @call_on_thread("", show_dialog=False) + def background(self, func: StoredFunc, store_output: bool) -> None: + # time.sleep(0.1) + try: + if store_output: + res = func.call() + self.results.append(res) + else: + func.call() + callback = StoredFunc(self.update_status, Success.OK) + self.thread_man.set_cleanup_func(callback) + except Exception as e: + self.failed = True + # TODO: generic exception wrapper + self.exception = f"{type(e).__name__}: {e}" + callback = StoredFunc(self.update_status, Success.FAIL) + self.thread_man.set_cleanup_func(callback) + + def get_results(self) -> list[Any]: + return self.results + + def update_task(self, task: str) -> None: + self.store.append((task, "Running", True, 0)) + + # TODO: use enum + def update_status(self, state: Success) -> None: + d = {Success.OK: "OK", Success.FAIL: "FAILED"} + msg = d[state] + model = self.view.get_model() + _iter = model.get_iter(len(model) - 1) + model.set_value(_iter, 1, msg) + self.iter_step() + + def _set_spinner_vis( + self, + column: Gtk.TreeViewColumn, + cell: Gtk.CellRendererSpinner, + model: Gtk.TreeModel, + it: Gtk.TreeIter, + data: Any, + ) -> None: + if model[it][1] == "Running": + cell.set_property("visible", True) + else: + cell.set_property("visible", False) + + def _format_color( + self, + column: Gtk.TreeViewColumn, + cell: Gtk.CellRendererText, + model: Gtk.TreeModel, + it: Gtk.TreeIter, + data: Any, + ) -> None: + state = model[it][1] + if column.get_sort_column_id() != 1: + return + if state == "OK": + cell.set_property("foreground", HEX_GREEN) + elif state == "FAILED": + cell.set_property("foreground", HEX_RED) + else: + cell.set_property("foreground", None) + + +class BootWindow(Gtk.Window): + def __init__(self, xdg: "Xdg", version: str) -> None: + super().__init__() + + self.results: list[Any] = [] + + dialog = BootDialog(self, xdg, version) + dialog.run() + dialog.connect("destroy", self._on_destroy) + Gtk.main() + + def set_results(self, res: list[Any]) -> None: + self.results = res + + def get_results(self) -> str: + return self.results + + def _on_destroy(self, widget) -> None: + Gtk.main_quit() From d1cf2a98501d6c6942fb92abc096200166910449 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:00:15 +0900 Subject: [PATCH 02/18] chore: clear typehinting errors --- dzgui/views/trees/tree_mods.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dzgui/views/trees/tree_mods.py b/dzgui/views/trees/tree_mods.py index 130708f..e8340d7 100644 --- a/dzgui/views/trees/tree_mods.py +++ b/dzgui/views/trees/tree_mods.py @@ -88,13 +88,12 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore model: Gtk.TreeModel, it: Gtk.TreeIter, data: Any, - ) -> Any: + ) -> None: state = model[it][4] if state is True: cell.set_property("foreground", HEX_RED) else: cell.set_property("foreground", None) - return def _format_float( self, @@ -103,8 +102,7 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): # type: ignore model: Gtk.TreeModel, it: Gtk.TreeIter, data: Any, - ) -> Any: + ) -> None: val = model[it][3] formatted = localize.number(val) cell.set_property("text", formatted) - return From fe40346a016ab7354c53d591275679d74faae8fe Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:00:49 +0900 Subject: [PATCH 03/18] chore: update user on tarfile --- scripts/release.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/release.py b/scripts/release.py index c80315e..2863dcc 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -51,7 +51,11 @@ if proc.returncode == 0: output_exe.rename(release_exe) tarpath = output.joinpath(tarname) with tarfile.open(tarpath, "w:gz") as tar: - tar.add(release_exe, arcname=appname) + info = tar.gettarinfo(release_exe) + info.uname = appname + info.name = appname + with open(release_exe, "rb") as f: + tar.addfile(info, f) print(f"Wrote tarfile to '{tarpath}'") proc = subprocess.run([release_exe, "-v"], capture_output=True, text=True) From ac9f582def0b102446f441fd33e96712f99b3cbe Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:21:55 +0900 Subject: [PATCH 04/18] chore: clear typehinting errors --- dzgui/main.py | 5 ---- dzgui/managers/threading.py | 2 +- dzgui/views/dialogs/boot.py | 48 +++++++++++++++---------------------- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/dzgui/main.py b/dzgui/main.py index 59e19b9..4d6952e 100644 --- a/dzgui/main.py +++ b/dzgui/main.py @@ -43,11 +43,6 @@ parser.add_argument("-u", "--uninstall", action="store_true", help=flags.uninsta parser.add_argument("-d", "--debug", action="store_true", help=flags.debug) args = parser.parse_args() -import gi - -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk, GLib, Gdk # noqa - # TODO: profile load time def make_parents(path: "Path") -> None: diff --git a/dzgui/managers/threading.py b/dzgui/managers/threading.py index d972487..adecb7a 100644 --- a/dzgui/managers/threading.py +++ b/dzgui/managers/threading.py @@ -47,7 +47,7 @@ class StoredFunc: self.func = func self.bindings = sig.bind(*args, **kwargs) - def call(self) -> None: + def call(self) -> Any: return self.func(*self.bindings.args, *self.bindings.kwargs) diff --git a/dzgui/views/dialogs/boot.py b/dzgui/views/dialogs/boot.py index 39b5142..74e4aff 100644 --- a/dzgui/views/dialogs/boot.py +++ b/dzgui/views/dialogs/boot.py @@ -1,7 +1,9 @@ import sys -import time +from typing import Literal, TYPE_CHECKING + +# import time from enum import Enum -from typing import Any, Self, TYPE_CHECKING +from typing import Any, Self # TODO: import dialog titles from dzgui.api.mods import remove_stale_signatures @@ -11,8 +13,10 @@ from dzgui.init.coords import get_local_coords from dzgui.init.update import check_updates from dzgui.const.constants import EXPAND, FILL from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager +from dzgui.util.strings import dialog_header from dzgui.util.symlink import rebuild_symlinks -from dzgui.views.components.buttons import ClipboardButton + +# from dzgui.views.components.buttons import ClipboardButton import gi @@ -21,6 +25,7 @@ from gi.repository import Gtk, GLib, Gdk # noqa if TYPE_CHECKING: from dzgui.config.xdg import Xdg + from dzgui.util.ip import Coords class Success(Enum): @@ -30,16 +35,17 @@ class Success(Enum): # TODO: add margins to tree class BootDialog(Gtk.Dialog): - def __init__(self, parent: Gtk.Widget, xdg: "Xdg", version: str) -> None: + def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None: super().__init__( # text="TEST", # buttons=Gtk.ButtonsType.NONE, # message_type=Gtk.MessageType.INFO, # secondary_text="BOOTING UP", # TODO: strings - title="TEST TITLE", + title=dialog_header, parent=parent, - flags=Gtk.DialogFlags.MODAL, + modal=True, + # flags=Gtk.DialogFlags.MODAL, ) self.parent = parent @@ -82,7 +88,6 @@ class BootDialog(Gtk.Dialog): self.view.append_column(col_int) self.view.get_selection().set_mode(Gtk.SelectionMode.NONE) - self.connect("response", self._on_response) self.connect("delete-event", self._on_delete) self.scrollable_tree = Gtk.ScrolledWindow(overlay_scrolling=False) @@ -152,17 +157,13 @@ class BootDialog(Gtk.Dialog): self.failed = False self.steps = iter(steps) - def pulse_spinner(self) -> None: + def pulse_spinner(self) -> Literal[True]: for row in self.store: if row[2]: if row[3] == 150: row[3] = 0 else: row[3] += 1 - # if self.pulse == 100: - # self.pulse = 0 - # else: - # self.pulse += 1 self.spinner_renderer.set_property("pulse", row[3]) return True @@ -170,16 +171,9 @@ class BootDialog(Gtk.Dialog): GLib.timeout_add(100, self.pulse_spinner) self.iter_step() - def _on_delete(self, widget: Self, event: Gdk.Event) -> None: + def _on_delete(self, widget: Self, event: Gdk.Event) -> Literal[True]: return True - def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None: - print("caught response") - if response == Gtk.ResponseType.DELETE_EVENT: - return True - if response == Gtk.ResponseType.CLOSE: - return True - def iter_step(self) -> None: try: step, label, store_output = next(self.steps) @@ -214,9 +208,6 @@ class BootDialog(Gtk.Dialog): callback = StoredFunc(self.update_status, Success.FAIL) self.thread_man.set_cleanup_func(callback) - def get_results(self) -> list[Any]: - return self.results - def update_task(self, task: str) -> None: self.store.append((task, "Running", True, 0)) @@ -224,9 +215,7 @@ class BootDialog(Gtk.Dialog): def update_status(self, state: Success) -> None: d = {Success.OK: "OK", Success.FAIL: "FAILED"} msg = d[state] - model = self.view.get_model() - _iter = model.get_iter(len(model) - 1) - model.set_value(_iter, 1, msg) + self.store[len(self.store) - 1][1] = msg self.iter_step() def _set_spinner_vis( @@ -275,8 +264,9 @@ class BootWindow(Gtk.Window): def set_results(self, res: list[Any]) -> None: self.results = res - def get_results(self) -> str: - return self.results + def get_results(self) -> tuple["Coords", str]: + coords, version_url = self.results + return (coords, version_url) - def _on_destroy(self, widget) -> None: + def _on_destroy(self, dialog: BootDialog) -> None: Gtk.main_quit() From 162839672b57c4b09478ab78b46097aca7ef5735 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:38:00 +0900 Subject: [PATCH 05/18] fix: make parents if missing (#312) --- dzgui/util/_json.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dzgui/util/_json.py b/dzgui/util/_json.py index cad0263..a81e979 100644 --- a/dzgui/util/_json.py +++ b/dzgui/util/_json.py @@ -14,6 +14,7 @@ def read_json(path: Path) -> Any: def write_json(data: dict[str, Any], path: Path) -> None: try: + path.parent.mkdir(parents=True, exist_ok=True) j = json.dumps(data, indent=2) path.write_text(j) except Exception as e: From 97244e783cc474bc6dc953924e024571640f6172 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:38:39 +0900 Subject: [PATCH 06/18] fix: make BMValidationPage optional (#311) --- dzgui/views/dialogs/wizard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dzgui/views/dialogs/wizard.py b/dzgui/views/dialogs/wizard.py index 598a551..d6ca68d 100644 --- a/dzgui/views/dialogs/wizard.py +++ b/dzgui/views/dialogs/wizard.py @@ -534,6 +534,9 @@ class Assistant(Gtk.Assistant): bar.set_text(f"{page_num}/{total}") # NOTE: disable forward action + # TODO: use page enums + if page == self.page5: + return if page != self.page1: EMITTER.emit("step_pending") From 26006daa744e132af16c1d401cbe5d6cfd2ddca0 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:41:00 +0900 Subject: [PATCH 07/18] docs: update changelog --- CHANGELOG.md | 1 + dzgui/data/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 888263e..223e197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Preconnect dialog - Preconnect warnings/failsafes like filesize - Save filters per server context between sessions +- Preboot progress dialog ## Changed diff --git a/dzgui/data/CHANGELOG.md b/dzgui/data/CHANGELOG.md index 888263e..223e197 100644 --- a/dzgui/data/CHANGELOG.md +++ b/dzgui/data/CHANGELOG.md @@ -35,6 +35,7 @@ - Preconnect dialog - Preconnect warnings/failsafes like filesize - Save filters per server context between sessions +- Preboot progress dialog ## Changed From 760208917cfe53a9a7fb542c8d5d410025631bcb Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:41:11 +0900 Subject: [PATCH 08/18] chore: bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 63badb5..6635ddc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux" authors = [ {name = "aclist"} ] -version = "7.0.0b6" +version = "7.0.0b7" license = "GPL-3.0-or-later" license-files = ["LICENSE"] readme = "README.md" From d56cfdd0b20129062f28945b59b072f0b0470a87 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 04:55:44 +0900 Subject: [PATCH 09/18] docs: update special thanks --- dzgui/util/strings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dzgui/util/strings.py b/dzgui/util/strings.py index d006184..1d76209 100644 --- a/dzgui/util/strings.py +++ b/dzgui/util/strings.py @@ -360,12 +360,14 @@ thanks = Thanks( "bongjutsu", "Deku", "dj3hac", + "finntux", "GaryBlackbourne", "jiriks74", "Johnofwrong", "MatheusLasserr", "nolan-perez", "scandalouss", + "SnackSBR", "StevelDusa", "Thoughtduck216", ], From 4852e4a31fd373b0a509fdf94b22571d8d0bc735 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 05:13:31 +0900 Subject: [PATCH 10/18] fix: abort early on failure --- dzgui/views/dialogs/boot.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/dzgui/views/dialogs/boot.py b/dzgui/views/dialogs/boot.py index 74e4aff..9a20d2e 100644 --- a/dzgui/views/dialogs/boot.py +++ b/dzgui/views/dialogs/boot.py @@ -126,8 +126,6 @@ class BootDialog(Gtk.Dialog): self.content = self.get_content_area() self.content.pack_start(self.scrollable_tree, EXPAND, FILL, 0) self.content.pack_start(self.error_box, EXPAND, FILL, 0) - # self.content.pack_start(self.loading_label, EXPAND, FILL, 0) - # self.content.pack_start(self.spinner, EXPAND, FILL, 0) self.show_all() self.error_box.hide() @@ -148,8 +146,8 @@ class BootDialog(Gtk.Dialog): (StoredFunc(get_ipdb, self.xdg.ips), geo, False), (StoredFunc(get_local_coords, self.xdg.ips), coords, True), (StoredFunc(check_updates, self.version), updates, True), - # (StoredFunc(time.sleep, 5.1), "Sleeping", True), # (StoredFunc(time.sleep, 0.1), "Sleeping", True), + # (StoredFunc(lambda: 1/0), "Sleeping", True), # (StoredFunc(time.sleep, 0.1), "Sleeping", False), # (StoredFunc(lambda: 1 / 0), "Broken function", False), ] @@ -175,20 +173,20 @@ class BootDialog(Gtk.Dialog): return True def iter_step(self) -> None: + if self.failed: + # TODO: abstract into method + self.error_box.show() + self.error_label.set_text(self.exception) + self.spinner.stop() + self.loading_label.hide() + return try: step, label, store_output = next(self.steps) self.update_task(label) self.background(step, store_output) except StopIteration: - if self.failed: - # TODO: abstract into method - self.error_box.show() - self.error_label.set_text(self.exception) - self.spinner.stop() - self.loading_label.hide() - else: - self.parent.set_results(self.results) - self.destroy() + self.parent.set_results(self.results) + self.destroy() @call_on_thread("", show_dialog=False) def background(self, func: StoredFunc, store_output: bool) -> None: From b5d9c030dd44766cf7cbf527593792c8a1de415d Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 15:49:02 +0900 Subject: [PATCH 11/18] fix: use dedicated copy button --- dzgui/views/components/buttons.py | 14 +++++++++++--- dzgui/views/components/connect_panel.py | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index c4b3bf2..affcc5b 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -1,4 +1,4 @@ -from typing import Callable, Self, TYPE_CHECKING +from typing import Callable, Self, TYPE_CHECKING, Union from dzgui.util.clip import copy_clipboard from dzgui.util.format import pluralize @@ -70,18 +70,26 @@ class LargeIconTextButton(IconButton): class ClipboardButton(IconTextButton): - def __init__(self, controller: "Controller", func: Callable) -> None: + def __init__(self, controller: Union["Controller", None], func: Callable) -> None: super().__init__(CLIPBOARD, atomic_buttons.copy) self.controller = controller self.connect("clicked", self._on_button_clicked, func) - self.set_tooltip_text("Copy IP to clipboard") + self.set_tooltip_text("Copy to clipboard") def _on_button_clicked(self, button: Self, func: Callable) -> None: data = func() copy_clipboard(data) +# TODO: determine when controller would be passed to this button or drop +class CopyIpButton(ClipboardButton): + def __init__(self, controller: Union["Controller", None], func: Callable) -> None: + super().__init__(controller, func) + + self.set_tooltip_text("Copy IP to clipboard") + + class WebButton(IconTextButton): def __init__(self, label: str) -> None: super().__init__(icon=WEB_BROWSER, label=label) diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index 552a9bc..9e0a4c8 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -6,7 +6,7 @@ from dzgui.strings import connect_panel from dzgui.util.keys import is_ctrl_mask from dzgui.views.components.buttons import ( AddButton, - ClipboardButton, + CopyIpButton, SteamConnectButton, ) from dzgui.views.components.entry import IpEntry, PortEntry @@ -151,7 +151,7 @@ class FavPanel(Gtk.Frame): self.fav_button = SteamConnectButton() self.fav_button.connect("clicked", self._on_connect_clicked) - self.copy_button = ClipboardButton(self.controller, self.get_fav_ip) + self.copy_button = CopyIpButton(self.controller, self.get_fav_ip) if favorite is None: self.toggle_buttons(False) From 9266473853a02d7666b1dc5a290debf28240ec87 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 15:49:18 +0900 Subject: [PATCH 12/18] chore: optimize ipv6 strip logic --- dzgui/config/ipdb.py | 74 +++++++++++++++++-------------------- dzgui/init/coords.py | 5 --- dzgui/main.py | 3 -- dzgui/views/dialogs/boot.py | 21 +++-------- 4 files changed, 39 insertions(+), 64 deletions(-) diff --git a/dzgui/config/ipdb.py b/dzgui/config/ipdb.py index 53c89db..832ac69 100644 --- a/dzgui/config/ipdb.py +++ b/dzgui/config/ipdb.py @@ -23,40 +23,37 @@ def find_download_url(url: str) -> str | None: def get_ipdb(ips_path: Path) -> None: - url = find_download_url(DB_IP) - if url is None: - return - - date = find_date(url) - month_file = ips_path.parent / ".month" - if ips_path.exists() and month_file.exists(): - old_date = month_file.read_text().rstrip("\n") - if old_date == date: - logger.info(f"IP DB date matches: {date}") + try: + url = find_download_url(DB_IP) + if url is None: return - # TODO: log additional output - logger.info(f"Fetching IPDB for {date} from {url}") - try: + date = find_date(url) + month_file = ips_path.parent / ".month" + if ips_path.exists() and month_file.exists(): + old_date = month_file.read_text().rstrip("\n") + if old_date == date: + logger.info(f"IP DB date matches: {date}") + return + + # TODO: log additional output + logger.info(f"Fetching IPDB for {date} from {url}") tmp = serialize(url) - except Exception as e: - logger.critical(e) - return + logger.info(f"Extracting {tmp}") + unzip(tmp, ips_path) - logger.info(f"Extracting {tmp}") - unzip(tmp, ips_path) - - logger.info("Stripping IPv6 records") - try: + logger.info("Stripping IPv6 records") strip_ipv6(ips_path) + + print("here") + with open(month_file, "w") as f: + f.write(date) + logger.info(f"Wrote {date} to {month_file}") except Exception as e: + # NOTE: in the event of failure, geolocation calc is simply not performed logger.critical(e) return - with open(month_file, "w") as f: - f.write(date) - logger.info(f"Wrote {date} to {month_file}") - def find_date(url: str) -> str: date = re.sub(r"(dbip-city-lite-)(.*)(.csv.gz)", r"\2", url) @@ -81,19 +78,16 @@ def serialize(url: str) -> Path: return tmp -# TODO: optimize this function -# consider using grep -# cf. grep -vE "^[a-z0-9]{4}:" | grep -v "::" > "$ip_file" def strip_ipv6(path: Path) -> None: - ips = [] - with open(path, "r") as f: - s = f.read() - - reg = r"^\d{1,3}\..*" - ips = re.findall(reg, s, re.MULTILINE) - assert ips[0].split(",")[0] == "0.0.0.0" - assert ips[-1].split(",")[0] == "224.0.0.0" - - with open(path, "w") as f: - for ip in ips: - f.write(ip + "\n") + alt_path = path.parent.joinpath("stripped.csv") + with open(path, "r") as f, open(alt_path, "w") as out: + for line in f: + reg = r"^\d{1,3}\..*" + m = re.match(reg, line) + if not m: + continue + els = line.split(",") + final = ",".join([els[0], els[1], els[6], els[7]]) + out.write(final) + path.unlink() + alt_path.rename(path.name) diff --git a/dzgui/init/coords.py b/dzgui/init/coords.py index 7ae3afe..009b386 100644 --- a/dzgui/init/coords.py +++ b/dzgui/init/coords.py @@ -13,11 +13,6 @@ logger = logging.getLogger(APP_NAME) def get_local_coords(path: "Path") -> Coords | None: try: my_ip = get_local_ip() - except Exception as e: - logger.warn(e) - return None - - try: return get_coords(path, my_ip) except Exception as e: logger.warn(e) diff --git a/dzgui/main.py b/dzgui/main.py index 4d6952e..45edf9d 100644 --- a/dzgui/main.py +++ b/dzgui/main.py @@ -133,9 +133,6 @@ def main() -> None: # remove_stale_signatures(XDG.config, XDG.version) ## TODO: handle IP DB failure and use coords fallback - ## TODO: drop this after dialog is complete - # print("Fetching geolocation data, may take some time...") - # get_ipdb(XDG.ips) # local_coords = get_local_coords(XDG.ips) # latest_release = check_updates(version) diff --git a/dzgui/views/dialogs/boot.py b/dzgui/views/dialogs/boot.py index 9a20d2e..c1ecc47 100644 --- a/dzgui/views/dialogs/boot.py +++ b/dzgui/views/dialogs/boot.py @@ -15,8 +15,8 @@ from dzgui.const.constants import EXPAND, FILL from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.util.strings import dialog_header from dzgui.util.symlink import rebuild_symlinks +from dzgui.views.components.buttons import ClipboardButton -# from dzgui.views.components.buttons import ClipboardButton import gi @@ -37,15 +37,9 @@ class Success(Enum): class BootDialog(Gtk.Dialog): def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None: super().__init__( - # text="TEST", - # buttons=Gtk.ButtonsType.NONE, - # message_type=Gtk.MessageType.INFO, - # secondary_text="BOOTING UP", - # TODO: strings title=dialog_header, parent=parent, modal=True, - # flags=Gtk.DialogFlags.MODAL, ) self.parent = parent @@ -60,14 +54,12 @@ class BootDialog(Gtk.Dialog): self.view = Gtk.TreeView(enable_search=False, headers_visible=False) self.view.set_model(self.store) - # self.view.set_fixed_height_mode(True) - for i, column_title in enumerate(["Task", "State"]): # , "Spinner"]): + for i, column_title in enumerate(["Task", "State"]): renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn(column_title, renderer, text=i) column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_resizable(False) - # column.set_expand(True) column.set_sort_column_id(i) column.set_cell_data_func(renderer, self._format_color, func_data=None) self.view.append_column(column) @@ -108,10 +100,8 @@ class BootDialog(Gtk.Dialog): ) self.error_label = Gtk.Label() - # TODO: recycle ClipboardButton from generics # TODO: abstract class - # self.copy_button = ClipboardButton() - self.copy_button = Gtk.Button(label="Copy") + self.copy_button = ClipboardButton(None, lambda: self.error_label.get_text()) self.button_hbox = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.CENTER, spacing=10 ) @@ -166,7 +156,6 @@ class BootDialog(Gtk.Dialog): return True def run(self) -> None: - GLib.timeout_add(100, self.pulse_spinner) self.iter_step() def _on_delete(self, widget: Self, event: Gdk.Event) -> Literal[True]: @@ -190,7 +179,8 @@ class BootDialog(Gtk.Dialog): @call_on_thread("", show_dialog=False) def background(self, func: StoredFunc, store_output: bool) -> None: - # time.sleep(0.1) + # FIXME: freezes during heavy IO + GLib.timeout_add(100, self.pulse_spinner) try: if store_output: res = func.call() @@ -209,7 +199,6 @@ class BootDialog(Gtk.Dialog): def update_task(self, task: str) -> None: self.store.append((task, "Running", True, 0)) - # TODO: use enum def update_status(self, state: Success) -> None: d = {Success.OK: "OK", Success.FAIL: "FAILED"} msg = d[state] From c82d42da09969307f27afc5ab1c8b28e553f3d36 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 21:16:08 +0900 Subject: [PATCH 13/18] docs: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 223e197..0f40bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - Suppress log messages from imported modules - Embed Workshop link in Options menu - Disable overlay scrollbars on server tables +- Reduce size of geolocation DB on disk ## Dropped - Debug mode From 69ab3723a1cc2a14f85867231e127241f031233a Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Wed, 27 May 2026 21:16:28 +0900 Subject: [PATCH 14/18] feat: add get_enum() to ScrolledWizardPage --- dzgui/views/dialogs/wizard.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dzgui/views/dialogs/wizard.py b/dzgui/views/dialogs/wizard.py index d6ca68d..bdc298c 100644 --- a/dzgui/views/dialogs/wizard.py +++ b/dzgui/views/dialogs/wizard.py @@ -31,8 +31,6 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gdk, Gtk, GLib, GObject, GdkPixbuf # noqa E402 -# TODO: currently unused, corresponds to linear index -# TODO: use of pagenum enum and page_type attribute is redundant class PageNum(Enum): INTRO = 1 HAS_CONFIG = 2 @@ -93,6 +91,9 @@ class ScrolledWizardPage(Gtk.ScrolledWindow): self.connect("map", self._on_map) + def get_enum(self) -> PageNum: + return self.enum + def get_progress_bar(self) -> Progress: return self.prog @@ -474,6 +475,7 @@ class Assistant(Gtk.Assistant): def _advance_page(self, index: int) -> int: page = self.get_nth_page(index) + # TODO: use enums match page: case self.page1: pass From 78e17121b6ae57b8c37bd6eba7099c931f5191f9 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 00:31:35 +0900 Subject: [PATCH 15/18] chore: add preboot strings file --- dzgui/strings/preboot.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 dzgui/strings/preboot.py diff --git a/dzgui/strings/preboot.py b/dzgui/strings/preboot.py new file mode 100644 index 0000000..01a7ad6 --- /dev/null +++ b/dzgui/strings/preboot.py @@ -0,0 +1,5 @@ +symlinks = "Rebuilding symlinks" +signatures = "Updating mod signatures" +geo = "Checking geolocation records" +coords = "Checking local coordinates" +updates = "Checking for updates" From 63889c2299dce0511d952bbe4a17fe3ac51597c8 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 00:32:11 +0900 Subject: [PATCH 16/18] fix: UI frames dropping during IO events --- CHANGELOG.md | 2 +- dzgui/config/ipdb.py | 32 ++++++++++++++++++++------ dzgui/views/dialogs/boot.py | 45 +++++++++++++------------------------ 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f40bbd..4a1b254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,7 @@ - Suppress log messages from imported modules - Embed Workshop link in Options menu - Disable overlay scrollbars on server tables -- Reduce size of geolocation DB on disk +- Reduce size of geolocation DB on disk (~100MB) ## Dropped - Debug mode diff --git a/dzgui/config/ipdb.py b/dzgui/config/ipdb.py index 832ac69..8af768c 100644 --- a/dzgui/config/ipdb.py +++ b/dzgui/config/ipdb.py @@ -79,15 +79,33 @@ def serialize(url: str) -> Path: def strip_ipv6(path: Path) -> None: - alt_path = path.parent.joinpath("stripped.csv") + """Can be IO intensive and cause visual lag on UI frames + running in the main thread even when run in its own thread; + lines are batched into memory-manageable chunks to reduce + disk writes. Raw file can be 8M+ records long, so it is + not read into memory at once. + + Relative size is reduced by ~100MB by pruning unwanted columns + """ + # NOTE: "^::," is the boundary line between IPv4 and IPv6 + # NOTE: deprecated regex matching (slower by 5s) + #reg = r"^\d{1,3}\..*" + alt_path = path.parent.joinpath("ips_stripped.csv") + merged = "" + its = 0 with open(path, "r") as f, open(alt_path, "w") as out: for line in f: - reg = r"^\d{1,3}\..*" - m = re.match(reg, line) - if not m: - continue els = line.split(",") - final = ",".join([els[0], els[1], els[6], els[7]]) - out.write(final) + if "." not in els[0]: + break + final = ",".join([els[0], els[1], els[-2], els[-1]]) + merged += final + its += 1 + if its == 500: + out.write(merged) + its = 0 + merged = "" + if its > 0: + out.write(merged) path.unlink() alt_path.rename(path.name) diff --git a/dzgui/views/dialogs/boot.py b/dzgui/views/dialogs/boot.py index c1ecc47..974239b 100644 --- a/dzgui/views/dialogs/boot.py +++ b/dzgui/views/dialogs/boot.py @@ -6,13 +6,14 @@ from enum import Enum from typing import Any, Self # TODO: import dialog titles -from dzgui.api.mods import remove_stale_signatures +from dzgui.api.mods import remove_stale_signatures as remove_stale from dzgui.config.ipdb import get_ipdb from dzgui.const.constants import HEX_GREEN, HEX_RED from dzgui.init.coords import get_local_coords from dzgui.init.update import check_updates from dzgui.const.constants import EXPAND, FILL from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager +from dzgui.strings import preboot from dzgui.util.strings import dialog_header from dzgui.util.symlink import rebuild_symlinks from dzgui.views.components.buttons import ClipboardButton @@ -33,6 +34,7 @@ class Success(Enum): FAIL = 2 +# TODO: strings for "Running", "Failed", etc. # TODO: add margins to tree class BootDialog(Gtk.Dialog): def __init__(self, parent: "BootWindow", xdg: "Xdg", version: str) -> None: @@ -86,11 +88,6 @@ class BootDialog(Gtk.Dialog): self.scrollable_tree.add(self.view) self.scrollable_tree.set_size_request(700, 400) - # TODO: pack spinner into dedicated box - self.loading_label = Gtk.Label(label="Loading") - self.spinner = Gtk.Spinner() - self.spinner.start() - self.error_box = Gtk.Box( halign=Gtk.Align.CENTER, orientation=Gtk.Orientation.VERTICAL, @@ -120,31 +117,23 @@ class BootDialog(Gtk.Dialog): self.error_box.hide() - symlinks = "Rebuilding symlinks" - signatures = "Updating mod signatures" - geo = "Checking geolocation records" - coords = "Checking local coordinates" - updates = "Checking for updates" - steps = [ - (StoredFunc(rebuild_symlinks, self.xdg.config), symlinks, False), + (StoredFunc(rebuild_symlinks, self.xdg.config), preboot.symlinks, False), ( - StoredFunc(remove_stale_signatures, self.xdg.config, self.xdg.version), - signatures, + StoredFunc(remove_stale, self.xdg.config, self.xdg.version), + preboot.signatures, False, ), - (StoredFunc(get_ipdb, self.xdg.ips), geo, False), - (StoredFunc(get_local_coords, self.xdg.ips), coords, True), - (StoredFunc(check_updates, self.version), updates, True), - # (StoredFunc(time.sleep, 0.1), "Sleeping", True), - # (StoredFunc(lambda: 1/0), "Sleeping", True), - # (StoredFunc(time.sleep, 0.1), "Sleeping", False), - # (StoredFunc(lambda: 1 / 0), "Broken function", False), + (StoredFunc(get_ipdb, self.xdg.ips), preboot.geo, False), + (StoredFunc(get_local_coords, self.xdg.ips), preboot.coords, True), + (StoredFunc(check_updates, self.version), preboot.updates, True), ] self.results: list[Any] = [] self.failed = False self.steps = iter(steps) + GLib.timeout_add(100, self.pulse_spinner) + def pulse_spinner(self) -> Literal[True]: for row in self.store: if row[2]: @@ -163,11 +152,8 @@ class BootDialog(Gtk.Dialog): def iter_step(self) -> None: if self.failed: - # TODO: abstract into method self.error_box.show() self.error_label.set_text(self.exception) - self.spinner.stop() - self.loading_label.hide() return try: step, label, store_output = next(self.steps) @@ -179,8 +165,6 @@ class BootDialog(Gtk.Dialog): @call_on_thread("", show_dialog=False) def background(self, func: StoredFunc, store_output: bool) -> None: - # FIXME: freezes during heavy IO - GLib.timeout_add(100, self.pulse_spinner) try: if store_output: res = func.call() @@ -226,15 +210,16 @@ class BootDialog(Gtk.Dialog): it: Gtk.TreeIter, data: Any, ) -> None: + prop = "foreground" state = model[it][1] if column.get_sort_column_id() != 1: return if state == "OK": - cell.set_property("foreground", HEX_GREEN) + cell.set_property(prop, HEX_GREEN) elif state == "FAILED": - cell.set_property("foreground", HEX_RED) + cell.set_property(prop, HEX_RED) else: - cell.set_property("foreground", None) + cell.set_property(prop, None) class BootWindow(Gtk.Window): From be6be2af7c2c1019e1914b85c9493a9ecd12d705 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 00:34:19 +0900 Subject: [PATCH 17/18] fix: use absolute path --- dzgui/config/ipdb.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dzgui/config/ipdb.py b/dzgui/config/ipdb.py index 8af768c..a25fbbe 100644 --- a/dzgui/config/ipdb.py +++ b/dzgui/config/ipdb.py @@ -45,7 +45,6 @@ def get_ipdb(ips_path: Path) -> None: logger.info("Stripping IPv6 records") strip_ipv6(ips_path) - print("here") with open(month_file, "w") as f: f.write(date) logger.info(f"Wrote {date} to {month_file}") @@ -89,7 +88,7 @@ def strip_ipv6(path: Path) -> None: """ # NOTE: "^::," is the boundary line between IPv4 and IPv6 # NOTE: deprecated regex matching (slower by 5s) - #reg = r"^\d{1,3}\..*" + # reg = r"^\d{1,3}\..*" alt_path = path.parent.joinpath("ips_stripped.csv") merged = "" its = 0 @@ -108,4 +107,4 @@ def strip_ipv6(path: Path) -> None: if its > 0: out.write(merged) path.unlink() - alt_path.rename(path.name) + alt_path.rename(path) From dbeaad73c6e51be62b0887c9ac24f1a07cf0eeeb Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Thu, 28 May 2026 01:24:45 +0900 Subject: [PATCH 18/18] trigger checks