Compare commits

..

No commits in common. "27136a09066d1a84db7c6e7145d95ad5b5652eae" and "f839001097fd525815406d1b1f48ff18bfe68998" have entirely different histories.

27 changed files with 36 additions and 137 deletions

View File

@ -10,6 +10,7 @@ from shlex import shlex
from pathlib import Path from pathlib import Path
from dzgui.init.prereqs import has_steam_client from dzgui.init.prereqs import has_steam_client
from dzgui.api.mods import _hash
from dzgui.const.constants import ( from dzgui.const.constants import (
APPID_DAYZ, APPID_DAYZ,
APP_NAME, APP_NAME,
@ -57,9 +58,6 @@ def get_steam_paths() -> list[tuple[Path, str]]:
def concat_mods(mods: list[str]) -> str: def concat_mods(mods: list[str]) -> str:
# TODO: circular import workaround
from dzgui.api.mods import _hash
hashes = [] hashes = []
for mod in mods: for mod in mods:
md5sum = _hash(mod) md5sum = _hash(mod)
@ -172,10 +170,8 @@ def launch_offline(
"-skipintro", "-skipintro",
f"-name={name}", f"-name={name}",
f"-mod={symlinks}", f"-mod={symlinks}",
f"-mission={mission}",
] ]
if len(mission) > 0:
arg = f"-mission={mission}"
params.append(arg)
proc = subprocess.run([*client_args, *params]) proc = subprocess.run([*client_args, *params])
return proc.returncode return proc.returncode

View File

@ -3,8 +3,6 @@ import shlex
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from dzgui.const.boilerplate import config_boilerplate
""" """
Convert legacy dztuirc to config.json Convert legacy dztuirc to config.json
@ -91,8 +89,4 @@ def rc2json(file: Path) -> str:
keys["ip_list"] = ips keys["ip_list"] = ips
keys["use_miles"] = False keys["use_miles"] = False
keys["start_tab"] = 0 keys["start_tab"] = 0
# NOTE: workaround for DZGUI 6 logic (see #375)
for key in config_boilerplate.keys():
if key not in keys:
keys[key] = config_boilerplate[key]
return json.dumps(keys, indent=2) return json.dumps(keys, indent=2)

View File

@ -4,8 +4,7 @@ UDP_PORT = 27016
VM_FILE = "/proc/sys/vm/max_map_count" VM_FILE = "/proc/sys/vm/max_map_count"
MIN_COUNT = 1048576 MIN_COUNT = 1048576
API_RATE_LIMIT = 1 RATE_LIMIT_THRESHOLD = 3
CLIENT_RATE_LIMIT = 3
REQUEST_TIMEOUT = 10 REQUEST_TIMEOUT = 10
APPNAME_DAYZ = "DayZ" APPNAME_DAYZ = "DayZ"

View File

@ -172,7 +172,3 @@ class Emitter(GObject.GObject):
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=()) @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def invalid_custom_mods(self) -> None: def invalid_custom_mods(self) -> None:
pass pass
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
def all_mods_synched(self) -> None:
pass

View File

@ -23,7 +23,7 @@ from dzgui.managers.contextmenu import ContextMenuManager
from dzgui.managers.notes import NoteManager from dzgui.managers.notes import NoteManager
from dzgui.model.servers import ServerModelManager from dzgui.model.servers import ServerModelManager
from dzgui.util.diag import write_diagnostic from dzgui.util.diag import write_diagnostic
from dzgui.util.format import format_exception, format_player_count from dzgui.util.format import format_player_count
from dzgui.util.open_links import open_user_workshop, open_workshop_page from dzgui.util.open_links import open_user_workshop, open_workshop_page
from dzgui.views.dialogs.filepicker import FilePicker from dzgui.views.dialogs.filepicker import FilePicker
from dzgui.views.dialogs.generic import ExceptionDialog from dzgui.views.dialogs.generic import ExceptionDialog
@ -203,9 +203,7 @@ class Controller(GObject.GObject):
try: try:
# TODO: where to put config file check # TODO: where to put config file check
self.mediator.grid.notebook.settings.populate_settings() self.mediator.grid.notebook.settings.populate_settings()
except Exception as e: except Exception:
msg = format_exception(e)
logger.critical(msg)
return return
case ButtonType.MODS: case ButtonType.MODS:
self.load_mods() self.load_mods()

