diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 5b1c9ee3..ecc240c2 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -49,7 +49,20 @@ class MainApplication(Adw.Application): ) 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: options = command_line.get_options_dict() @@ -74,7 +87,9 @@ class MainApplication(Adw.Application): return 0 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(): self.window.hide() else: @@ -83,13 +98,13 @@ class MainApplication(Adw.Application): def dummy_menu_entry(self) -> None: log.info("Dummy menu entry called") - def show_window(self, *_args: Any) -> None: + def on_activate(self, app: Any) -> None: if not self.window: self.init_style() self.window = MainWindow(config=ClanConfig(initial_view="list")) self.window.set_application(self) - self.window.present() + self.window.show() # TODO: For css styling def init_style(self) -> None: diff --git a/pkgs/clan-vm-manager/clan_vm_manager/components/profiler.py b/pkgs/clan-vm-manager/clan_vm_manager/components/profiler.py index 597a6993..ed6ca5c2 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/components/profiler.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/components/profiler.py @@ -79,7 +79,9 @@ class ProfilerStore: print("=" * 7 + key + "=" * 7) print_profile(profiler, pstats.SortKey.TIME) print_profile(profiler, pstats.SortKey.CUMULATIVE) - print(explanation) + + if len(self.profilers) > 0: + print(explanation) def trim_path_to_three_levels(path: str) -> str: @@ -108,9 +110,8 @@ def profile(func: Callable) -> Callable: res = func(*args, **kwargs) profiler.disable() except Exception as ex: - log.exception(ex) profiler.disable() - return None + raise ex return res if os.getenv("PERF", "0") == "1": diff --git a/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py b/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py index b34031d7..ee1830ed 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/components/vmobj.py @@ -37,6 +37,9 @@ class VMObject(GObject.Object): self.emit("vm_status_changed", self) return GLib.SOURCE_REMOVE + def update(self, data: HistoryEntry) -> None: + self.data = data + def __init__( self, icon: Path, @@ -296,6 +299,7 @@ class VMObject(GObject.Object): return log.info(f"Killing VM {self.get_id()} now") self.vm_process.kill_group() + self.build_process.kill_group() def read_whole_log(self) -> str: if not self.vm_process.out_file.exists(): diff --git a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py index 850af550..a7a8b57a 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_join.py @@ -63,19 +63,31 @@ class JoinList: cls._instance = cls.__new__(cls) 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 + 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: return self.list_store.get_n_items() == 0 def push( - self, value: JoinValue, after_join: Callable[[JoinValue, JoinValue], None] + self, uri: ClanURI, after_join: Callable[[JoinValue, JoinValue], None] ) -> None: """ Add a join 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]: log.info(f"Join request already exists: {value.url}. Ignoring.") return diff --git a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py index c76ebdf7..0581fb19 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/singletons/use_vms.py @@ -63,7 +63,7 @@ class ClanStore: def push(self, vm: VMObject) -> None: 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 if url not in self.clan_store: log.debug(f"Creating new VMStore for {url}") @@ -71,9 +71,18 @@ class ClanStore: vm_store.append(vm) self.clan_store[url] = vm_store else: - log.debug(f"Appending VM {vm.data.flake.flake_attr} to store") vm_store = self.clan_store[url] - vm_store.append(vm) + 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) def remove(self, vm: VMObject) -> None: del self.clan_store[vm.data.flake.flake_url][vm.data.flake.flake_attr] 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 3f8f6296..797bd778 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -184,13 +184,15 @@ class ClanList(Gtk.Box): if boxed_list.has_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_subtitle(item.url.get_internal()) 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() row.set_subtitle( 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: log.debug("Join request: %s", url) clan_uri = ClanURI.from_str(url) - value = JoinValue(url=clan_uri) - JoinList.use().push(value, self.on_after_join) + JoinList.use().push(clan_uri, self.on_after_join) def on_after_join(self, source: JoinValue, item: JoinValue) -> None: # If the join request list is empty disable the shadow artefact 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 681e0d3b..01163d75 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 @@ -65,6 +65,11 @@ class MainWindow(Adw.ApplicationWindow): for entry in list_history(): GLib.idle_add(ClanStore.use().create_vm_task, entry) - def on_destroy(self, *_args: Any) -> None: - self.tray_icon.destroy() + def kill_vms(self) -> None: + log.debug("Killing all VMs") ClanStore.use().kill_all() + + def on_destroy(self, *_args: Any) -> None: + log.info("====Destroying Adw.ApplicationWindow===") + ClanStore.use().kill_all() + self.tray_icon.destroy()