Merge pull request 'clan_vm_manager: Fix vms not shutting down after closing GTK app. Sync JoinList with ClanStore' (#906) from Qubasa-main into main
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user