From c52c83002c0eccc0e6df2bff34f8886ea0117eed Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 5 Feb 2024 14:30:47 +0700 Subject: [PATCH] async join --- pkgs/clan-vm-manager/clan_vm_manager/app.py | 5 -- .../clan_vm_manager/models/use_join.py | 48 ++++++++++++------- .../clan_vm_manager/models/use_vms.py | 27 +++++++---- .../clan_vm_manager/views/list.py | 26 ++++++---- .../clan_vm_manager/windows/main_window.py | 2 +- 5 files changed, 67 insertions(+), 41 deletions(-) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 15a6a4159..6139ebee6 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -4,8 +4,6 @@ from pathlib import Path import gi -from clan_vm_manager.models.use_join import Join - gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") @@ -28,9 +26,6 @@ class MainApplication(Adw.Application): self.config = config self.connect("shutdown", self.on_shutdown) - if config.url: - Join.use().push(config.url) - def on_shutdown(self, app: Gtk.Application) -> None: log.debug("Shutting down") VMS.use().kill_all() diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py index b6458e0be..018ecb6fa 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py @@ -1,30 +1,45 @@ import logging +import threading from collections.abc import Callable -from typing import Any +from typing import Any, ClassVar import gi from clan_cli import ClanError from clan_cli.clan_uri import ClanURI -from clan_cli.history.add import HistoryEntry, add_history +from clan_cli.history.add import add_history from clan_vm_manager.errors.show_error import show_error_dialog +from clan_vm_manager.models.use_vms import VMS, Clans gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") -from gi.repository import Gio, GObject +from gi.repository import Gio, GLib, GObject log = logging.getLogger(__name__) class JoinValue(GObject.Object): # TODO: custom signals for async join - # __gsignals__: ClassVar = {} + + __gsignals__: ClassVar = { + "join_finished": (GObject.SignalFlags.RUN_FIRST, None, [GObject.Object]), + } url: ClanURI - def __init__(self, url: ClanURI) -> None: + def __init__( + self, url: ClanURI, on_join: Callable[["JoinValue", Any], None] + ) -> None: super().__init__() self.url = url + self.connect("join_finished", on_join) + + def __join(self) -> None: + add_history(self.url, all_machines=False) + GLib.idle_add(lambda: self.emit("join_finished", self)) + + def join(self) -> None: + threading.Thread(target=self.__join).start() class Join: @@ -48,26 +63,27 @@ class Join: return cls._instance - def push(self, url: ClanURI) -> None: + def push(self, url: ClanURI, on_join: Callable[[JoinValue], None]) -> None: """ Add a join request. This method can add multiple join requests if called subsequently for each request. """ - self.list_store.append(JoinValue(url)) - def join(self, item: JoinValue, cb: Callable[[list[HistoryEntry]], None]) -> None: - # TODO: remove the item that was accepted join from this list - # and call the success function. (The caller is responsible for handling the success) + def after_join(item: JoinValue, _: Any) -> None: + self.discard(item) + Clans.use().refresh() + VMS.use().refresh() + print("Refreshed list after join") + on_join(item) + + self.list_store.append(JoinValue(url, after_join)) + + def join(self, item: JoinValue) -> None: try: log.info(f"trying to join: {item.url}") - - history = add_history(item.url) - cb(history) - self.discard(item) - + item.join() except ClanError as e: show_error_dialog(e) - pass def discard(self, item: JoinValue) -> None: (has, idx) = self.list_store.find(item) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py index 4eab31004..0f319cc41 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py @@ -38,6 +38,18 @@ class ClanGroup(GObject.Object): self.list_store.append(vm) +def init_grp_store(list_store: Gio.ListStore) -> None: + groups: dict[str | Path, list["VM"]] = {} + for vm in get_saved_vms(): + ll = groups.get(vm.data.flake.flake_url, []) + ll.append(vm) + groups[vm.data.flake.flake_url] = ll + + for url, vm_list in groups.items(): + grp = ClanGroup(url, vm_list) + list_store.append(grp) + + class Clans: list_store: Gio.ListStore _instance: "None | ClanGroup" = None @@ -51,19 +63,14 @@ class Clans: if cls._instance is None: cls._instance = cls.__new__(cls) cls.list_store = Gio.ListStore.new(ClanGroup) - - groups: dict[str | Path, list["VM"]] = {} - for vm in get_saved_vms(): - ll = groups.get(vm.data.flake.flake_url, []) - ll.append(vm) - groups[vm.data.flake.flake_url] = ll - - for url, vms in groups.items(): - grp = ClanGroup(url, vms) - cls.list_store.append(grp) + init_grp_store(cls.list_store) return cls._instance + def refresh(self) -> None: + self.list_store.remove_all() + init_grp_store(self.list_store) + class VM(GObject.Object): # Define a custom signal with the name "vm_stopped" and a string argument for the message 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 7341a2353..06beb4b7e 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -3,8 +3,9 @@ from collections.abc import Callable from functools import partial import gi -from clan_cli.history.add import HistoryEntry +from clan_cli import history +from clan_vm_manager.models.interfaces import ClanConfig from clan_vm_manager.models.use_join import Join, JoinValue from clan_vm_manager.models.use_views import Views @@ -42,12 +43,16 @@ class ClanList(Gtk.Box): # ------------------------# """ - def __init__(self) -> None: + def __init__(self, config: ClanConfig) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL) groups = Clans.use() join = Join.use() + if config.url: + join.push(config.url, self.after_join) + + self.__init_machines = history.add.list_history() self.join_boxed_list = create_boxed_list( model=join.list_store, render_row=self.render_join_row ) @@ -152,6 +157,7 @@ class ClanList(Gtk.Box): cancel_button = Gtk.Button(label="Cancel") cancel_button.add_css_class("error") cancel_button.connect("clicked", partial(self.on_discard_clicked, item)) + self.cancel_button = cancel_button trust_button = Gtk.Button(label="Join") trust_button.add_css_class("success") @@ -178,18 +184,20 @@ class ClanList(Gtk.Box): dialog.set_transient_for(p) # set the parent window of the dialog dialog.choose() + def after_join(self, item: JoinValue) -> None: + # If the join request list is empty disable the shadow artefact + if not Join.use().list_store.get_n_items(): + self.join_boxed_list.add_css_class("no-shadow") + print("after join in list") + def on_trust_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None: - def on_join(_history: list[HistoryEntry]) -> None: - VMS.use().refresh() + widget.set_sensitive(False) + self.cancel_button.set_sensitive(False) # TODO(@hsjobeki): Confirm and edit details # Views.use().view.set_visible_child_name("details") - Join.use().join(item, cb=on_join) - - # If the join request list is empty disable the shadow artefact - if not Join.use().list_store.get_n_items(): - self.join_boxed_list.add_css_class("no-shadow") + Join.use().join(item) def on_discard_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None: Join.use().discard(item) 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 b24d41acb..19329ba9a 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 @@ -29,7 +29,7 @@ class MainWindow(Adw.ApplicationWindow): scroll = Gtk.ScrolledWindow() scroll.set_propagate_natural_height(True) scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - scroll.set_child(ClanList()) + scroll.set_child(ClanList(config)) stack_view.add_named(scroll, "list") stack_view.add_named(Details(), "details")