From 4d1437b5cc8eb9c62f8f4cf1bb0ea56df0628914 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Tue, 27 Feb 2024 03:53:19 +0700 Subject: [PATCH] clan-vm-manager: Moved switch from list view to VM object. --- .../clan_vm_manager/models/executor.py | 10 ++- .../clan_vm_manager/models/use_views.py | 4 -- .../clan_vm_manager/models/use_vms.py | 34 ++++++++++- .../clan_vm_manager/views/list.py | 61 ++++--------------- .../clan_vm_manager/windows/main_window.py | 1 - 5 files changed, 51 insertions(+), 59 deletions(-) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py b/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py index 9388006e..33e7f570 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py @@ -3,6 +3,7 @@ import os import signal import sys import traceback +from datetime import datetime from pathlib import Path from typing import Any @@ -63,6 +64,7 @@ def _init_proc( out_file: Path, proc_name: str, on_except: Callable[[Exception, mp.process.BaseProcess], None] | None, + tstart: datetime, **kwargs: Any, ) -> None: # Create a new process group @@ -85,8 +87,8 @@ def _init_proc( linebreak = "=" * 5 # Execute the main function - print(linebreak + f"{func.__name__}:{pid}" + linebreak, file=sys.stderr) - + print(linebreak + f" {func.__name__}:{pid} " + linebreak, file=sys.stderr) + print(f"Spawn overhead time: {datetime.now() - tstart}s", file=sys.stderr) try: func(**kwargs) except Exception as ex: @@ -111,6 +113,8 @@ def spawn( func: Callable, **kwargs: Any, ) -> MPProcess: + tstart = datetime.now() + # Decouple the process from the parent if mp.get_start_method(allow_none=True) is None: mp.set_start_method(method="forkserver") @@ -121,7 +125,7 @@ def spawn( # Start the process proc = mp.Process( target=_init_proc, - args=(func, out_file, proc_name, on_except), + args=(func, out_file, proc_name, on_except, tstart), name=proc_name, kwargs=kwargs, ) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_views.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_views.py index 00fd0afe..4f997cce 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_views.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_views.py @@ -22,7 +22,6 @@ class Views: _instance: "None | Views" = None view: Adw.ViewStack - main_window: Adw.ApplicationWindow = None # Make sure the VMS class is used as a singleton def __init__(self) -> None: @@ -35,6 +34,3 @@ class Views: cls.view = Adw.ViewStack() return cls._instance - - def set_main_window(self, window: Adw.ApplicationWindow) -> None: - self.main_window = window diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py index 89654133..b05a776f 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py @@ -139,11 +139,38 @@ class VM(GObject.Object): # To be able to set the switch state programmatically # we need to store the handler id returned by the connect method # and block the signal while we change the state. This is cursed. - self.switch_handler_id: int = 0 + self.switch = Gtk.Switch() + self.switch_handler_id: int = self.switch.connect( + "notify::active", self.on_switch_toggle + ) + self.connect("vm_status_changed", self.vm_status_changed) # Make sure the VM is killed when the reference to this object is dropped self._finalizer = weakref.finalize(self, self.kill_ref_drop) + def vm_status_changed(self, vm: "VM", _vm: "VM") -> None: + self.switch.set_state(self.is_running() and not self.is_building()) + if self.switch.get_sensitive() is False and not self.is_building(): + self.switch.set_sensitive(True) + + exit_vm = self.vm_process.proc.exitcode + exit_build = self.build_process.proc.exitcode + exitc = exit_vm or exit_build + if not self.is_running() and exitc != 0: + self.switch.handler_block(self.switch_handler_id) + self.switch.set_active(False) + self.switch.handler_unblock(self.switch_handler_id) + log.error(f"VM exited with error. Exitcode: {exitc}") + + def on_switch_toggle(self, switch: Gtk.Switch, user_state: bool) -> None: + if switch.get_active(): + switch.set_state(False) + self.start() + else: + switch.set_state(True) + self.shutdown() + switch.set_sensitive(False) + # We use a context manager to create the machine object # and make sure it is destroyed when the context is exited @contextmanager @@ -175,6 +202,7 @@ class VM(GObject.Object): def __start(self) -> None: with self.create_machine() as machine: # Start building VM + tstart = datetime.now() log.info(f"Building VM {self.get_id()}") log_dir = Path(str(self.log_dir.name)) self.build_process = spawn( @@ -203,6 +231,8 @@ class VM(GObject.Object): # Wait for the build to finish then hide the progress bar self.build_process.proc.join() + tend = datetime.now() + log.info(f"VM {self.get_id()} build took {tend - tstart}s") self.progress_bar.hide() # Check if the VM was built successfully @@ -313,9 +343,11 @@ class VM(GObject.Object): def shutdown(self) -> None: if not self.is_running(): log.warning("VM not running. Ignoring shutdown request.") + self.emit("vm_status_changed", self) return if self.is_shutting_down(): log.warning("Shutdown already in progress") + self.emit("vm_status_changed", self) return self._stop_thread = threading.Thread(target=self.__stop) self._stop_thread.start() diff --git a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py index 6f65f75f..d6850f90 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -9,7 +9,6 @@ from clan_cli.clan_uri import ClanURI from clan_vm_manager.models.interfaces import ClanConfig from clan_vm_manager.models.use_join import Join, JoinValue -from clan_vm_manager.models.use_views import Views from clan_vm_manager.models.use_vms import VMs gi.require_version("Adw", "1") @@ -67,16 +66,17 @@ class ClanList(Gtk.Box): ) self.group_list.add_css_class("group-list") - search_bar = Gtk.SearchBar() - # This widget will typically be the top-level window - search_bar.set_key_capture_widget(Views.use().main_window) - entry = Gtk.SearchEntry() - entry.set_placeholder_text("Search cLan") - entry.connect("search-changed", self.on_search_changed) - entry.add_css_class("search-entry") - search_bar.set_child(entry) + # disable search bar because of unsound handling of VM objects + # search_bar = Gtk.SearchBar() + # # This widget will typically be the top-level window + # search_bar.set_key_capture_widget(Views.use().main_window) + # entry = Gtk.SearchEntry() + # entry.set_placeholder_text("Search cLan") + # entry.connect("search-changed", self.on_search_changed) + # entry.add_css_class("search-entry") + # search_bar.set_child(entry) - self.append(search_bar) + # self.append(search_bar) self.append(self.join_boxed_list) self.append(self.group_list) @@ -169,11 +169,9 @@ class ClanList(Gtk.Box): row.add_suffix(box) # This allows children to have different sizes # ==== Action buttons ==== - switch = Gtk.Switch() - switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) switch_box.set_valign(Gtk.Align.CENTER) - switch_box.append(switch) + switch_box.append(vm.switch) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) box.set_valign(Gtk.Align.CENTER) @@ -193,11 +191,6 @@ class ClanList(Gtk.Box): box.append(switch_box) box.append(pref_button) - vm.switch_handler_id = switch.connect( - "notify::active", partial(self.on_row_toggle, vm) - ) - vm.connect("vm_status_changed", partial(self.vm_status_changed, switch)) - # suffix.append(box) row.add_suffix(box) @@ -254,15 +247,6 @@ class ClanList(Gtk.Box): return row - def show_error_dialog(self, error: str) -> None: - p = Views.use().main_window - - dialog = Adw.MessageDialog(heading="Error") - dialog.add_response("ok", "ok") - dialog.set_body(error) - dialog.set_transient_for(p) # set the parent window of the dialog - dialog.choose() - def on_join_request(self, widget: Any, url: str) -> None: log.debug("Join request: %s", url) clan_uri = ClanURI.from_str(url) @@ -287,26 +271,3 @@ class ClanList(Gtk.Box): Join.use().discard(item) if not Join.use().list_store.get_n_items(): self.join_boxed_list.add_css_class("no-shadow") - - def on_row_toggle(self, vm: VM, switch: Gtk.Switch, user_state: bool) -> None: - if switch.get_active(): - switch.set_state(False) - vm.start() - else: - switch.set_state(True) - vm.shutdown() - switch.set_sensitive(False) - - def vm_status_changed(self, switch: Gtk.Switch, vm: VM, _vm: VM) -> None: - switch.set_state(vm.is_running() and not vm.is_building()) - if switch.get_sensitive() is False and not vm.is_building(): - switch.set_sensitive(True) - - exit_vm = vm.vm_process.proc.exitcode - exit_build = vm.build_process.proc.exitcode - exitc = exit_vm or exit_build - if not vm.is_running() and exitc != 0: - switch.handler_block(vm.switch_handler_id) - switch.set_active(False) - switch.handler_unblock(vm.switch_handler_id) - log.error(f"VM exited with error. Exitcode: {exitc}") diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py index 5720e045..58b614ea 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/main_window.py @@ -33,7 +33,6 @@ class MainWindow(Adw.ApplicationWindow): # Initialize all views stack_view = Views.use().view - Views.use().set_main_window(self) scroll = Gtk.ScrolledWindow() scroll.set_propagate_natural_height(True)