clan-vm-manager: Moved switch from list view to VM object.
All checks were successful
checks / check-links (pull_request) Successful in 23s
checks / checks-impure (pull_request) Successful in 2m0s
checks / checks (pull_request) Successful in 2m39s

This commit is contained in:
Luis Hebendanz 2024-02-27 03:53:19 +07:00
parent 58bc8d162d
commit 4d1437b5cc
5 changed files with 51 additions and 59 deletions

View File

@ -3,6 +3,7 @@ import os
import signal import signal
import sys import sys
import traceback import traceback
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -63,6 +64,7 @@ def _init_proc(
out_file: Path, out_file: Path,
proc_name: str, proc_name: str,
on_except: Callable[[Exception, mp.process.BaseProcess], None] | None, on_except: Callable[[Exception, mp.process.BaseProcess], None] | None,
tstart: datetime,
**kwargs: Any, **kwargs: Any,
) -> None: ) -> None:
# Create a new process group # Create a new process group
@ -85,8 +87,8 @@ def _init_proc(
linebreak = "=" * 5 linebreak = "=" * 5
# Execute the main function # 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: try:
func(**kwargs) func(**kwargs)
except Exception as ex: except Exception as ex:
@ -111,6 +113,8 @@ def spawn(
func: Callable, func: Callable,
**kwargs: Any, **kwargs: Any,
) -> MPProcess: ) -> MPProcess:
tstart = datetime.now()
# Decouple the process from the parent # Decouple the process from the parent
if mp.get_start_method(allow_none=True) is None: if mp.get_start_method(allow_none=True) is None:
mp.set_start_method(method="forkserver") mp.set_start_method(method="forkserver")
@ -121,7 +125,7 @@ def spawn(
# Start the process # Start the process
proc = mp.Process( proc = mp.Process(
target=_init_proc, target=_init_proc,
args=(func, out_file, proc_name, on_except), args=(func, out_file, proc_name, on_except, tstart),
name=proc_name, name=proc_name,
kwargs=kwargs, kwargs=kwargs,
) )

View File

@ -22,7 +22,6 @@ class Views:
_instance: "None | Views" = None _instance: "None | Views" = None
view: Adw.ViewStack view: Adw.ViewStack
main_window: Adw.ApplicationWindow = None
# Make sure the VMS class is used as a singleton # Make sure the VMS class is used as a singleton
def __init__(self) -> None: def __init__(self) -> None:
@ -35,6 +34,3 @@ class Views:
cls.view = Adw.ViewStack() cls.view = Adw.ViewStack()
return cls._instance return cls._instance
def set_main_window(self, window: Adw.ApplicationWindow) -> None:
self.main_window = window

View File

@ -139,11 +139,38 @@ class VM(GObject.Object):
# To be able to set the switch state programmatically # To be able to set the switch state programmatically
# we need to store the handler id returned by the connect method # we need to store the handler id returned by the connect method
# and block the signal while we change the state. This is cursed. # 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 # Make sure the VM is killed when the reference to this object is dropped
self._finalizer = weakref.finalize(self, self.kill_ref_drop) 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 # We use a context manager to create the machine object
# and make sure it is destroyed when the context is exited # and make sure it is destroyed when the context is exited
@contextmanager @contextmanager
@ -175,6 +202,7 @@ class VM(GObject.Object):
def __start(self) -> None: def __start(self) -> None:
with self.create_machine() as machine: with self.create_machine() as machine:
# Start building VM # Start building VM
tstart = datetime.now()
log.info(f"Building VM {self.get_id()}") log.info(f"Building VM {self.get_id()}")
log_dir = Path(str(self.log_dir.name)) log_dir = Path(str(self.log_dir.name))
self.build_process = spawn( self.build_process = spawn(
@ -203,6 +231,8 @@ class VM(GObject.Object):
# Wait for the build to finish then hide the progress bar # Wait for the build to finish then hide the progress bar
self.build_process.proc.join() self.build_process.proc.join()
tend = datetime.now()
log.info(f"VM {self.get_id()} build took {tend - tstart}s")
self.progress_bar.hide() self.progress_bar.hide()
# Check if the VM was built successfully # Check if the VM was built successfully
@ -313,9 +343,11 @@ class VM(GObject.Object):
def shutdown(self) -> None: def shutdown(self) -> None:
if not self.is_running(): if not self.is_running():
log.warning("VM not running. Ignoring shutdown request.") log.warning("VM not running. Ignoring shutdown request.")
self.emit("vm_status_changed", self)
return return
if self.is_shutting_down(): if self.is_shutting_down():
log.warning("Shutdown already in progress") log.warning("Shutdown already in progress")
self.emit("vm_status_changed", self)
return return
self._stop_thread = threading.Thread(target=self.__stop) self._stop_thread = threading.Thread(target=self.__stop)
self._stop_thread.start() self._stop_thread.start()

View File

@ -9,7 +9,6 @@ from clan_cli.clan_uri import ClanURI
from clan_vm_manager.models.interfaces import ClanConfig from clan_vm_manager.models.interfaces import ClanConfig
from clan_vm_manager.models.use_join import Join, JoinValue 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 from clan_vm_manager.models.use_vms import VMs
gi.require_version("Adw", "1") gi.require_version("Adw", "1")
@ -67,16 +66,17 @@ class ClanList(Gtk.Box):
) )
self.group_list.add_css_class("group-list") self.group_list.add_css_class("group-list")
search_bar = Gtk.SearchBar() # disable search bar because of unsound handling of VM objects
# This widget will typically be the top-level window # search_bar = Gtk.SearchBar()
search_bar.set_key_capture_widget(Views.use().main_window) # # This widget will typically be the top-level window
entry = Gtk.SearchEntry() # search_bar.set_key_capture_widget(Views.use().main_window)
entry.set_placeholder_text("Search cLan") # entry = Gtk.SearchEntry()
entry.connect("search-changed", self.on_search_changed) # entry.set_placeholder_text("Search cLan")
entry.add_css_class("search-entry") # entry.connect("search-changed", self.on_search_changed)
search_bar.set_child(entry) # 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.join_boxed_list)
self.append(self.group_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 row.add_suffix(box) # This allows children to have different sizes
# ==== Action buttons ==== # ==== Action buttons ====
switch = Gtk.Switch()
switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
switch_box.set_valign(Gtk.Align.CENTER) 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 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
box.set_valign(Gtk.Align.CENTER) box.set_valign(Gtk.Align.CENTER)
@ -193,11 +191,6 @@ class ClanList(Gtk.Box):
box.append(switch_box) box.append(switch_box)
box.append(pref_button) 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) # suffix.append(box)
row.add_suffix(box) row.add_suffix(box)
@ -254,15 +247,6 @@ class ClanList(Gtk.Box):
return row 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: def on_join_request(self, widget: Any, url: str) -> None:
log.debug("Join request: %s", url) log.debug("Join request: %s", url)
clan_uri = ClanURI.from_str(url) clan_uri = ClanURI.from_str(url)
@ -287,26 +271,3 @@ class ClanList(Gtk.Box):
Join.use().discard(item) Join.use().discard(item)
if not Join.use().list_store.get_n_items(): if not Join.use().list_store.get_n_items():
self.join_boxed_list.add_css_class("no-shadow") 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}")

View File

@ -33,7 +33,6 @@ class MainWindow(Adw.ApplicationWindow):
# Initialize all views # Initialize all views
stack_view = Views.use().view stack_view = Views.use().view
Views.use().set_main_window(self)
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
scroll.set_propagate_natural_height(True) scroll.set_propagate_natural_height(True)