diff --git a/dzgui/api/mods.py b/dzgui/api/mods.py index ead3485..c04f1fc 100644 --- a/dzgui/api/mods.py +++ b/dzgui/api/mods.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from pathlib import Path import dzgui.api.pefile as PeFile -from dzgui.api.servers import Record, get_rules +from dzgui.api.servers import Record, get_rules, fqip_to_record from dzgui.const.constants import ( APP_NAME, APPID_DAYZ, @@ -153,10 +153,8 @@ def remove_stale_signatures(config: Path, versions: Path) -> None: def find_stale_mods(config: Path) -> list[int]: def push_record(rec: str) -> list: - add = rec.split(":") - ip = add[0] - qport = add[2] - return get_rules(ip, qport) + record = fqip_to_record(rec) + return [mod.workshop_id for mod in get_rules(record)] steam = lookup(config, Preferences.DEFAULT) steam_path = Path(steam) @@ -171,7 +169,8 @@ def find_stale_mods(config: Path) -> list[int]: wait(futures) for future in futures: res = future.result() - remote_mods += res + remote_mods.extend(res) - stale = [mod for mod in local if mod not in remote_mods] + unique_mods = set(remote_mods) + stale = [mod for mod in local if mod not in unique_mods] return stale diff --git a/dzgui/controllers/emitter.py b/dzgui/controllers/emitter.py index c58f4d7..51faae2 100644 --- a/dzgui/controllers/emitter.py +++ b/dzgui/controllers/emitter.py @@ -124,8 +124,8 @@ class Emitter(GObject.GObject): def load_maps(self, store: ListStore) -> None: pass - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) - def mod_page_loaded(self) -> None: + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(str,)) + def mods_updated(self, msg: str) -> None: pass @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index f9bab62..7f822d5 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -41,7 +41,6 @@ from dzgui.managers.config import ConfigManager from dzgui.managers.connection import ConnectionManager from dzgui.managers.contextmenu import ContextMenuManager from dzgui.managers.notes import NoteManager -from dzgui.model.proxy_model import ProxyModelManager from dzgui.model.servers import ServerModelManager from dzgui.model.model_factory import ModelFactory from dzgui.util import strings @@ -122,25 +121,6 @@ class Controller(GObject.GObject): def get_emitter(self) -> Emitter: return self.emitter - @deprecated("depends on load mods logic") - def call_on_thread(dialog_str: str) -> Callable: - def decorator(func: Callable) -> Callable: - @wraps(func) - def wrapper(*args, **kwargs): - def callback() -> None: - func(*args, **kwargs) - GLib.idle_add(self._destroy_on_idle) - - self = args[0] - self.wait_dialog = WaitDialog(self, dialog_str, jobs=self.pending_jobs) - self.wait_dialog.show_all() - thread = threading.Thread(target=callback) - thread.start() - - return wrapper - - return decorator - def register_widget(self, attr: str, widget: Gtk.Widget) -> None: try: setattr(self.mediator, attr, widget) @@ -282,135 +262,22 @@ class Controller(GObject.GObject): client = self.query_config(Preferences.CLIENT) open_user_workshop(uid, client) - ### START LOAD MODS LOGIC + def load_mods(self) -> None: + from dzgui.managers.mods import ModManager + + self.mod_man = ModManager(self) def uncolorize_mods(self) -> None: - model = self.get_mod_store() - for mod in model: - it = mod.iter - path = model.get_path(it) - model[path][4] = None - path = Gtk.TreePath.new_from_indices([0]) - self.mediator.modtreeview.set_cursor(path) + self.mod_man.uncolorize_mods() - def highlight_stale_cleanup(self, stale_mods: list) -> None: - """Manipulates attached ListStore in the main event loop""" - model = self.get_mod_store() - if model is None: - return - for mod in model: - it = mod.iter - path = model.get_path(it) - if int(mod[2]) in stale_mods: - model[path][4] = True - self.emitter.emit("mods_highlighted") - - # TODO: delegate to mod manager - @call_on_thread(strings.dialog.working) def highlight_stale(self) -> None: - # TODO: set progress bar for number of mods - stale = find_stale_mods(self.prefs.paths.config) - self.cleanup_func = StoredFunc(self.highlight_stale_cleanup, stale) - - def set_cleanup_func(self, func: StoredFunc) -> None: - self.cleanup_func = func - - def get_cleanup_func(self) -> StoredFunc: - return self.cleanup_func - - def _destroy_on_idle(self) -> None: - self.wait_dialog.destroy() - func = self.get_cleanup_func() - if func is not None: - func.call() - self.set_cleanup_func(None) - self.mediator.window.set_sensitive(True) + self.mod_man.highlight_stale() def toggle_mod_selection(self, state: bool) -> None: - sel = self.mediator.modtreeview.get_selection() - if state: - sel.select_all() - else: - sel.unselect_all() - # TODO: signal - self.mediator.modtreeview.grab_focus() + self.mod_man.toggle_mod_selection(state) - def load_mods_cleanup(self, model: Gtk.ListStore) -> None: - self.mediator.modtreeview.set_model(model) - self.emitter.emit("mod_page_loaded") - - # def load_mods(self) -> None: - # path = self.query_config(Preferences.DEFAULT) - # ModManager(path) - - # TODO: delegate to threadmanager - @call_on_thread(strings.dialog.modlist) - def load_mods(self) -> None: - model = ModelFactory().make_mod_store() - path = self.query_config(Preferences.DEFAULT) - mods = get_delimited_mods(Path(path)) - model.extend(mods) - - self.cleanup_func = StoredFunc(self.load_mods_cleanup, model) - - def delete_multiple_mods(self) -> None: - sel = self.mediator.modtreeview.get_selection() - model, pathlist = sel.get_selected_rows() - # NOTE: reverse when multiple selection - for path in reversed(pathlist): - self.delete_single_mod(path) - - total_mods, total_size = self.calc_mod_size() - self.format_mod_statusbar() - - def delete_single_mod_cleanup(self, _iter: Gtk.TreeIter) -> None: - self.get_mod_store().remove(_iter) - remove_stale_signatures(self.prefs.paths.config, self.prefs.paths.version) - - # TODO: strings - # TODO: delegate to LocalModManager or api/mods.py - @call_on_thread("deleting mod") - def delete_single_mod(self, tree_path: Gtk.TreePath) -> None: - config = self.prefs.paths.config - mod, _iter = self.get_mod_from_tree_path(tree_path) - - path = lookup(config, Preferences.DEFAULT) - steam_path = Path(path) - mods_path = get_local_mod_path(steam_path) - app_path = PeFile.get_nested_app_path(steam_path, APPID_DAYZ) - - md5 = _hash(mod) - symlink = app_path / md5 - symlink.unlink() - shutil.rmtree(mods_path / mod) - - # NOTE: second pass to unlink DAYZ_EXP mods - # TODO: test this with working APPID_DAYZ_EXP installation - try: - app_path_exp = PeFile.get_nested_app_path(steam_path, APPID_DAYZ_EXP) - symlink = app_path_exp / md5 - symlink.unlink() - except PeFile.AppNotInstalledError: - pass - - self.cleanup_func = StoredFunc(self.delete_single_mod_cleanup, _iter) - - def format_mod_statusbar(self) -> str: - total_mods, total_size = self.calc_mod_size() - msg = format_mods(total_size, total_mods) - return msg - - def calc_mod_size(self) -> tuple[int, int]: - model = self.get_mod_store() - if model is None: - return 0, 0 - total_mods = len(model) - total_size = 0 - for mod in model: - total_size += mod[3] - return total_mods, total_size - - ### END LOAD MODS LOGIC + def delete_mods(self) -> None: + self.mod_man.delete_mods() def get_mod_store(self) -> Gtk.TreeModel | None: return self.mediator.modtreeview.get_model() @@ -652,3 +519,6 @@ class Controller(GObject.GObject): log_man = LogManager() alerts = log_man.get_alerts(self.prefs.paths.debug) return alerts + + def get_modtreeview(self) -> "ModTreeView": + return self.mediator.modtreeview diff --git a/dzgui/managers/contextmenu.py b/dzgui/managers/contextmenu.py index a075a6a..642d645 100644 --- a/dzgui/managers/contextmenu.py +++ b/dzgui/managers/contextmenu.py @@ -44,14 +44,7 @@ class ContextMenuManager: case ContextMenu.COPY_SERVER_NAME: self.copy_server_name() case ContextMenu.DELETE_MOD: - print("DEBUG: unimplemented") - # self.delete_single_mod() - # TODO: connect to emitter automatically - # Gtk.TreeModel, row-inserted/row-deleted - # updates statusbar - # FIXME: signal should instead be emitted off of treeview when rows added/inserted - # self.update_mod_statusbar() - pass + self.controller.delete_mods() case ContextMenu.OPEN_WORKSHOP: self.open_mod_page() case ContextMenu.SET_FAV: diff --git a/dzgui/views/components/mod_panel.py b/dzgui/views/components/mod_panel.py index 5bc4552..5f05337 100644 --- a/dzgui/views/components/mod_panel.py +++ b/dzgui/views/components/mod_panel.py @@ -81,7 +81,7 @@ class ModSelectionPanel(Gtk.Box): case ModButton.UNSELECT_ALL: self.controller.toggle_mod_selection(False) case ModButton.DELETE_SELECTED: - self.controller.delete_multiple_mods() + self.controller.delete_mods() case ModButton.HIGHLIGHT_STALE: self.controller.highlight_stale() case ModButton.UNHIGHLIGHT_STALE: diff --git a/dzgui/views/components/statusbar.py b/dzgui/views/components/statusbar.py index 7b4dd05..7533cb5 100644 --- a/dzgui/views/components/statusbar.py +++ b/dzgui/views/components/statusbar.py @@ -60,7 +60,7 @@ class Statusbar(Gtk.Grid): self.emitter.connect("distcalc_started", self._on_distcalc_started) self.emitter.connect("distcalc_ended", self._on_distcalc_ended) self.emitter.connect("servers_loaded", self._on_servers_loaded) - self.emitter.connect("mod_page_loaded", self._on_mod_page_loaded) + self.emitter.connect("mods_updated", self._on_mods_updated) self.emitter.connect("log_page_loaded", self._on_log_page_loaded) def _on_alerts_clicked(self, button: LoggerAlertsButton) -> None: @@ -70,8 +70,7 @@ class Statusbar(Gtk.Grid): # FIXME: fails if button was not previously drawn self.alert_button.hide() - def _on_mod_page_loaded(self, emitter: "Emitter") -> None: - msg = self.controller.format_mod_statusbar() + def _on_mods_updated(self, emitter: "Emitter", msg: str) -> None: self.set_by_context(NotebookPage.MODS, msg) def _on_notebook_page_changed( diff --git a/dzgui/views/trees/tree_mods.py b/dzgui/views/trees/tree_mods.py index c272d40..993ee40 100644 --- a/dzgui/views/trees/tree_mods.py +++ b/dzgui/views/trees/tree_mods.py @@ -26,7 +26,7 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): super().__init__(controller, menu=ContextMenuGroup.MOD) self.controller = controller emitter = self.controller.get_emitter() - emitter.connect("mod_page_loaded", self._on_mod_page_loaded) + emitter.connect("mods_updated", self._on_mods_updated) emitter.connect("mods_highlighted", self._on_mods_highlighted) self.set_fixed_height_mode(True) @@ -62,7 +62,7 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView): self.get_selection().unselect_all() # TODO: test loading with no mods - def _on_mod_page_loaded(self, emitter: "Emitter") -> None: + def _on_mods_updated(self, emitter: "Emitter", msg: str) -> None: self.set_cursor(0) def get_selected_mod(self) -> str: