clan_vm_manager: Fix vms not shutting down after closing GTK app. Sync JoinList with ClanStore
All checks were successful
checks / check-links (pull_request) Successful in 21s
checks / checks-impure (pull_request) Successful in 1m55s
checks / checks (pull_request) Successful in 2m21s

This commit is contained in:
Luis Hebendanz 2024-03-05 23:10:30 +07:00
parent 06bc425797
commit 580c63e760
7 changed files with 64 additions and 17 deletions

View File

@ -49,7 +49,20 @@ class MainApplication(Adw.Application):
) )
self.window: Adw.ApplicationWindow | None = None self.window: Adw.ApplicationWindow | None = None
self.connect("activate", self.show_window) self.connect("activate", self.on_activate)
self.connect("shutdown", self.on_shutdown)
def on_shutdown(self, *_args: Any) -> None:
log.debug("Shutting down Adw.Application")
log.debug(f"get_windows: {self.get_windows()}")
if self.window:
# TODO: Doesn't seem to raise the destroy signal. Need to investigate
# self.get_windows() returns an empty list. Desync between window and application?
self.window.close()
# Killing vms directly. This is dirty
self.window.kill_vms()
else:
log.error("No window to destroy")
def do_command_line(self, command_line: Any) -> int: def do_command_line(self, command_line: Any) -> int:
options = command_line.get_options_dict() options = command_line.get_options_dict()
@ -74,7 +87,9 @@ class MainApplication(Adw.Application):
return 0 return 0
def on_window_hide_unhide(self, *_args: Any) -> None: def on_window_hide_unhide(self, *_args: Any) -> None:
assert self.window is not None if not self.window:
log.error("No window to hide/unhide")
return
if self.window.is_visible(): if self.window.is_visible():
self.window.hide() self.window.hide()
else: else:
@ -83,13 +98,13 @@ class MainApplication(Adw.Application):
def dummy_menu_entry(self) -> None: def dummy_menu_entry(self) -> None:
log.info("Dummy menu entry called") log.info("Dummy menu entry called")
def show_window(self, *_args: Any) -> None: def on_activate(self, app: Any) -> None:
if not self.window: if not self.window:
self.init_style() self.init_style()
self.window = MainWindow(config=ClanConfig(initial_view="list")) self.window = MainWindow(config=ClanConfig(initial_view="list"))
self.window.set_application(self) self.window.set_application(self)
self.window.present() self.window.show()
# TODO: For css styling # TODO: For css styling
def init_style(self) -> None: def init_style(self) -> None:

View File

@ -79,6 +79,8 @@ class ProfilerStore:
print("=" * 7 + key + "=" * 7) print("=" * 7 + key + "=" * 7)
print_profile(profiler, pstats.SortKey.TIME) print_profile(profiler, pstats.SortKey.TIME)
print_profile(profiler, pstats.SortKey.CUMULATIVE) print_profile(profiler, pstats.SortKey.CUMULATIVE)
if len(self.profilers) > 0:
print(explanation) print(explanation)
@ -108,9 +110,8 @@ def profile(func: Callable) -> Callable:
res = func(*args, **kwargs) res = func(*args, **kwargs)
profiler.disable() profiler.disable()
except Exception as ex: except Exception as ex:
log.exception(ex)
profiler.disable() profiler.disable()
return None raise ex
return res return res
if os.getenv("PERF", "0") == "1": if os.getenv("PERF", "0") == "1":

View File

