Merge pull request 'async join' (#807) from hsjobeki-main into main

This commit is contained in:
clan-bot
2024-02-05 08:07:51 +00:00
5 changed files with 67 additions and 41 deletions

View File

@@ -4,8 +4,6 @@ from pathlib import Path
import gi import gi
from clan_vm_manager.models.use_join import Join
gi.require_version("Gtk", "4.0") gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1") gi.require_version("Adw", "1")
@@ -28,9 +26,6 @@ class MainApplication(Adw.Application):
self.config = config self.config = config
self.connect("shutdown", self.on_shutdown) self.connect("shutdown", self.on_shutdown)
if config.url:
Join.use().push(config.url)
def on_shutdown(self, app: Gtk.Application) -> None: def on_shutdown(self, app: Gtk.Application) -> None:
log.debug("Shutting down") log.debug("Shutting down")
VMS.use().kill_all() VMS.use().kill_all()

View File

@@ -1,30 +1,45 @@
import logging import logging
import threading
from collections.abc import Callable from collections.abc import Callable
from typing import Any from typing import Any, ClassVar
import gi import gi
from clan_cli import ClanError from clan_cli import ClanError
from clan_cli.clan_uri import ClanURI 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.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("Gtk", "4.0")
gi.require_version("Adw", "1") gi.require_version("Adw", "1")
from gi.repository import Gio, GObject from gi.repository import Gio, GLib, GObject
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class JoinValue(GObject.Object): class JoinValue(GObject.Object):
# TODO: custom signals for async join # TODO: custom signals for async join
# __gsignals__: ClassVar = {}
__gsignals__: ClassVar = {
"join_finished": (GObject.SignalFlags.RUN_FIRST, None, [GObject.Object]),
}
url: ClanURI url: ClanURI
def __init__(self, url: ClanURI) -> None: def __init__(
self, url: ClanURI, on_join: Callable[["JoinValue", Any], None]
) -> None:
super().__init__() super().__init__()
self.url = url 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: class Join:
@@ -48,26 +63,27 @@ class Join:
return cls._instance return cls._instance
def push(self, url: ClanURI) -> None: def push(self, url: ClanURI, on_join: Callable[[JoinValue], 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.
""" """
self.list_store.append(JoinValue(url))
def join(self, item: JoinValue, cb: Callable[[list[HistoryEntry]], None]) -> None: def after_join(item: JoinValue, _: Any) -> None:
# TODO: remove the item that was accepted join from this list self.discard(item)
# and call the success function. (The caller is responsible for handling the success) 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: try:
log.info(f"trying to join: {item.url}") log.info(f"trying to join: {item.url}")
item.join()
history = add_history(item.url)
cb(history)
self.discard(item)
except ClanError as e: except ClanError as e:
show_error_dialog(e) show_error_dialog(e)
pass
def discard(self, item: JoinValue) -> None: def discard(self, item: JoinValue) -> None:
(has, idx) = self.list_store.find(item) (has, idx) = self.list_store.find(item)

View File

@@ -38,6 +38,18 @@ class ClanGroup(GObject.Object):
self.list_store.append(vm) 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: class Clans:
list_store: Gio.ListStore list_store: Gio.ListStore
_instance: "None | ClanGroup" = None _instance: "None | ClanGroup" = None
@@ -51,19 +63,14 @@ class Clans:
if cls._instance is None: if cls._instance is None:
cls._instance = cls.__new__(cls) cls._instance = cls.__new__(cls)
cls.list_store = Gio.ListStore.new(ClanGroup) cls.list_store = Gio.ListStore.new(ClanGroup)
init_grp_store(cls.list_store)
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)
return cls._instance return cls._instance
def refresh(self) -> None:
self.list_store.remove_all()
init_grp_store(self.list_store)
class VM(GObject.Object): class VM(GObject.Object):
# Define a custom signal with the name "vm_stopped" and a string argument for the message # Define a custom signal with the name "vm_stopped" and a string argument for the message

View File

@@ -3,8 +3,9 @@ from collections.abc import Callable
from functools import partial from functools import partial
import gi 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_join import Join, JoinValue
from clan_vm_manager.models.use_views import Views 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) super().__init__(orientation=Gtk.Orientation.VERTICAL)
groups = Clans.use() groups = Clans.use()
join = Join.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( self.join_boxed_list = create_boxed_list(
model=join.list_store, render_row=self.render_join_row 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 = Gtk.Button(label="Cancel")
cancel_button.add_css_class("error") cancel_button.add_css_class("error")
cancel_button.connect("clicked", partial(self.on_discard_clicked, item)) cancel_button.connect("clicked", partial(self.on_discard_clicked, item))
self.cancel_button = cancel_button
trust_button = Gtk.Button(label="Join") trust_button = Gtk.Button(label="Join")
trust_button.add_css_class("success") 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.set_transient_for(p) # set the parent window of the dialog
dialog.choose() 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_trust_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
def on_join(_history: list[HistoryEntry]) -> None: widget.set_sensitive(False)
VMS.use().refresh() self.cancel_button.set_sensitive(False)
# TODO(@hsjobeki): Confirm and edit details # TODO(@hsjobeki): Confirm and edit details
# Views.use().view.set_visible_child_name("details") # Views.use().view.set_visible_child_name("details")
Join.use().join(item, cb=on_join) Join.use().join(item)
# 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")
def on_discard_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None: def on_discard_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None:
Join.use().discard(item) Join.use().discard(item)

View File

@@ -29,7 +29,7 @@ class MainWindow(Adw.ApplicationWindow):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
scroll.set_propagate_natural_height(True) scroll.set_propagate_natural_height(True)
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 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(scroll, "list")
stack_view.add_named(Details(), "details") stack_view.add_named(Details(), "details")