diff --git a/dzgui/api/acf.py b/dzgui/api/acf.py new file mode 100644 index 0000000..2518cd7 --- /dev/null +++ b/dzgui/api/acf.py @@ -0,0 +1,96 @@ +import re +from collections.abc import Iterator +from typing import Any +from warnings import deprecated + + +@deprecated("Use dzgui.api.steam.unsubscribe()") +class WorkshopACF: + def __init__(self, file: str) -> None: + super().__init__() + + self.dict: dict[str, Any] + self.load(file) + + def as_dict(self) -> dict[str, Any]: + return self.dict + + def load(self, file: str) -> None: + delimiter = r"\t\t" + lines = [] + with open(file, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + els = re.split(delimiter, line, maxsplit=1) + lines.append(els) + self.dict = self.parse(iter(lines)) + + def parse(self, lines: Iterator[list[str]]) -> dict[str, str]: + acf: dict[str, Any] = {} + try: + while True: + line = next(lines) + if len(line) == 1: + key = line[0] + if key == "{": + continue + return acf + elif key == "}": + return acf + else: + key = self.dequote(key) + acf[key] = self.parse(lines) + elif len(line) == 2: + k, v = line + k = self.dequote(k) + v = self.dequote(v) + try: + n = list(acf.keys())[-1] + acf[n][k] = v + except Exception: + acf[k] = v + except StopIteration: + return acf + + def unpack(self, d: dict, lines: list[Any] = []) -> str: + t1 = "AppWorkshop" + t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails") + for k, v in d.items(): + if type(v) is dict: + if k in t1: + self.indent = 0 + elif k in t2: + self.indent = 1 + else: + self.indent = 2 + pref = "\t" * self.indent + lines.append(pref + self.enquote(k)) + lines.append(pref + "{") + self.unpack(v, lines) + lines.append(pref + "}") + else: + pref = "\t" * (self.indent + 1) + lines.append(pref + self.enquote(k) + "\t\t" + self.enquote(v)) + s = "" + for line in lines: + s += line + "\n" + return s + + def to_file(self, file: str) -> None: + s = self.unpack(self.dict) + with open(file, "w") as f: + f.write(s) + + @classmethod + def enquote(cls, s: str) -> str: + return f'"{s}"' + + @classmethod + def dequote(cls, s: str) -> str: + return s.rstrip('"').lstrip('"') + + def delete(self, modid: int) -> None: + del self.dict["AppWorkshop"]["WorkshopItemsInstalled"][modid] + del self.dict["AppWorkshop"]["WorkshopItemDetails"][modid] diff --git a/dzgui/api/steam.py b/dzgui/api/steam.py index 63ca05f..397080c 100644 --- a/dzgui/api/steam.py +++ b/dzgui/api/steam.py @@ -3,20 +3,25 @@ import logging import os import requests import subprocess +from typing import Union +from warnings import deprecated from shlex import shlex from pathlib import Path from dzgui.init.prereqs import has_steam_client +from dzgui.api.mods import _hash from dzgui.const.constants import ( + APPID_DAYZ, APP_NAME, DEBIAN_STEAM_PATH, DEFAULT_STEAM_PATH, FLATPAK_STEAM_PATH, UBUNTU_STEAM_PATH, + REQUEST_TIMEOUT, VDF_PATH, ) -from dzgui.const.endpoints import STEAM_PUBLISHED_FILES +from dzgui.const.endpoints import SUB_ENDPOINT, STEAM_PUBLISHED_FILES, UNSUB_ENDPOINT from dzgui.strings import wizard from dzgui.util.bash import concat_bash_args @@ -53,8 +58,6 @@ def get_steam_paths() -> list[tuple[Path, str]]: def concat_mods(mods: list[str]) -> str: - from dzgui.util.symlink import _hash - hashes = [] for mod in mods: md5sum = _hash(mod) @@ -68,26 +71,21 @@ def get_local_signatures(version_file: Path) -> dict[str, int]: for line in lines: data = line.split(",") _id = data[0] - _hash = int(data[1]) - hashes[_id] = _hash + mod_hash = int(data[1]) + hashes[_id] = mod_hash return hashes -def enqueue_mod(client: str, mod: str, appid: int) -> None: - client_args = concat_bash_args(client) - subprocess.Popen([*client_args, "+workshop_download_item", str(appid), mod]) - - def get_needs_update( version_file: Path, remote_hashes: list[tuple[str, str, int, int]] ) -> list[tuple[str, str, int, int]]: - local_hashes = get_local_signatures(version_file) + local_stamps = get_local_signatures(version_file) needs_update: list[tuple[str, str, int, int]] = [] - for title, _id, _hash, size in remote_hashes: - if _id not in local_hashes: - needs_update.append((title, _id, _hash, size)) - elif _hash != local_hashes[_id]: - needs_update.append((title, _id, _hash, size)) + for title, _id, stamp, size in remote_hashes: + if _id not in local_stamps: + needs_update.append((title, _id, stamp, size)) + elif stamp != local_stamps[_id]: + needs_update.append((title, _id, stamp, size)) else: continue return needs_update @@ -232,6 +230,29 @@ def vdf2json(path: Path) -> str: jbuf += "\n" +def update_workshop(key: str, mod: int, endpoint: str) -> None: + payload: dict[str, Union[int, str]] = { + "publishedfileid": mod, + "appid": APPID_DAYZ, + "key": key, + "list_type": 1, + "notify_client": 1, + } + try: + res = requests.post(endpoint, params=payload, timeout=REQUEST_TIMEOUT) + res.raise_for_status() + except Exception as e: + logger.critical(e) + + +def subscribe(key: str, mod: int) -> None: + update_workshop(key, mod, SUB_ENDPOINT) + + +def unsubscribe(key: str, mod: int) -> None: + update_workshop(key, mod, UNSUB_ENDPOINT) + + def gen_shortcut() -> None: # TODO: """ @@ -244,3 +265,9 @@ def gen_shortcut() -> None: # or get right-most 32 bits # STEAMID_64 & 0xFFFFFFFF pass + + +@deprecated("Use subscribe()") +def enqueue_mod(client: str, mod: str, appid: int) -> None: + client_args = concat_bash_args(client) + subprocess.Popen([*client_args, "+workshop_download_item", str(appid), mod]) diff --git a/dzgui/const/constants.py b/dzgui/const/constants.py index d9689d2..cee5a63 100644 --- a/dzgui/const/constants.py +++ b/dzgui/const/constants.py @@ -4,6 +4,7 @@ UDP_PORT = 27016 VM_FILE = "/proc/sys/vm/max_map_count" MIN_COUNT = 1048576 +RATE_LIMIT_THRESHOLD = 3 REQUEST_TIMEOUT = 10 APPNAME_DAYZ = "DayZ" diff --git a/dzgui/const/endpoints.py b/dzgui/const/endpoints.py index 621f233..9d15b02 100644 --- a/dzgui/const/endpoints.py +++ b/dzgui/const/endpoints.py @@ -1,6 +1,11 @@ # internal -STEAM_PUBLISHED_FILES = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/?format=json" -STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1/?" +STEAM_PUBLISHED_FILES = ( + "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1" +) +STEAM_SERVERS = "https://api.steampowered.com/IGameServersService/GetServerList/v1" +SUB_ENDPOINT = "https://api.steampowered.com/IPublishedFileService/Subscribe/v1" +UNSUB_ENDPOINT = "https://api.steampowered.com/IPublishedFileService/Unsubscribe/v1" + BM_SERVERS = "https://api.battlemetrics.com/servers?" GITHUB = "https://github.com/aclist" GITHUB_RELEASES = "https://api.github.com/repos/aclist/dztui/releases/latest" diff --git a/dzgui/const/enum.py b/dzgui/const/enum.py index 9e84cc5..feab780 100644 --- a/dzgui/const/enum.py +++ b/dzgui/const/enum.py @@ -168,7 +168,7 @@ class ContextMenu(EnumWithAttrs): COPY_LOG_CLIPBOARD = {"label": strings.copy_log} COPY_SERVER_IP = {"label": strings.copy_ip} COPY_SERVER_NAME = {"label": strings.copy_name} - DELETE_MOD = {"label": strings.delete_mod} + UNSUB_MOD = {"label": strings.unsub_mod} OPEN_WORKSHOP = {"label": strings.open_workshop} REFRESH_PLAYERS = {"label": strings.refresh_players} REMOVE_HISTORY = {"label": strings.remove_history} @@ -184,7 +184,7 @@ class ContextMenuGroup(Enum): """ SERVER_MOD = (ContextMenu.OPEN_WORKSHOP,) - MOD = (ContextMenu.OPEN_WORKSHOP, ContextMenu.DELETE_MOD) + MOD = (ContextMenu.OPEN_WORKSHOP, ContextMenu.UNSUB_MOD) MOD_OFFLINE = (None,) LOG = (ContextMenu.COPY_LOG_CLIPBOARD,) SERVER_BROWSER = ( @@ -250,9 +250,9 @@ class ModButton(EnumWithAttrs): "label": strings.mod_panel.unhighlight_stale, "tooltip": strings.mod_panel.unhighlight_stale_tooltip, } - DELETE_SELECTED = { - "label": strings.mod_panel.delete_selected, - "tooltip": strings.mod_panel.delete_selected_tooltip, + UNSUB_SELECTED = { + "label": strings.mod_panel.unsub_selected, + "tooltip": strings.mod_panel.unsub_selected_tooltip, } SELECT_STALE = { "label": strings.mod_panel.select_stale, diff --git a/dzgui/controllers/mc.py b/dzgui/controllers/mc.py index c7168ab..ae0bf6d 100644 --- a/dzgui/controllers/mc.py +++ b/dzgui/controllers/mc.py @@ -233,7 +233,7 @@ class Controller(GObject.GObject): mod_man = self.mediator.modtreeview.get_mod_man() mod_man.toggle_mod_selection(state) - def delete_mods( + def unsub_mods( self, treeview: Union["ModTreeView", "OfflineModTreeView", None] = None ) -> None: if treeview is None: @@ -241,7 +241,7 @@ class Controller(GObject.GObject): else: view = treeview mod_man = view.get_mod_man() - mod_man.delete_mods() + mod_man.unsub_mods() def get_mod_store(self) -> Gtk.TreeModel | None: return self.mediator.modtreeview.get_model() @@ -523,11 +523,11 @@ class Controller(GObject.GObject): ind = self.config_man.get_start_tab() self.get_servers().notebook.set_current_page(ind) - def update_and_load_to_menu(self, raise_window: bool) -> None: - self.connection_man.update_and_connect(raise_window, menu_only=True) + def update_and_load_to_menu(self) -> None: + self.connection_man.update_and_connect(menu_only=True) - def update_and_connect(self, raise_window: bool) -> None: - self.connection_man.update_and_connect(raise_window) + def update_and_connect(self) -> None: + self.connection_man.update_and_connect() def update_status(self) -> None: self.mediator.preconnect.mark_finished() diff --git a/dzgui/managers/connection.py b/dzgui/managers/connection.py index c0d092e..27c2806 100644 --- a/dzgui/managers/connection.py +++ b/dzgui/managers/connection.py @@ -12,10 +12,10 @@ import dzgui.api.servers as Servers from dzgui.api.steam import ( connect, - enqueue_mod, get_remote_signatures, get_needs_update, load_to_menu, + subscribe, ) from dzgui.api.mods import ( @@ -30,6 +30,7 @@ from dzgui.const.constants import ( APPID_DAYZ_EXP, APPNAME_DAYZ, APPNAME_DAYZ_EXP_HUMAN, + RATE_LIMIT_THRESHOLD, ) from dzgui.const.enum import NotebookPage, Preferences from dzgui.init.proc import is_dayz_running, is_steam_running @@ -293,20 +294,16 @@ class ConnectionManager: self.controller.add_to_history(self.history, self.record) self.controller.open_page(NotebookPage.SERVERS) - def _update_mods(self, raise_window: bool, menu_only: bool = False) -> None: - # NOTE: fast enqueue all mods in auto mode + def _update_mods(self, menu_only: bool = False) -> None: prefs = self.controller.get_prefs() + config_man = self.controller.get_config_man() + key = config_man.lookup(Preferences.STEAM) for title, mod, stamp, size in self.missing_mods: if self.controller.is_cancel_pending(): return - enqueue_mod(self.client, mod, self.appid) - # NOTE: prevents rate limiting - time.sleep(3) - - if raise_window is True: - logger.info("Bringing window to foreground") - GLib.idle_add(self.controller.present_window) + subscribe(key, int(mod)) + time.sleep(RATE_LIMIT_THRESHOLD) for title, mod, stamp, size in self.missing_mods: mod_path = self.workshop / mod @@ -330,8 +327,8 @@ class ConnectionManager: self._connect_steam(menu_only) @call_on_thread(waiting_for_mods, show_cancel=True) - def update_and_connect(self, raise_window: bool, menu_only: bool = False) -> None: + def update_and_connect(self, menu_only: bool = False) -> None: if len(self.missing_mods) > 0: - self._update_mods(raise_window, menu_only) + self._update_mods(menu_only) else: self._connect_steam(menu_only) diff --git a/dzgui/managers/contextmenu.py b/dzgui/managers/contextmenu.py index 7701b1c..37e6b81 100644 --- a/dzgui/managers/contextmenu.py +++ b/dzgui/managers/contextmenu.py @@ -80,8 +80,8 @@ class ContextMenuManager: if isinstance(self.treeview, (ModTreeView, OfflineModTreeView)): match action: - case ContextMenu.DELETE_MOD: - self.controller.delete_mods(self.treeview) + case ContextMenu.UNSUB_MOD: + self.controller.unsub_mods(self.treeview) case ContextMenu.OPEN_WORKSHOP: self.open_mod_page() diff --git a/dzgui/managers/filter.py b/dzgui/managers/filter.py index 92adf55..3ccfbbe 100644 --- a/dzgui/managers/filter.py +++ b/dzgui/managers/filter.py @@ -89,8 +89,9 @@ class FilterManager: continue self.append_map([m]) + # TODO: currently unused def get_unique_maps(self) -> list[str]: - return [row[0] for row in self.map_store if row != "All maps"] + return [row[0] for row in self.map_store if row[0] != "All maps"] def get_all_filters(self) -> tuple: map_name = self.get_active_map_name() diff --git a/dzgui/managers/mods.py b/dzgui/managers/mods.py index 05e9996..af27090 100644 --- a/dzgui/managers/mods.py +++ b/dzgui/managers/mods.py @@ -1,17 +1,22 @@ import logging -import shutil +import time from pathlib import Path from typing import TYPE_CHECKING +from dzgui.api.steam import unsubscribe from dzgui.api.mods import ( get_delimited_mods, - get_local_mod_path, find_stale_mods, _hash, remove_stale_signatures, ) -from dzgui.const.constants import APP_NAME, APPID_DAYZ, APPID_DAYZ_EXP +from dzgui.const.constants import ( + APP_NAME, + APPID_DAYZ, + APPID_DAYZ_EXP, + RATE_LIMIT_THRESHOLD, +) from dzgui.const.enum import Preferences from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.model.model_factory import FastInsertListStore, ModelFactory @@ -77,7 +82,7 @@ class ModManager: total_mods = len(self.store) self.emitter.emit("mods_updated", msg, total_mods) - def delete_mods(self) -> None: + def unsub_mods(self) -> None: sel = self.treeview.get_selection() model, pathlist = sel.get_selected_rows() # NOTE: reverse when multiple selection @@ -88,7 +93,7 @@ class ModManager: continue mod, _iter = res mods.append((mod, _iter)) - self.delete_mods_on_system(mods) + self.unsub_all_mods(mods) def get_mod_from_tree_path( self, tree_path: Gtk.TreePath @@ -101,24 +106,28 @@ class ModManager: return mod, tree_iter @call_on_thread(dialogs.deleting_mods) - def delete_mods_on_system(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None: + def unsub_all_mods(self, mods: list[tuple[str, Gtk.TreeIter]]) -> None: for mod, _iter in mods: - self.delete_single_mod(mod) + self.unsub_atomic_mod(mod) iters = [_iter for mod, _iter in mods] - func = StoredFunc(self._on_mods_deleted, iters) + func = StoredFunc(self._on_mods_unsubbed, iters) self.thread_man.set_cleanup_func(func) - def delete_single_mod(self, mod: str) -> None: + def unsub_atomic_mod(self, mod: str) -> None: + config_man = self.controller.get_config_man() + key = config_man.lookup(Preferences.STEAM) + unsubscribe(key, int(mod)) + steam_path = Path(self.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) - + try: + md5 = _hash(mod) + symlink = app_path / md5 + symlink.unlink() + except Exception as e: + logger.warning(e) # NOTE: second pass to unlink DAYZ_EXP mods # TODO: test this with working APPID_DAYZ_EXP installation try: @@ -127,8 +136,9 @@ class ModManager: symlink.unlink() except PeFile.AppNotInstalledError: pass + time.sleep(RATE_LIMIT_THRESHOLD) - def _on_mods_deleted(self, iters: list[Gtk.TreeIter]) -> None: + def _on_mods_unsubbed(self, iters: list[Gtk.TreeIter]) -> None: if self.store is None: return for _iter in iters: diff --git a/dzgui/model/servers.py b/dzgui/model/servers.py index 1a61bba..092c8ab 100644 --- a/dzgui/model/servers.py +++ b/dzgui/model/servers.py @@ -374,7 +374,7 @@ class ServerModelManager: proxy = self._get_proxy_man().get_proxy_model() self.tv.set_model(proxy) - if self.controller.get_active_treeview().get_enum == ServerTab.SAVED: + if self.controller.get_active_treeview().get_enum() == ServerTab.SAVED: self.emitter.emit("servers_loaded", self.enum) filter_man = self.tv.get_filter_man() diff --git a/dzgui/strings/dialogs.py b/dzgui/strings/dialogs.py index 5df7bb7..57c27a6 100644 --- a/dzgui/strings/dialogs.py +++ b/dzgui/strings/dialogs.py @@ -7,7 +7,7 @@ update_success = "Updated successfully. Please exit and relaunch." load_error_lan = "Failed to find any servers on your network.\nCheck the server query port or your firewall settings." fetching_mods = "Fetching mod metadata" -deleting_mods = "Deleting mods" +deleting_mods = "Unsubscribing mods" scanning_mods = "Scanning mods" parsing_mods = "Parsing mods" diff --git a/dzgui/util/strings.py b/dzgui/util/strings.py index 3d1a0e4..060dcd8 100644 --- a/dzgui/util/strings.py +++ b/dzgui/util/strings.py @@ -52,7 +52,7 @@ show_mods = "Show server-side mods" show_details = "Show server details" refresh_players = "Refresh player count" open_workshop = "Open in Steam Workshop" -delete_mod = "Delete mod" +unsub_mod = "Unsubscribe mod" copy_name = "Copy name to clipboard" copy_ip = "Copy IP to clipboard" copy_log = "Copy record(s) to clipboard" @@ -215,8 +215,8 @@ class ModPanelStrings: unhighlight_stale_tooltip: str highlight_stale: str highlight_stale_tooltip: str - delete_selected: str - delete_selected_tooltip: str + unsub_selected: str + unsub_selected_tooltip: str unselect_all: str unselect_all_tooltip: str select_all: str @@ -337,8 +337,8 @@ mod_panel = ModPanelStrings( "Shows locally-installed mods which are not used by any server " "in your Saved Servers" ), - delete_selected="Delete selected", - delete_selected_tooltip="Deletes selected mods from the system", + unsub_selected="Unsubscribe selected", + unsub_selected_tooltip="Unsubscribes from selected mods", unselect_all="Unselect all", unselect_all_tooltip="Bulk unselects all mods", select_all="Select all", @@ -463,7 +463,7 @@ crumbs = Crumbs( thanks="Help > Special thanks", developers="Options > Developers", default="Servers > ", - offline="Mods > Play offline" + offline="Mods > Play offline", ) no_mods = "No local mods found." diff --git a/dzgui/views/components/box.py b/dzgui/views/components/box.py new file mode 100644 index 0000000..be64f82 --- /dev/null +++ b/dzgui/views/components/box.py @@ -0,0 +1,25 @@ +from typing import Sequence + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # noqa + + +class GenericBox(Gtk.Box): + def __init__(self, orientation: Gtk.Orientation, spacing: int = 0) -> None: + super().__init__(orientation=orientation, spacing=spacing) + + def extend(self, els: Sequence[Gtk.Widget]) -> None: + for el in els: + self.add(el) + + +class HBox(GenericBox): + def __init__(self, spacing: int = 0) -> None: + super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=spacing) + + +class VBox(GenericBox): + def __init__(self, spacing: int = 0) -> None: + super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=spacing) diff --git a/dzgui/views/components/buttons.py b/dzgui/views/components/buttons.py index 63b1986..1e7c32e 100644 --- a/dzgui/views/components/buttons.py +++ b/dzgui/views/components/buttons.py @@ -208,6 +208,8 @@ class SteamWorkshopButton(SteamTextButton): def __init__(self) -> None: super().__init__(label=buttons.workshop) self.set_tooltip_text(buttons.workshop_tooltip) + self.set_margin_top(10) + self.set_margin_bottom(10) class AddButton(IconTextButton): diff --git a/dzgui/views/components/connect_panel.py b/dzgui/views/components/connect_panel.py index ae17ffc..0e4e6f4 100644 --- a/dzgui/views/components/connect_panel.py +++ b/dzgui/views/components/connect_panel.py @@ -20,7 +20,8 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk # noqa E402 if TYPE_CHECKING: - from dzgui.controllers.mc import Controller, Emitter + from dzgui.controllers.mc import Controller + from dzgui.controllers.emitter import Emitter COLS = 1 ROWS = 1 diff --git a/dzgui/views/components/mod_panel.py b/dzgui/views/components/mod_panel.py index 194ab2e..b1c19ea 100644 --- a/dzgui/views/components/mod_panel.py +++ b/dzgui/views/components/mod_panel.py @@ -11,7 +11,8 @@ gi.require_version("Gtk", "3.0") from gi.repository import Gtk # noqa E402 if TYPE_CHECKING: - from dzgui.controllers.mc import Controller, Emitter + from dzgui.controllers.mc import Controller + from dzgui.controllers.emitter import Emitter class EnumeratedModButton(Gtk.Button): @@ -48,7 +49,7 @@ class ModSelectionPanel(Gtk.Box): buttons = ( ModButton.SELECT_ALL, ModButton.UNSELECT_ALL, - ModButton.DELETE_SELECTED, + ModButton.UNSUB_SELECTED, ) for button in buttons: b = EnumeratedModButton(button) @@ -100,8 +101,8 @@ class ModSelectionPanel(Gtk.Box): self.controller.toggle_mod_selection(True) case ModButton.UNSELECT_ALL: self.controller.toggle_mod_selection(False) - case ModButton.DELETE_SELECTED: - self.controller.delete_mods() + case ModButton.UNSUB_SELECTED: + self.controller.unsub_mods() case ModButton.HIGHLIGHT_STALE: self.controller.highlight_stale() case ModButton.UNHIGHLIGHT_STALE: diff --git a/dzgui/views/pages/mods.py b/dzgui/views/pages/mods.py index fdd1e7b..b840c11 100644 --- a/dzgui/views/pages/mods.py +++ b/dzgui/views/pages/mods.py @@ -1,5 +1,10 @@ +from pathlib import Path + from typing import Self, TYPE_CHECKING -from dzgui.const.enum import NotebookPage +from dzgui.api.steam import find_user_id +from dzgui.const.enum import NotebookPage, Preferences +from dzgui.views.components.box import HBox +from dzgui.views.components.buttons import SteamWorkshopButton from dzgui.views.components.scrollable import NoOverlayScrolledWindow from dzgui.views.trees.tree_mods import ModTreeView @@ -26,6 +31,17 @@ class Mods(Gtk.Box): self.controller.register_widget("modtreeview", self.tree) self.emitter = controller.get_emitter() + # TODO: move + default_steam_path = self.controller.query_config(Preferences.DEFAULT) + steam_path = Path(default_steam_path) + uid = find_user_id(steam_path) + + pretty_uid = "" if uid is None else uid + hbox = HBox(spacing=10) + workshop_button = SteamWorkshopButton() + workshop_button.connect( + "clicked", lambda _: self.controller.open_user_workshop(pretty_uid) + ) self.offline_button = Gtk.Button( label="Play offline", halign=Gtk.Align.START, @@ -34,7 +50,10 @@ class Mods(Gtk.Box): ) self.offline_button.connect("clicked", self._on_offline_clicked) - self.add(self.offline_button) + hbox.add(workshop_button) + hbox.add(self.offline_button) + + self.add(hbox) self.add(self.box) self.connect("map", self._on_map) diff --git a/dzgui/views/pages/offline.py b/dzgui/views/pages/offline.py index ff618a9..fe20d5f 100644 --- a/dzgui/views/pages/offline.py +++ b/dzgui/views/pages/offline.py @@ -1,6 +1,6 @@ from __future__ import annotations from enum import Enum -from typing import Self, Sequence, TYPE_CHECKING, Union +from typing import Self, TYPE_CHECKING, Union from dzgui.util import css from dzgui.const.constants import ( @@ -16,6 +16,7 @@ from dzgui.const.constants import ( from dzgui.const.enum import ContextMenuGroup, NotebookPage from dzgui.managers.offline import OfflineManager from dzgui.strings import generic, offline +from dzgui.views.components.box import HBox, VBox from dzgui.views.components.buttons import Icon, IconTextButton from dzgui.views.components.eventbox import InfoEventBox from dzgui.views.components.frame import HeadingFrame @@ -39,25 +40,6 @@ class FolderError(Enum): NO_VALID_MISSION = 2 -class GenericBox(Gtk.Box): - def __init__(self, orientation: Gtk.Orientation, spacing: int = 0) -> None: - super().__init__(orientation=orientation, spacing=spacing) - - def extend(self, els: Sequence[Gtk.Widget]) -> None: - for el in els: - self.add(el) - - -class HBox(GenericBox): - def __init__(self, spacing: int = 0) -> None: - super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=spacing) - - -class VBox(GenericBox): - def __init__(self, spacing: int = 0) -> None: - super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=spacing) - - class PageHeading(Gtk.Label): def __init__(self, label: str) -> None: super().__init__(label=label, halign=Gtk.Align.CENTER) diff --git a/dzgui/views/pages/options.py b/dzgui/views/pages/options.py index 29b5130..066a7cf 100644 --- a/dzgui/views/pages/options.py +++ b/dzgui/views/pages/options.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import TYPE_CHECKING from dzgui.api import pefile as PeFile -from dzgui.api.steam import find_user_id + from dzgui.config import query from dzgui.const.constants import ( APPID_DAYZ, @@ -19,9 +19,8 @@ from dzgui.const.endpoints import STEAM_API_SETUP, BM_API_SETUP from dzgui.const.enum import Preferences, ServerTab from dzgui.strings import errors, options from dzgui.util import strings, css, open_links -from dzgui.views.components.buttons import SteamWorkshopButton + from dzgui.views.components.labels import LeftLabel -from dzgui.views.components.eventbox import InfoEventBox from dzgui.views.components.buttons import WebButton from dzgui.views.components.frame import HeadingFrame from dzgui.views.components.misc import ClientCombo @@ -134,16 +133,6 @@ class Options(Gtk.Box): [LeftLabel(strings.options.name), self.player_box], ] - eb = InfoEventBox(options.workshop_eventbox, controller) - - workshop_button = SteamWorkshopButton() - workshop_button.connect( - "clicked", lambda _: self.controller.open_user_workshop(self.uid) - ) - mod_rows = [ - [LeftLabel(options.workshop_label), workshop_button, eb], - ] - self.dayz_version_label = Gtk.Label(label=strings.null) self.dayz_exp_version_label = Gtk.Label(label=strings.null) @@ -169,7 +158,6 @@ class Options(Gtk.Box): api_box.add(api_links_box) prefs_grid = self._make_grid(pref_rows) - mods_grid = self._make_grid(mod_rows) version_grid = self._make_grid(version_rows) col = 1 @@ -190,7 +178,6 @@ class Options(Gtk.Box): for pair in [ (api_box, strings.options.api_keys), (prefs_grid, strings.options.prefs), - (mods_grid, strings.options.mods), (version_grid, strings.options.version), ]: @@ -416,9 +403,6 @@ class Options(Gtk.Box): bm = self.controller.query_config(Preferences.BM) steam_path = Path(default_steam_path) - # NOTE: this is a best effort guess at the most recent user - uid = find_user_id(steam_path) - self.uid = "" if uid is None else uid self.old_steam = steam self.old_bm = bm diff --git a/dzgui/views/pages/preconnect.py b/dzgui/views/pages/preconnect.py index aa1dc15..02ec5fc 100644 --- a/dzgui/views/pages/preconnect.py +++ b/dzgui/views/pages/preconnect.py @@ -112,18 +112,6 @@ class PreConnectionAssistant(Gtk.Box): tooltip_text=preconnect.connect_last_tooltip, ) - # TODO: abstract - self.raise_window = Gtk.CheckButton( - label="Foreground DZGUI while downloading", - halign=Gtk.Align.END, - hexpand=True, - valign=Gtk.Align.END, - visible=False, - has_tooltip=True, - sensitive=False, - tooltip_text="Foreground the DZGUI window after mod downloads are queued", - active=True, - ) self.button_box = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, valign=Gtk.Align.END, @@ -133,8 +121,7 @@ class PreConnectionAssistant(Gtk.Box): box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) for button in self.back, self.ok, self.connect_last: box.add(button) - for el in self.raise_window, box: - self.button_box.add(el) + self.button_box.add(box) self.back.connect("clicked", self._on_back_clicked) self.ok.connect("clicked", self._on_ok_clicked) @@ -217,8 +204,6 @@ class PreConnectionAssistant(Gtk.Box): for child in widgets: child.set_visible(True) - self.raise_window.set_visible(False) - self.raise_window.set_sensitive(False) self.ok.set_sensitive(True) self.ok.set_label(preconnect.update_mods) @@ -230,10 +215,10 @@ class PreConnectionAssistant(Gtk.Box): self.ok.emit("clicked") def _on_connect_last_clicked(self, button: Gtk.Button) -> None: - self.controller.update_and_load_to_menu(self.raise_window.get_active()) + self.controller.update_and_load_to_menu() def _on_ok_clicked(self, button: Gtk.Button) -> None: - self.controller.update_and_connect(self.raise_window.get_active()) + self.controller.update_and_connect() def _on_back_clicked(self, button: Gtk.Button) -> None: self.controller.open_page(NotebookPage.SERVERS) @@ -317,10 +302,6 @@ class PreConnectionAssistant(Gtk.Box): if prereqs.required_space == 0: self.ok.set_label(preconnect.connect) - self.raise_window.set_visible(False) - else: - self.raise_window.set_visible(True) - self.raise_window.set_sensitive(True) pretty = number(prereqs.required_space) suffix = f" Need to download {pretty} MiB of mod updates." diff --git a/dzgui/views/pages/servers.py b/dzgui/views/pages/servers.py index 5bbfa7c..caefe21 100644 --- a/dzgui/views/pages/servers.py +++ b/dzgui/views/pages/servers.py @@ -16,7 +16,7 @@ logger = logging.getLogger(APP_NAME) if TYPE_CHECKING: from dzgui.controllers.mc import Controller - from dzgui.controllers.mc import Emitter + from dzgui.controllers.emitter import Emitter class ScrollableTree(Gtk.ScrolledWindow):