@ -37,6 +37,9 @@ class VMObject(GObject.Object):
self.emit("vm_status_changed", self) self.emit("vm_status_changed", self)
return GLib.SOURCE_REMOVE return GLib.SOURCE_REMOVE
def update(self, data: HistoryEntry) -> None:
self.data = data
def __init__( def __init__(
self, self,
icon: Path, icon: Path,
@ -296,6 +299,7 @@ class VMObject(GObject.Object):
return return
log.info(f"Killing VM {self.get_id()} now") log.info(f"Killing VM {self.get_id()} now")
self.vm_process.kill_group() self.vm_process.kill_group()
self.build_process.kill_group()
def read_whole_log(self) -> str: def read_whole_log(self) -> str:
if not self.vm_process.out_file.exists(): if not self.vm_process.out_file.exists():

View File

@ -63,19 +63,31 @@ class JoinList:
cls._instance = cls.__new__(cls) cls._instance = cls.__new__(cls)
cls.list_store = Gio.ListStore.new(JoinValue) cls.list_store = Gio.ListStore.new(JoinValue)
# Rerendering the join list every time an item changes in the clan_store
ClanStore.use().clan_store.connect(
"items-changed", cls._instance.on_clan_store_items_changed
)
return cls._instance return cls._instance
def on_clan_store_items_changed(
self, source: Any, position: int, removed: int, added: int
) -> None:
self.list_store.items_changed(
0, self.list_store.get_n_items(), self.list_store.get_n_items()
)
def is_empty(self) -> bool: def is_empty(self) -> bool:
return self.list_store.get_n_items() == 0 return self.list_store.get_n_items() == 0
def push( def push(
self, value: JoinValue, after_join: Callable[[JoinValue, JoinValue], None] self, uri: ClanURI, after_join: Callable[[JoinValue, JoinValue], None]
) -> None: ) -> None:
""" """
Add a join request. Add a join request.
This method can add multiple join requests if called subsequently for each request. This method can add multiple join requests if called subsequently for each request.
""" """
value = JoinValue(uri)
if value.url.get_id() in [item.url.get_id() for item in self.list_store]: if value.url.get_id() in [item.url.get_id() for item in self.list_store]:
log.info(f"Join request already exists: {value.url}. Ignoring.") log.info(f"Join request already exists: {value.url}. Ignoring.")
return return

View File

@ -63,7 +63,7 @@ class ClanStore:
def push(self, vm: VMObject) -> None: def push(self, vm: VMObject) -> None:
url = vm.data.flake.flake_url url = vm.data.flake.flake_url
# Only write to the store if the VM is not already in it # Only write to the store if the Clan is not already in it
# Every write to the KVStore rerenders bound widgets to the clan_store # Every write to the KVStore rerenders bound widgets to the clan_store
if url not in self.clan_store: if url not in self.clan_store:
log.debug(f"Creating new VMStore for {url}") log.debug(f"Creating new VMStore for {url}")
@ -71,8 +71,17 @@ class ClanStore:
vm_store.append(vm) vm_store.append(vm)
self.clan_store[url] = vm_store self.clan_store[url] = vm_store
else: else:
log.debug(f"Appending VM {vm.data.flake.flake_attr} to store")
vm_store = self.clan_store[url] vm_store = self.clan_store[url]
machine = vm.data.flake.flake_attr
old_vm = vm_store.get(machine)
if old_vm:
log.info(
f"VM {vm.data.flake.flake_attr} already exists in store. Updating data field."
)
old_vm.update(vm.data)
else:
log.debug(f"Appending VM {vm.data.flake.flake_attr} to store")
vm_store.append(vm) vm_store.append(vm)
def remove(self, vm: VMObject) -> None: def remove(self, vm: VMObject) -> None:

View File

@ -184,13 +184,15 @@ class ClanList(Gtk.Box):
if boxed_list.has_css_class("no-shadow"): if boxed_list.has_css_class("no-shadow"):
boxed_list.remove_css_class("no-shadow") boxed_list.remove_css_class("no-shadow")
row = Adw.ActionRow() log.debug("Rendering join row for %s", item.url)
row = Adw.ActionRow()
row.set_title(item.url.params.flake_attr) row.set_title(item.url.params.flake_attr)
row.set_subtitle(item.url.get_internal()) row.set_subtitle(item.url.get_internal())
row.add_css_class("trust") row.add_css_class("trust")
if item.url.params.flake_attr in ClanStore.use().clan_store: # Can't do this here because clan store is empty at this point
if item.url.get_internal() in ClanStore.use().clan_store:
sub = row.get_subtitle() sub = row.get_subtitle()
row.set_subtitle( row.set_subtitle(
sub + "\nClan already exists. Joining again will update it" sub + "\nClan already exists. Joining again will update it"
@ -223,8 +225,7 @@ class ClanList(Gtk.Box):
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)
value = JoinValue(url=clan_uri) JoinList.use().push(clan_uri, self.on_after_join)
JoinList.use().push(value, self.on_after_join)
def on_after_join(self, source: JoinValue, item: JoinValue) -> None: def on_after_join(self, source: JoinValue, item: JoinValue) -> None:
# If the join request list is empty disable the shadow artefact # If the join request list is empty disable the shadow artefact

View File

@ -65,6 +65,11 @@ class MainWindow(Adw.ApplicationWindow):
for entry in list_history(): for entry in list_history():
GLib.idle_add(ClanStore.use().create_vm_task, entry) GLib.idle_add(ClanStore.use().create_vm_task, entry)
def on_destroy(self, *_args: Any) -> None: def kill_vms(self) -> None:
self.tray_icon.destroy() log.debug("Killing all VMs")
ClanStore.use().kill_all() ClanStore.use().kill_all()
def on_destroy(self, *_args: Any) -> None:
log.info("====Destroying Adw.ApplicationWindow===")
ClanStore.use().kill_all()
self.tray_icon.destroy()