mirror of
https://github.com/aclist/dztui.git
synced 2026-08-27 18:27:07 +02:00
feat: ModManager
This commit is contained in:
parent
efaaf7f434
commit
4f8d397bbc
@ -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
|
||||
|
||||
@ -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=())
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user