mirror of
https://github.com/aclist/dztui.git
synced 2026-08-25 17:32:36 +02:00
feat: unsubscribe from mods gracefully
This commit is contained in:
parent
179f92234a
commit
8d77d46eda
@ -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)
|
||||
@ -73,21 +76,16 @@ def get_local_signatures(version_file: Path) -> dict[str, int]:
|
||||
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)
|
||||
needs_update: list[tuple[str, str, int, int]] = []
|
||||
for title, _id, _hash, size in remote_hashes:
|
||||
for title, _id, mod_hash, size in remote_hashes:
|
||||
if _id not in local_hashes:
|
||||
needs_update.append((title, _id, _hash, size))
|
||||
needs_update.append((title, _id, mod_hash, size))
|
||||
elif _hash != local_hashes[_id]:
|
||||
needs_update.append((title, _id, _hash, size))
|
||||
needs_update.append((title, _id, mod_hash, size))
|
||||
else:
|
||||
continue
|
||||
return needs_update
|
||||
@ -232,6 +230,28 @@ 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 +264,8 @@ 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"
|
||||
|
||||
@ -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
|
||||
@ -294,15 +295,15 @@ class ConnectionManager:
|
||||
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
|
||||
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)
|
||||
subscribe(key, mod)
|
||||
time.sleep(RATE_LIMIT_THRESHOLD)
|
||||
|
||||
if raise_window is True:
|
||||
logger.info("Bringing window to foreground")
|
||||
|
||||
@ -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, 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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user