mirror of
https://github.com/aclist/dztui.git
synced 2026-08-27 02:07:18 +02:00
feat: catch cancel and shutdown events
This commit is contained in:
parent
319966f8e1
commit
2e5860650f
@ -47,6 +47,7 @@ if TYPE_CHECKING:
|
||||
from dzgui.views.components.filter_panel import FilterPanel
|
||||
from dzgui.views.components.right_panel import RightPanel
|
||||
from dzgui.views.components.statusbar import Statusbar
|
||||
from dzgui.views.pages.options import Options
|
||||
from dzgui.views.pages.preconnect import PreConnectionAssistant
|
||||
from dzgui.views.pages.servers import ServerNotebook
|
||||
from dzgui.views.trees.tree_log import LogTreeView
|
||||
@ -90,6 +91,7 @@ class Controller(GObject.GObject):
|
||||
self.pending_jobs = 1
|
||||
|
||||
self.exit_event = threading.Event()
|
||||
self.cancel_event = threading.Event()
|
||||
self.connection_man: ConnectionManager
|
||||
|
||||
def get_emitter(self) -> Emitter:
|
||||
@ -463,6 +465,15 @@ class Controller(GObject.GObject):
|
||||
def set_exit_event(self) -> None:
|
||||
self.exit_event.set()
|
||||
|
||||
def set_cancel_event(self) -> None:
|
||||
self.cancel_event.set()
|
||||
|
||||
def get_cancel_event(self) -> threading.Event:
|
||||
return self.cancel_event
|
||||
|
||||
def clear_cancel_event(self) -> None:
|
||||
self.cancel_event.clear()
|
||||
|
||||
def open_connection_assistant(self, prereqs: "Prerequisites") -> None:
|
||||
self.open_page(NotebookPage.CONNECTION)
|
||||
self.mediator.preconnect.populate(prereqs)
|
||||
@ -479,4 +490,3 @@ class Controller(GObject.GObject):
|
||||
|
||||
def get_steam_client_name(self) -> str:
|
||||
return self.mediator.options.get_client_name()
|
||||
|
||||
|
||||
@ -1,27 +1,26 @@
|
||||
import psutil
|
||||
import subprocess
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from dzgui.const.constants import (
|
||||
DAYZ_BINARY,
|
||||
STEAM_CMD,
|
||||
FLATPAK_APPID,
|
||||
FLATPAK_CMD,
|
||||
FLATPAK_RUN_CMD,
|
||||
FLATPAK_SANDBOX,
|
||||
)
|
||||
from dzgui.views.dialogs.early_alert import EarlyAlertDialog
|
||||
from dzgui.util.strings import init
|
||||
|
||||
|
||||
# TODO: move to util.proc
|
||||
def is_dayz_running() -> bool:
|
||||
"""Subprocesses spawned from Steam will not show up in regular process tree"""
|
||||
procs = []
|
||||
substring = DAYZ_BINARY
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
procs.append(proc.cmdline())
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
continue
|
||||
return any(substring in item for sublist in procs for item in sublist)
|
||||
|
||||
@ -37,10 +36,11 @@ def is_steam_running(cmd: str) -> bool:
|
||||
raise TypeError("Not a valid Steam client selection")
|
||||
|
||||
|
||||
# CHORE: test alternate clients
|
||||
def is_flatpak_steam_running() -> bool:
|
||||
if has_cmd(FLATPAK_CMD) is False:
|
||||
return False
|
||||
proc = subprocess.check_output([FLATPAK_CMD, "ps"], capture_output=True, text=True)
|
||||
proc = subprocess.check_output([FLATPAK_CMD, "ps"], text=True)
|
||||
lines = proc.stdout.splitlines()
|
||||
if FLATPAK_APPID in lines:
|
||||
return True
|
||||
|
||||
@ -75,7 +75,6 @@ class Prerequisites:
|
||||
dayz_running: bool
|
||||
steam_proc: SteamProcess
|
||||
mods: list[str]
|
||||
missing_mods: int
|
||||
foreground_cmd: str | None
|
||||
game_mode: bool
|
||||
|
||||
@ -184,7 +183,6 @@ class ConnectionManager:
|
||||
dayz_running=dayz_running,
|
||||
steam_proc=steam_proc,
|
||||
mods=remote_mods,
|
||||
missing_mods=len(self.missing_mods),
|
||||
foreground_cmd=self.foreground_cmd, # TODO: change to bool
|
||||
game_mode=game_mode,
|
||||
)
|
||||
@ -245,7 +243,6 @@ class ConnectionManager:
|
||||
dialog = ExceptionDialog(self.controller, server_timeout)
|
||||
dialog.run()
|
||||
|
||||
# TODO: threading
|
||||
def _connect_steam(self) -> None:
|
||||
addr = f"{self.record.ip}:{self.record.gameport}"
|
||||
playername = self.controller.query_config(Preferences.NAME)
|
||||
@ -256,8 +253,12 @@ class ConnectionManager:
|
||||
self.thread_man.set_cleanup_func(func)
|
||||
return
|
||||
|
||||
self.thread_man.show_cancel(False)
|
||||
self.thread_man.update_dialog(waiting_for_launch)
|
||||
while True:
|
||||
if self.controller.get_exit_event().is_set():
|
||||
# TODO: some facility to also close spawned steam process
|
||||
return
|
||||
if is_dayz_running():
|
||||
break
|
||||
time.sleep(1)
|
||||
@ -290,6 +291,12 @@ class ConnectionManager:
|
||||
while True:
|
||||
# NOTE: mods will finish at the same time
|
||||
# TODO: check for early cancel event
|
||||
# TODO: check global sigint event
|
||||
if self.controller.get_exit_event().is_set():
|
||||
return
|
||||
if self.controller.get_cancel_event().is_set():
|
||||
self.controller.clear_cancel_event()
|
||||
return
|
||||
cur_size = get_mod_dir_size(mod_path)
|
||||
if cur_size == size:
|
||||
break
|
||||
@ -298,11 +305,11 @@ class ConnectionManager:
|
||||
update_signatures(self.missing_mods, prefs.paths.version)
|
||||
# TODO: get config path or just push steam path directly
|
||||
rebuild_symlinks(prefs.paths.config)
|
||||
self._connect_steam()
|
||||
|
||||
@call_on_thread("Waiting for Steam to update mods")
|
||||
@call_on_thread("Waiting for Steam to update mods", show_cancel=True)
|
||||
def update_and_connect(self, raise_window: bool) -> None:
|
||||
if len(self.missing_mods) > 0:
|
||||
self._update_mods(raise_window)
|
||||
self._connect_steam()
|
||||
else:
|
||||
self._connect_steam()
|
||||
|
||||
@ -20,7 +20,9 @@ from gi.repository import Gtk, GLib # noqa E402
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
|
||||
|
||||
def call_on_thread(dialog_str: str, show_dialog: bool = True) -> Callable:
|
||||
def call_on_thread(
|
||||
dialog_str: str, show_dialog: bool = True, show_cancel: bool = False
|
||||
) -> Callable:
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> None:
|
||||
@ -32,7 +34,7 @@ def call_on_thread(dialog_str: str, show_dialog: bool = True) -> Callable:
|
||||
raise TypeError(
|
||||
"Attribute 'thread_man' must be of type 'ThreadingManager'"
|
||||
)
|
||||
self.thread_man.call_on_thread(dialog_str, stored, show_dialog)
|
||||
self.thread_man.call_on_thread(dialog_str, stored, show_dialog, show_cancel)
|
||||
|
||||
return wrapper
|
||||
|
||||
@ -57,15 +59,22 @@ class ThreadingManager:
|
||||
self.destroy_first = False
|
||||
|
||||
def call_on_thread(
|
||||
self, dialog_str: str, func: StoredFunc, show_dialog: bool = True
|
||||
self,
|
||||
dialog_str: str,
|
||||
func: StoredFunc,
|
||||
show_dialog: bool = True,
|
||||
show_cancel: bool = False,
|
||||
) -> None:
|
||||
def callback() -> None:
|
||||
func.call()
|
||||
GLib.idle_add(self._destroy_on_idle)
|
||||
|
||||
if show_dialog:
|
||||
self.wait_dialog = WaitDialog(self.controller, dialog_str, jobs=self.jobs)
|
||||
self.wait_dialog = WaitDialog(
|
||||
self.controller, dialog_str, jobs=self.jobs, show_cancel=show_cancel
|
||||
)
|
||||
self.wait_dialog.show_all()
|
||||
|
||||
thread = threading.Thread(target=callback)
|
||||
thread.start()
|
||||
|
||||
@ -113,3 +122,6 @@ class ThreadingManager:
|
||||
|
||||
def get_wait_dialog(self) -> WaitDialog:
|
||||
return self.wait_dialog
|
||||
|
||||
def show_cancel(self, state: bool) -> None:
|
||||
GLib.idle_add(self.wait_dialog.show_cancel, state)
|
||||
|
||||
@ -64,7 +64,7 @@ class ServerModCols:
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class MenuCols:
|
||||
name: str
|
||||
hidden: object # GObject.TYPE_PYOBJECT
|
||||
hidden: object
|
||||
|
||||
|
||||
class FastInsertListStore(ListStore):
|
||||
@ -108,6 +108,7 @@ class FastInsertListStore(ListStore):
|
||||
self.append(row)
|
||||
|
||||
def append(self, row: list[Any] | tuple[Any, ...] | None = ...) -> TreeIter:
|
||||
# FIXME: argument cannot be none
|
||||
"""
|
||||
Optimized for speed, but makes no assurances about row homogeneity
|
||||
and may segfault if types and length are not identical to ListStore.
|
||||
|
||||
@ -91,6 +91,7 @@ class Statusbar(Gtk.Grid):
|
||||
|
||||
self.set_by_context(enum, bar)
|
||||
|
||||
# TODO: unused
|
||||
def _on_notebook_page_returned(
|
||||
self, statusbar: Self, prior_context: NotebookPage
|
||||
) -> None:
|
||||
@ -109,8 +110,9 @@ class Statusbar(Gtk.Grid):
|
||||
context: Union["ServerTab", NotebookPage],
|
||||
) -> None:
|
||||
self.spinner.stop()
|
||||
# TODO: CalcDist is being called when table is not loaded
|
||||
if dist is None:
|
||||
self.set_by_context(context, self.playercount)
|
||||
self.set_by_context(context, "")
|
||||
else:
|
||||
pretty = self.append_distance(dist)
|
||||
self.set_by_context(context, pretty)
|
||||
|
||||
@ -87,7 +87,13 @@ class NotifyDialog(GenericDialog):
|
||||
|
||||
|
||||
class WaitDialog(GenericDialog):
|
||||
def __init__(self, controller: "Controller", secondary: str, jobs: int = 1):
|
||||
def __init__(
|
||||
self,
|
||||
controller: "Controller",
|
||||
secondary: str,
|
||||
jobs: int = 1,
|
||||
show_cancel: bool = False,
|
||||
):
|
||||
super().__init__(
|
||||
controller=controller,
|
||||
text=strings.wait,
|
||||
@ -96,19 +102,29 @@ class WaitDialog(GenericDialog):
|
||||
secondary=secondary,
|
||||
)
|
||||
|
||||
self.controller = controller
|
||||
self.jobs = jobs
|
||||
self.cur_job = 1
|
||||
|
||||
self.cancel = Gtk.Button(label="Cancel", halign=Gtk.Align.CENTER)
|
||||
self.cancel.connect("clicked", lambda _: self.controller.set_cancel_event())
|
||||
|
||||
self.connect("delete-event", lambda widget, event: True)
|
||||
content = self.get_content_area()
|
||||
spinner = Gtk.Spinner()
|
||||
self.prog = Gtk.ProgressBar()
|
||||
|
||||
content.pack_end(self.cancel, NO_EXPAND, NO_FILL, 0)
|
||||
content.pack_end(spinner, NO_EXPAND, NO_FILL, 0)
|
||||
|
||||
if self.jobs > 1:
|
||||
content.pack_end(self.prog, NO_EXPAND, NO_FILL, 0)
|
||||
else:
|
||||
spinner.start()
|
||||
|
||||
if show_cancel is False:
|
||||
self.connect("realize", lambda _: self.cancel.set_visible(False))
|
||||
|
||||
def update_text(self, msg: str) -> None:
|
||||
self.format_secondary_text(msg)
|
||||
|
||||
@ -119,6 +135,9 @@ class WaitDialog(GenericDialog):
|
||||
self.prog.set_fraction(fraction)
|
||||
self.cur_job += 1
|
||||
|
||||
def show_cancel(self, state: bool) -> None:
|
||||
self.cancel.set_visible(state)
|
||||
|
||||
|
||||
class QuitDialog(GenericDialog):
|
||||
def __init__(self, controller: "Controller", secondary: str):
|
||||
|
||||
@ -135,7 +135,7 @@ class ServerModDialog(ServerDialog):
|
||||
columns = [
|
||||
server_mods.mod,
|
||||
server_mods.mod_id,
|
||||
server_mods.installed,
|
||||
server_mods.up_to_date,
|
||||
]
|
||||
for i, column_title in enumerate(columns):
|
||||
renderer = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
|
||||
|
||||
@ -203,8 +203,6 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
||||
child.set_visible(True)
|
||||
self.raise_window.set_visible(False)
|
||||
|
||||
# TODO: enable button if wmctrl or xdotool is available
|
||||
# TODO: check this at boot time and pass in via connection manager
|
||||
self.raise_window.set_sensitive(False)
|
||||
self.ok.set_sensitive(True)
|
||||
self.ok.set_label(preconnect.update_mods)
|
||||
@ -228,8 +226,6 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
||||
warnings: list[str] = []
|
||||
errors: list[str] = []
|
||||
|
||||
# TODO: check availability of currently selected steam cmd
|
||||
|
||||
"""Errors"""
|
||||
if prereqs.binary_missing:
|
||||
errors.append(
|
||||
@ -273,6 +269,17 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
||||
self.error_placeholder.set_visible(False)
|
||||
self.ok.set_sensitive(False)
|
||||
|
||||
def _hide_mod_area(self) -> None:
|
||||
self.scrolled.set_visible(False)
|
||||
self.progress_box.set_visible(False)
|
||||
self.mods_placeholder.set_visible(True)
|
||||
|
||||
def _show_mod_area(self) -> None:
|
||||
self.scrolled.set_visible(True)
|
||||
self.progress_box.set_visible(True)
|
||||
self.mods_placeholder.set_visible(False)
|
||||
self.tree.grab_focus()
|
||||
|
||||
def populate(self, prereqs: "Prerequisites") -> None:
|
||||
mods = prereqs.mods
|
||||
self.tree.populate(mods)
|
||||
@ -281,27 +288,23 @@ class PreConnectionAssistant(Gtk.ScrolledWindow):
|
||||
name = prereqs.name
|
||||
self.title.set_text(name)
|
||||
|
||||
if prereqs.foreground_cmd is not None:
|
||||
self.raise_window.set_sensitive(True)
|
||||
suffix = "All mods are up to date."
|
||||
|
||||
if prereqs.missing_mods < 1:
|
||||
self.ok.set_label(preconnect.connect)
|
||||
if total_mods < 1:
|
||||
self.scrolled.set_visible(False)
|
||||
self.progress_box.set_visible(False)
|
||||
self.mods_placeholder.set_visible(True)
|
||||
self._hide_mod_area()
|
||||
else:
|
||||
self.scrolled.set_visible(True)
|
||||
self.progress_box.set_visible(True)
|
||||
self.raise_window.set_visible(True)
|
||||
self.mods_placeholder.set_visible(False)
|
||||
self.tree.grab_focus()
|
||||
self._show_mod_area()
|
||||
|
||||
# TODO: strings
|
||||
suffix = "All mods are up to date."
|
||||
if prereqs.required_space > 0:
|
||||
pretty = number(prereqs.required_space)
|
||||
suffix = f" Need to download {pretty} MiB of mod updates."
|
||||
if prereqs.required_space == 0:
|
||||
self.ok.set_label(preconnect.connect)
|
||||
self.raise_window.set_visible(False)
|
||||
else:
|
||||
self.raise_window.set_visible(True)
|
||||
if prereqs.foreground_cmd is not None:
|
||||
self.raise_window.set_sensitive(True)
|
||||
|
||||
pretty = number(prereqs.required_space)
|
||||
suffix = f" Need to download {pretty} MiB of mod updates."
|
||||
prefix = preconnect.total_mods
|
||||
self.mod_count.set_text(f"{prefix}{str(total_mods)}.{suffix}")
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user