mirror of
https://github.com/aclist/dztui.git
synced 2026-08-27 02:07:18 +02:00
commit
668ec1d7a0
96
dzgui/api/acf.py
Normal file
96
dzgui/api/acf.py
Normal file
@ -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]
|
||||
@ -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])
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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."
|
||||
|
||||
25
dzgui/views/components/box.py
Normal file
25
dzgui/views/components/box.py
Normal file
@ -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)
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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."
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user