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.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:

View File

@ -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":

View File

@ -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():

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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()