clan-vm-manager: Moved switch from list view to VM object.
This commit is contained in:
parent
58bc8d162d
commit
4d1437b5cc
@ -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
|
||||||
@ -86,7 +88,7 @@ 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,
|
||||||
)
|
)
|
||||||
|
@ -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
|
|
||||||
|
@ -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()
|
||||||
|
@ -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}")
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user