View File

@ -5,7 +5,6 @@ import shutil
from warnings import deprecated from warnings import deprecated
from dzgui.const.constants import ( from dzgui.const.constants import (
APP_NAME,
DAYZ_BINARY, DAYZ_BINARY,
STEAM_CMD, STEAM_CMD,
FLATPAK_APPID, FLATPAK_APPID,
@ -14,10 +13,6 @@ from dzgui.const.constants import (
FLATPAK_SANDBOX, FLATPAK_SANDBOX,
) )
from dzgui.util.format import format_exception
logger = logging.getLogger(APP_NAME)
# TODO: move to util.proc # TODO: move to util.proc
def is_dayz_running() -> bool: def is_dayz_running() -> bool:
@ -27,9 +22,7 @@ def is_dayz_running() -> bool:
for proc in psutil.process_iter(): for proc in psutil.process_iter():
try: try:
procs.append(proc.cmdline()) procs.append(proc.cmdline())
except Exception as e: except Exception:
msg = format_exception(e)
logger.warning(msg)
continue continue
return any(substring in item for sublist in procs for item in sublist) return any(substring in item for sublist in procs for item in sublist)

View File

@ -25,17 +25,17 @@ from dzgui.api.mods import (
update_signatures, update_signatures,
) )
from dzgui.const.constants import ( from dzgui.const.constants import (
API_RATE_LIMIT,
APP_NAME, APP_NAME,
APPID_DAYZ, APPID_DAYZ,
APPID_DAYZ_EXP, APPID_DAYZ_EXP,
APPNAME_DAYZ, APPNAME_DAYZ,
APPNAME_DAYZ_EXP_HUMAN, APPNAME_DAYZ_EXP_HUMAN,
RATE_LIMIT_THRESHOLD,
) )
from dzgui.const.enum import NotebookPage, Preferences from dzgui.const.enum import NotebookPage, Preferences
from dzgui.init.proc import is_dayz_running, is_steam_running from dzgui.init.proc import is_dayz_running, is_steam_running
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
from dzgui.strings.dialogs import waiting_for_launch, waiting_for_mods, waiting_for_directories from dzgui.strings.dialogs import waiting_for_launch, waiting_for_mods
from dzgui.strings.server_mods import checkmark, resync from dzgui.strings.server_mods import checkmark, resync
from dzgui.util.format import format_mib from dzgui.util.format import format_mib
from dzgui.util.strings import dialog, server_timeout from dzgui.util.strings import dialog, server_timeout
@ -276,6 +276,7 @@ class ConnectionManager:
self.thread_man.set_cleanup_func(func) self.thread_man.set_cleanup_func(func)
return return
self.thread_man.show_cancel(False)
self.thread_man.update_dialog(waiting_for_launch) self.thread_man.update_dialog(waiting_for_launch)
while True: while True:
# FIXME: cancel should not be visible here per setting above # FIXME: cancel should not be visible here per setting above
@ -302,9 +303,8 @@ class ConnectionManager:
if self.controller.is_cancel_pending(): if self.controller.is_cancel_pending():
return return
subscribe(key, int(mod)) subscribe(key, int(mod))
time.sleep(API_RATE_LIMIT) time.sleep(RATE_LIMIT_THRESHOLD)
self.thread_man.update_dialog(waiting_for_directories)
for title, mod, stamp, size in self.missing_mods: for title, mod, stamp, size in self.missing_mods:
mod_path = self.workshop / mod mod_path = self.workshop / mod
@ -322,12 +322,8 @@ class ConnectionManager:
time.sleep(1) time.sleep(1)
update_signatures(self.missing_mods, prefs.paths.version) update_signatures(self.missing_mods, prefs.paths.version)
rebuild_symlinks(prefs.paths.config)
# NOTE: update table status in main loop
self.thread_man.update_emitter("all_mods_synched")
# TODO: just push steam path directly # TODO: just push steam path directly
rebuild_symlinks(prefs.paths.config)
self._connect_steam(menu_only) self._connect_steam(menu_only)
@call_on_thread(waiting_for_mods, show_cancel=True) @call_on_thread(waiting_for_mods, show_cancel=True)

