Merge pull request 'async join' (#807) from hsjobeki-main into main
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user