View File

@ -12,10 +12,10 @@ from dzgui.api.mods import (
remove_stale_signatures, remove_stale_signatures,
) )
from dzgui.const.constants import ( from dzgui.const.constants import (
API_RATE_LIMIT,
APP_NAME, APP_NAME,
APPID_DAYZ, APPID_DAYZ,
APPID_DAYZ_EXP, APPID_DAYZ_EXP,
RATE_LIMIT_THRESHOLD,
) )
from dzgui.const.enum import Preferences from dzgui.const.enum import Preferences
from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager from dzgui.managers.threading import call_on_thread, StoredFunc, ThreadingManager
@ -23,7 +23,6 @@ from dzgui.model.model_factory import FastInsertListStore, ModelFactory
from dzgui.strings import dialogs from dzgui.strings import dialogs
from dzgui.util.format import format_mods from dzgui.util.format import format_mods
from dzgui.util.strings import server_timeout from dzgui.util.strings import server_timeout
from dzgui.util.symlink import rebuild_symlinks
from dzgui.views.dialogs.generic import ExceptionDialog from dzgui.views.dialogs.generic import ExceptionDialog
@ -67,13 +66,9 @@ class ModManager:
self.thread_man.set_cleanup_func(func) self.thread_man.set_cleanup_func(func)
return return
prefs = self.controller.get_prefs()
rebuild_symlinks(prefs.paths.config)
self.store = ModelFactory().make_mod_store() self.store = ModelFactory().make_mod_store()
self.store.extend(mods) self.store.extend(mods)
func = StoredFunc(self._on_mods_loaded) func = StoredFunc(self._on_mods_loaded)
self.thread_man.set_cleanup_func(func) self.thread_man.set_cleanup_func(func)
def set_store(self, store: "FastInsertListStore") -> None: def set_store(self, store: "FastInsertListStore") -> None:
@ -98,7 +93,6 @@ class ModManager:
continue continue
mod, _iter = res mod, _iter = res
mods.append((mod, _iter)) mods.append((mod, _iter))
self.thread_man.set_job_count(len(mods))
self.unsub_all_mods(mods) self.unsub_all_mods(mods)
def get_mod_from_tree_path( def get_mod_from_tree_path(
@ -115,8 +109,6 @@ class ModManager:
def unsub_all_mods(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: for mod, _iter in mods:
self.unsub_atomic_mod(mod) self.unsub_atomic_mod(mod)
self.thread_man.increment_dialog()
time.sleep(API_RATE_LIMIT)
iters = [_iter for mod, _iter in mods] iters = [_iter for mod, _iter in mods]
func = StoredFunc(self._on_mods_unsubbed, iters) func = StoredFunc(self._on_mods_unsubbed, iters)
@ -144,7 +136,7 @@ class ModManager:
symlink.unlink() symlink.unlink()
except PeFile.AppNotInstalledError: except PeFile.AppNotInstalledError:
pass pass
time.sleep(API_RATE_LIMIT) time.sleep(RATE_LIMIT_THRESHOLD)
def _on_mods_unsubbed(self, iters: list[Gtk.TreeIter]) -> None: def _on_mods_unsubbed(self, iters: list[Gtk.TreeIter]) -> None:
if self.store is None: if self.store is None:

View File

@ -81,11 +81,6 @@ class ThreadingManager:
def set_job_count(self, jobs: int) -> None: def set_job_count(self, jobs: int) -> None:
self.jobs = jobs self.jobs = jobs
def update_emitter(self, signal: str) -> None:
if self.controller:
emitter = self.controller.get_emitter()
GLib.idle_add(lambda: emitter.emit(signal))
def update_dialog(self, msg: str) -> None: def update_dialog(self, msg: str) -> None:
GLib.idle_add(lambda: self.wait_dialog.update_text(msg)) GLib.idle_add(lambda: self.wait_dialog.update_text(msg))

View File

@ -1,6 +1,5 @@
waiting_for_launch = "Waiting for DayZ to launch" waiting_for_launch = "Waiting for DayZ to launch"
waiting_for_mods = "Queueing up mods (step 1/2)" waiting_for_mods = "Waiting for Steam to update mods"
waiting_for_directories = "Steam is staging mods (step 2/2)"
fetching_update = "Fetching update" fetching_update = "Fetching update"
failed_to_update = "Failed to update DZGUI executable" failed_to_update = "Failed to update DZGUI executable"

View File

@ -10,7 +10,6 @@ warnings = "Warnings"
errors = "Errors" errors = "Errors"
mods = "Mods" mods = "Mods"
total_mods = "Total mods: " total_mods = "Total mods: "
up_to_date = "All mods are up to date."
all_updated = "All mods updated." all_updated = "All mods updated."
"If you recently installed {build} or moved it to a different drive, " "If you recently installed {build} or moved it to a different drive, "
"restart Steam to allow these changes to synchronize, then try again." "restart Steam to allow these changes to synchronize, then try again."

View File

@ -392,7 +392,7 @@ class Options(Gtk.Box):
if prefs.paths.config.is_file() is False: if prefs.paths.config.is_file() is False:
dialog = ExceptionDialog(self.controller, strings.config_not_found) dialog = ExceptionDialog(self.controller, strings.config_not_found)
dialog.run() dialog.run()
raise OSError(f"Config file '{prefs.paths.config}' not found") raise Exception
config = query.get_config(prefs.paths.config) config = query.get_config(prefs.paths.config)

View File

@ -23,7 +23,6 @@ from gi.repository import Gdk, Gtk # noqa E402
if TYPE_CHECKING: if TYPE_CHECKING:
from dzgui.managers.connection import Prerequisites from dzgui.managers.connection import Prerequisites
from dzgui.controllers.mc import Controller from dzgui.controllers.mc import Controller
from dzgui.controllers.emitter import Emitter
@dataclass @dataclass
@ -192,14 +191,9 @@ class PreConnectionAssistant(Gtk.Box):
self.add(self.scrolled_box) self.add(self.scrolled_box)
self.add(self.button_box) self.add(self.button_box)
emitter = self.controller.get_emitter()
emitter.connect("all_mods_synched", self._on_mods_synched)
self.connect("key-press-event", self._on_keypress) self.connect("key-press-event", self._on_keypress)
self.connect("map", self._on_map) self.connect("map", self._on_map)
def _on_mods_synched(self, emitter: "Emitter") -> None:
self.tree.mark_mods_synched()
def _on_map(self, widget: Self) -> None: def _on_map(self, widget: Self) -> None:
widgets = ( widgets = (
self.tree_frame, self.tree_frame,
@ -303,16 +297,16 @@ class PreConnectionAssistant(Gtk.Box):
self._hide_mod_area() self._hide_mod_area()
else: else:
self._show_mod_area() self._show_mod_area()
msg = preconnect.up_to_date msg = "All mods are up to date."
self.mod_count.set_text(msg) self.mod_count.set_text(msg)
if prereqs.required_space != 0: if prereqs.required_space == 0:
self.ok.set_label(preconnect.connect)
pretty = number(prereqs.required_space) pretty = number(prereqs.required_space)
suffix = f" Need to download {pretty} MiB of mod updates." suffix = f" Need to download {pretty} MiB of mod updates."
prefix = preconnect.total_mods prefix = preconnect.total_mods
self.mod_count.set_text(f"{prefix}{str(total_mods)}.{suffix}") self.mod_count.set_text(f"{prefix}{str(total_mods)}.{suffix}")
else:
self.ok.set_label(preconnect.connect)
if prereqs.is_last_server: if prereqs.is_last_server:
self.connect_last.show() self.connect_last.show()

View File

@ -48,10 +48,6 @@ class ServerModTreeView(ContextMixin, TreeView): # type: ignore
case _: case _:
pass pass
def mark_mods_synched(self) -> None:
for row in self.mod_store:
row[2] = server_mods.checkmark
# TODO: could be problematic if user downloads mods out of band # TODO: could be problematic if user downloads mods out of band
def _on_row_activated( def _on_row_activated(
self, self,

View File

@ -107,7 +107,6 @@ config-settings-package = { pygobject-stubs = { config = "Gtk3,Gdk3,Soup2" } }
[tool.pytest.ini_options] [tool.pytest.ini_options]
markers = [ markers = [
"config: config file keys/values", "config: config file keys/values",
"mods: tests mod metadata/link creation",
"pefile: validate PE files", "pefile: validate PE files",
"post_install: requires a completed installation", "post_install: requires a completed installation",
"slow: long-running tests", "slow: long-running tests",

3
tests/fixtures/cpp/test.py vendored Normal file
View File

@ -0,0 +1,3 @@
from dzgui.api.mods import tokenize
print(tokenize("meta4.cpp"))

View File

@ -1,42 +0,0 @@
#Path to DayZ installation
steam_path="/tmp"
#Battlemetrics API key
api_key=""
#Favorited server IP:PORT array
ip_list=(
)
#Favorite server to fast-connect to (limit one)
fav_server=""
#Favorite server label (human readable)
fav_label=""
#Custom player name (optional, required by some servers)
name=""
#Set to 1 to perform dry-run and print launch options
debug="1"
#Toggle stable/testing branch
branch="testing"
#Steam API key
steam_api=""
#Auto-install mods
auto_install="2"
#Automod staging directory
staging_dir="/tmp"
#Path to default Steam client
default_steam_path="/tmp"
#Preferred Steam launch command (for Flatpak support)
preferred_client="steam"
#DZGUI source path
src_path=""

View File

@ -1,7 +1,6 @@
import json import json
import pytest import pytest
from dzgui.const.boilerplate import config_boilerplate
from dzgui.config.query import get_config from dzgui.config.query import get_config
from dzgui.config.xdg import get_xdg_paths, parse_filepaths from dzgui.config.xdg import get_xdg_paths, parse_filepaths
from dzgui.config import convert from dzgui.config import convert
@ -12,11 +11,7 @@ pytestmark = pytest.mark.config
@pytest.fixture @pytest.fixture
def legacy_config(): def legacy_config():
return fixture_path("dztuirc_1") return fixture_path("dztuirc_one")
@pytest.fixture
def unset_values():
return fixture_path("dztuirc_3")
@pytest.fixture @pytest.fixture
@ -65,8 +60,8 @@ def test_contains_invalid_values(keys, config):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"fixture, expect", "fixture, expect",
[ [
("dztuirc_1", (False, True, True)), ("dztuirc_one", (False, True, True)),
("dztuirc_2", (False, False, False)), ("dztuirc_two", (False, False, False)),
], ],
) )
def test_bool_conversion(fixture, expect): def test_bool_conversion(fixture, expect):
@ -91,12 +86,5 @@ def test_key_conversion(legacy_config):
for key in keys: for key in keys:
assert key not in j assert key not in j
def test_missing_values(unset_values):
j = convert.rc2json(unset_values)
j = json.loads(j)
assert j.keys() == config_boilerplate.keys()
# TODO: test that when a config file is created from scratch, it contains all values # TODO: test that when a config file is created from scratch, it contains all values

View File

@ -1,13 +1,17 @@
import pytest import pytest
from pathlib import Path
from dzgui.api.mods import tokenize from dzgui.api.mods import tokenize
from tests.fixtures import fixture_path from tests.fixtures import fixture_path
@pytest.mark.mods @pytest.mark.mods
@pytest.mark.parametrize("i", range(1, 7)) @pytest.mark.parametrize("fixture", [
def test_modmeta(i: int) -> None: fixture_path("cpp/meta1.cpp"),
fixture = fixture_path(f"cpp/mod{i}") fixture_path("cpp/meta2.cpp"),
path = Path(fixture) fixture_path("cpp/meta3.cpp"),
meta = tokenize(path) fixture_path("cpp/meta4.cpp"),
fixture_path("cpp/meta5.cpp"),
fixture_path("cpp/meta6.cpp"),
]
)
def test_modmeta(fixture: str) -> None:
meta = tokenize(fixture)
assert meta["name"] == "ModName" assert meta["name"] == "ModName"