From f17cf410932c8f78b6490f7647a67440e5716bea Mon Sep 17 00:00:00 2001 From: Qubasa Date: Sun, 3 Mar 2024 11:21:12 +0700 Subject: [PATCH 1/5] clan-vm-manager: Fix incorrect use of all Glib.idle_add uses --- .../clan_vm_manager/models/use_join.py | 6 +++++- .../clan_vm_manager/models/use_vms.py | 18 +++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) 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 ad9ca8be5..e186b3017 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 @@ -26,6 +26,10 @@ class JoinValue(GObject.Object): url: ClanURI + def join_finished(self) -> bool: + self.emit("join_finished", self) + return GLib.SOURCE_REMOVE + def __init__( self, url: ClanURI, on_join: Callable[["JoinValue", Any], None] ) -> None: @@ -35,7 +39,7 @@ class JoinValue(GObject.Object): def __join(self) -> None: add_history(self.url, all_machines=False) - GLib.idle_add(self.emit, "join_finished", self) + GLib.idle_add(self.join_finished) def join(self) -> None: threading.Thread(target=self.__join).start() 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 5b79ecfbf..e96f15463 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 @@ -35,6 +35,10 @@ class VM(GObject.Object): "vm_status_changed": (GObject.SignalFlags.RUN_FIRST, None, [GObject.Object]) } + def vm_status_changed(self) -> bool: + self.emit("vm_status_changed", self) + return GLib.SOURCE_REMOVE + def __init__( self, icon: Path, @@ -75,12 +79,12 @@ class VM(GObject.Object): self.switch_handler_id: int = self.switch.connect( "notify::active", self.on_switch_toggle ) - self.connect("vm_status_changed", self.vm_status_changed) + self.connect("vm_status_changed", self.on_vm_status_changed) # Make sure the VM is killed when the reference to this object is dropped self._finalizer = weakref.finalize(self, self.kill_ref_drop) - def vm_status_changed(self, vm: "VM", _vm: "VM") -> None: + def on_vm_status_changed(self, vm: "VM", _vm: "VM") -> None: self.switch.set_state(self.is_running() and not self.is_building()) if self.switch.get_sensitive() is False and not self.is_building(): self.switch.set_sensitive(True) @@ -145,7 +149,7 @@ class VM(GObject.Object): tmpdir=log_dir, vm=self.data.flake.vm, ) - GLib.idle_add(self.emit, "vm_status_changed", self) + GLib.idle_add(self.vm_status_changed) # Start the logs watcher self._logs_id = GLib.timeout_add( @@ -170,7 +174,7 @@ class VM(GObject.Object): # Check if the VM was built successfully if self.build_process.proc.exitcode != 0: log.error(f"Failed to build VM {self.get_id()}") - GLib.idle_add(self.emit, "vm_status_changed", self) + GLib.idle_add(self.vm_status_changed) return log.info(f"Successfully built VM {self.get_id()}") @@ -182,7 +186,7 @@ class VM(GObject.Object): vm=self.data.flake.vm, ) log.debug(f"Started VM {self.get_id()}") - GLib.idle_add(self.emit, "vm_status_changed", self) + GLib.idle_add(self.vm_status_changed) # Start the logs watcher self._logs_id = GLib.timeout_add(50, self._get_logs_task, self.vm_process) @@ -193,7 +197,7 @@ class VM(GObject.Object): # Wait for the VM to stop self.vm_process.proc.join() log.debug(f"VM {self.get_id()} has stopped") - GLib.idle_add(self.emit, "vm_status_changed", self) + GLib.idle_add(self.vm_status_changed) def start(self) -> None: if self.is_running(): @@ -269,7 +273,7 @@ class VM(GObject.Object): # Try 20 times to stop the VM time.sleep(self.KILL_TIMEOUT / 20) - GLib.idle_add(self.emit, "vm_status_changed", self) + GLib.idle_add(self.vm_status_changed) log.debug(f"VM {self.get_id()} has stopped") def shutdown(self) -> None: From 6f80cee971eff29c7531b65d5ef7bc1105c56172 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Sun, 3 Mar 2024 12:47:18 +0700 Subject: [PATCH 2/5] clan_cli: history_add now returns newly added HistoryEntry. clan-vm-manager: Join now uses signals instead of callbacks. --- pkgs/clan-cli/clan_cli/history/add.py | 59 +++++++++-------- .../clan_vm_manager/models/use_join.py | 48 ++++++-------- .../clan_vm_manager/views/list.py | 66 +++++++++---------- 3 files changed, 84 insertions(+), 89 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/history/add.py b/pkgs/clan-cli/clan_cli/history/add.py index 3f126b2eb..16bb96987 100644 --- a/pkgs/clan-cli/clan_cli/history/add.py +++ b/pkgs/clan-cli/clan_cli/history/add.py @@ -35,14 +35,14 @@ class HistoryEntry: self.flake = FlakeConfig(**self.flake) -def merge_dicts(d1: dict, d2: dict) -> dict: +def _merge_dicts(d1: dict, d2: dict) -> dict: # create a new dictionary that copies d1 merged = dict(d1) # iterate over the keys and values of d2 for key, value in d2.items(): # if the key is in d1 and both values are dictionaries, merge them recursively if key in d1 and isinstance(d1[key], dict) and isinstance(value, dict): - merged[key] = merge_dicts(d1[key], value) + merged[key] = _merge_dicts(d1[key], value) # otherwise, update the value of the key in the merged dictionary else: merged[key] = value @@ -59,7 +59,7 @@ def list_history() -> list[HistoryEntry]: parsed = read_history_file() for i, p in enumerate(parsed.copy()): # Everything from the settings dict is merged into the flake dict, and can override existing values - parsed[i] = merge_dicts(p, p.get("settings", {})) + parsed[i] = _merge_dicts(p, p.get("settings", {})) logs = [HistoryEntry(**p) for p in parsed] except (json.JSONDecodeError, TypeError) as ex: raise ClanError(f"History file at {user_history_file()} is corrupted") from ex @@ -76,40 +76,47 @@ def new_history_entry(url: str, machine: str) -> HistoryEntry: ) -def add_history(uri: ClanURI, *, all_machines: bool) -> list[HistoryEntry]: +def add_all_to_history(uri: ClanURI) -> list[HistoryEntry]: + history = list_history() + new_entries: list[HistoryEntry] = [] + for machine in list_machines(uri.get_internal()): + new_entry = _add_maschine_to_history_list(uri.get_internal(), machine, history) + new_entries.append(new_entry) + write_history_file(history) + return new_entries + + +def add_history(uri: ClanURI) -> HistoryEntry: user_history_file().parent.mkdir(parents=True, exist_ok=True) history = list_history() - if not all_machines: - add_maschine_to_history(uri.get_internal(), uri.params.flake_attr, history) - - if all_machines: - for machine in list_machines(uri.get_internal()): - add_maschine_to_history(uri.get_internal(), machine, history) - + new_entry = _add_maschine_to_history_list( + uri.get_internal(), uri.params.flake_attr, history + ) write_history_file(history) - return history + return new_entry -def add_maschine_to_history( - uri_path: str, uri_machine: str, logs: list[HistoryEntry] -) -> None: - found = False - - for entry in logs: +def _add_maschine_to_history_list( + uri_path: str, uri_machine: str, entries: list[HistoryEntry] +) -> HistoryEntry: + for new_entry in entries: if ( - entry.flake.flake_url == str(uri_path) - and entry.flake.flake_attr == uri_machine + new_entry.flake.flake_url == str(uri_path) + and new_entry.flake.flake_attr == uri_machine ): - found = True - entry.last_used = datetime.datetime.now().isoformat() + new_entry.last_used = datetime.datetime.now().isoformat() + return new_entry - if not found: - history = new_history_entry(uri_path, uri_machine) - logs.append(history) + new_entry = new_history_entry(uri_path, uri_machine) + entries.append(new_entry) + return new_entry def add_history_command(args: argparse.Namespace) -> None: - add_history(args.uri, all_machines=args.all) + if args.all: + add_all_to_history(args.uri) + else: + add_history(args.uri) # takes a (sub)parser and configures it 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 e186b3017..e7ce8a313 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,15 +1,11 @@ import logging import threading -from collections.abc import Callable 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 add_history -from clan_vm_manager.errors.show_error import show_error_dialog - gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Gio, GLib, GObject @@ -26,32 +22,29 @@ class JoinValue(GObject.Object): url: ClanURI - def join_finished(self) -> bool: + def _join_finished(self) -> bool: self.emit("join_finished", self) return GLib.SOURCE_REMOVE - def __init__( - self, url: ClanURI, on_join: Callable[["JoinValue", Any], None] - ) -> None: + def __init__(self, url: ClanURI) -> 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(self.join_finished) + GLib.idle_add(self._join_finished) def join(self) -> None: threading.Thread(target=self.__join).start() -class Join: +class JoinList: """ This is a singleton. It is initialized with the first call of use() """ - _instance: "None | Join" = None + _instance: "None | JoinList" = None list_store: Gio.ListStore # Make sure the VMS class is used as a singleton @@ -59,38 +52,35 @@ class Join: raise RuntimeError("Call use() instead") @classmethod - def use(cls: Any) -> "Join": + def use(cls: Any) -> "JoinList": if cls._instance is None: cls._instance = cls.__new__(cls) cls.list_store = Gio.ListStore.new(JoinValue) return cls._instance - def push(self, url: ClanURI, on_join: Callable[[JoinValue], None]) -> None: + def is_empty(self) -> bool: + return self.list_store.get_n_items() == 0 + + def push(self, value: JoinValue) -> None: """ Add a join request. This method can add multiple join requests if called subsequently for each request. """ - if url.get_id() in [item.url.get_id() for item in self.list_store]: - log.info(f"Join request already exists: {url}") + 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}") return - def after_join(item: JoinValue, _: Any) -> None: - self.discard(item) - print("Refreshed list after join") - on_join(item) + value.connect("join_finished", self._on_join_finished) - self.list_store.append(JoinValue(url, after_join)) + self.list_store.append(value) - def join(self, item: JoinValue) -> None: - try: - log.info(f"trying to join: {item.url}") - item.join() - except ClanError as e: - show_error_dialog(e) + def _on_join_finished(self, _source: GObject.Object, value: JoinValue) -> None: + log.info(f"Join finished: {value.url}") + self.discard(value) - def discard(self, item: JoinValue) -> None: - (has, idx) = self.list_store.find(item) + def discard(self, value: JoinValue) -> None: + (has, idx) = self.list_store.find(value) if has: self.list_store.remove(idx) 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 9bb219f37..8aa2c0ffd 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -8,7 +8,7 @@ from clan_cli import history, machines from clan_cli.clan_uri import ClanURI 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 JoinList, JoinValue from clan_vm_manager.models.use_vms import VM, VMs, VMStore gi.require_version("Adw", "1") @@ -54,7 +54,7 @@ class ClanList(Gtk.Box): # Add join list self.join_boxed_list = create_boxed_list( - model=Join.use().list_store, render_row=self.render_join_row + model=JoinList.use().list_store, render_row=self.render_join_row ) self.join_boxed_list.add_css_class("join-list") self.append(self.join_boxed_list) @@ -113,8 +113,10 @@ class ClanList(Gtk.Box): # ====== Display Avatar ====== avatar = Adw.Avatar() - machine_icon = flake.vm.machine_icon + + # If there is a machine icon, display it else + # display the clan icon if machine_icon: avatar.set_custom_image(Gdk.Texture.new_from_filename(str(machine_icon))) elif flake.icon: @@ -128,10 +130,11 @@ class ClanList(Gtk.Box): # ====== Display Name And Url ===== row.set_title(flake.flake_attr) - row.set_title_lines(1) row.set_title_selectable(True) + # If there is a machine description, display it else + # display the clan name if flake.vm.machine_description: row.set_subtitle(flake.vm.machine_description) else: @@ -139,37 +142,35 @@ class ClanList(Gtk.Box): row.set_subtitle_lines(1) # ==== Display build progress bar ==== - box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) - box.set_valign(Gtk.Align.CENTER) - box.append(vm.progress_bar) - box.set_homogeneous(False) - row.add_suffix(box) # This allows children to have different sizes + build_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + build_box.set_valign(Gtk.Align.CENTER) + build_box.append(vm.progress_bar) + build_box.set_homogeneous(False) + row.add_suffix(build_box) # This allows children to have different sizes # ==== Action buttons ==== - switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - switch_box.set_valign(Gtk.Align.CENTER) - switch_box.append(vm.switch) - - box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) - box.set_valign(Gtk.Align.CENTER) + button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + button_box.set_valign(Gtk.Align.CENTER) + ## Drop down menu open_action = Gio.SimpleAction.new("edit", GLib.VariantType.new("s")) open_action.connect("activate", self.on_edit) - app = Gio.Application.get_default() app.add_action(open_action) - menu_model = Gio.Menu() menu_model.append("Edit", f"app.edit::{vm.get_id()}") pref_button = Gtk.MenuButton() pref_button.set_icon_name("open-menu-symbolic") pref_button.set_menu_model(menu_model) + button_box.append(pref_button) - box.append(switch_box) - box.append(pref_button) + ## VM switch button + switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + switch_box.set_valign(Gtk.Align.CENTER) + switch_box.append(vm.switch) + button_box.append(switch_box) - # suffix.append(box) - row.add_suffix(box) + row.add_suffix(button_box) return row @@ -221,24 +222,21 @@ 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) - Join.use().push(clan_uri, self.after_join) + value = JoinValue(url=clan_uri) + value.connect("join_finished", self.on_after_join) + JoinList.use().push(value) - def after_join(self, 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 not Join.use().list_store.get_n_items(): + if JoinList.use().is_empty(): 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, value: JoinValue, widget: Gtk.Widget) -> None: widget.set_sensitive(False) self.cancel_button.set_sensitive(False) + value.join() - # TODO(@hsjobeki): Confirm and edit details - # Views.use().view.set_visible_child_name("details") - - Join.use().join(item) - - def on_discard_clicked(self, item: JoinValue, widget: Gtk.Widget) -> None: - Join.use().discard(item) - if not Join.use().list_store.get_n_items(): + def on_discard_clicked(self, value: JoinValue, widget: Gtk.Widget) -> None: + JoinList.use().discard(value) + if JoinList.use().is_empty(): self.join_boxed_list.add_css_class("no-shadow") From c6a2db15a79db07e1ce45cba91fe78e982f3b4fc Mon Sep 17 00:00:00 2001 From: Qubasa Date: Sun, 3 Mar 2024 13:50:49 +0700 Subject: [PATCH 3/5] clan_vm_manager: Fix dynamic join --- .../clan_vm_manager/models/use_join.py | 10 ++++++++-- .../clan_vm_manager/models/use_vms.py | 18 ++++++++++++++++++ .../clan_vm_manager/views/list.py | 2 +- .../clan_vm_manager/windows/main_window.py | 19 ++----------------- 4 files changed, 29 insertions(+), 20 deletions(-) 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 e7ce8a313..29850bdc8 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 @@ -4,7 +4,9 @@ from typing import Any, ClassVar import gi from clan_cli.clan_uri import ClanURI -from clan_cli.history.add import add_history +from clan_cli.history.add import HistoryEntry, add_history + +from clan_vm_manager.models.use_vms import VMs gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") @@ -21,6 +23,7 @@ class JoinValue(GObject.Object): } url: ClanURI + entry: HistoryEntry | None def _join_finished(self) -> bool: self.emit("join_finished", self) @@ -29,9 +32,11 @@ class JoinValue(GObject.Object): def __init__(self, url: ClanURI) -> None: super().__init__() self.url = url + self.entry = None def __join(self) -> None: - add_history(self.url, all_machines=False) + new_entry = add_history(self.url) + self.entry = new_entry GLib.idle_add(self._join_finished) def join(self) -> None: @@ -79,6 +84,7 @@ class JoinList: def _on_join_finished(self, _source: GObject.Object, value: JoinValue) -> None: log.info(f"Join finished: {value.url}") self.discard(value) + VMs.use().push_history_entry(value.entry) def discard(self, value: JoinValue) -> None: (has, idx) = self.list_store.find(value) 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 e96f15463..710a81df9 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 @@ -18,6 +18,8 @@ from clan_cli.errors import ClanError from clan_cli.history.add import HistoryEntry from clan_cli.machines.machines import Machine +from clan_vm_manager import assets + from .executor import MPProcess, spawn from .gkvstore import GKVStore @@ -342,6 +344,22 @@ class VMs: def clan_store(self) -> GKVStore[str, VMStore]: return self._clan_store + def create_vm_task(self, vm: HistoryEntry) -> bool: + self.push_history_entry(vm) + return GLib.SOURCE_REMOVE + + def push_history_entry(self, entry: HistoryEntry) -> None: + if entry.flake.icon is None: + icon = assets.loc / "placeholder.jpeg" + else: + icon = entry.flake.icon + + vm = VM( + icon=Path(icon), + data=entry, + ) + self.push(vm) + def push(self, vm: VM) -> None: url = vm.data.flake.flake_url 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 8aa2c0ffd..90797f786 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -223,8 +223,8 @@ class ClanList(Gtk.Box): log.debug("Join request: %s", url) clan_uri = ClanURI.from_str(url) value = JoinValue(url=clan_uri) - value.connect("join_finished", self.on_after_join) JoinList.use().push(value) + value.connect("join_finished", 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 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 80b63e1a8..e1c472b9f 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 @@ -1,15 +1,13 @@ import logging import threading -from pathlib import Path from typing import Any import gi from clan_cli.history.list import list_history -from clan_vm_manager import assets from clan_vm_manager.models.interfaces import ClanConfig from clan_vm_manager.models.use_views import Views -from clan_vm_manager.models.use_vms import VM, VMs +from clan_vm_manager.models.use_vms import VMs from clan_vm_manager.views.details import Details from clan_vm_manager.views.list import ClanList @@ -61,24 +59,11 @@ class MainWindow(Adw.ApplicationWindow): self.connect("destroy", self.on_destroy) - def push_vm(self, vm: VM) -> bool: - VMs.use().push(vm) - return GLib.SOURCE_REMOVE - def _populate_vms(self) -> None: # Execute `clan flakes add ` to democlan for this to work # TODO: Make list_history a generator function for entry in list_history(): - if entry.flake.icon is None: - icon = assets.loc / "placeholder.jpeg" - else: - icon = entry.flake.icon - - vm = VM( - icon=Path(icon), - data=entry, - ) - GLib.idle_add(self.push_vm, vm) + GLib.idle_add(VMs.use().create_vm_task, entry) def on_destroy(self, *_args: Any) -> None: self.tray_icon.destroy() From 976b4a2c3a4ecd7041fd78799ec3f6f736001199 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Sun, 3 Mar 2024 15:44:16 +0700 Subject: [PATCH 4/5] clan_vm_manager: Fix incorrect signal behaviour in GKVStore setitem --- pkgs/clan-vm-manager/clan_vm_manager/models/gkvstore.py | 8 ++++++-- pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py | 8 ++++++-- pkgs/clan-vm-manager/clan_vm_manager/views/list.py | 3 +-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/gkvstore.py b/pkgs/clan-vm-manager/clan_vm_manager/models/gkvstore.py index 143690d3a..6ff6112e2 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/gkvstore.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/gkvstore.py @@ -95,6 +95,10 @@ class GKVStore(GObject.GObject, Gio.ListModel, Generic[K, V]): # # ######################### def insert(self, position: int, item: V) -> None: + log.warning("Inserting is O(n) in GKVStore. Better use append") + log.warning( + "This functions may have incorrect items_changed signal behavior. Please test it" + ) key = self.key_gen(item) if key in self._items: raise ValueError("Key already exists in the dictionary") @@ -141,12 +145,12 @@ class GKVStore(GObject.GObject, Gio.ListModel, Generic[K, V]): log.warning("Updating an existing key in GKVStore is O(n)") position = self.keys().index(key) self._items[key] = value - self.items_changed(position, 0, 1) + self.items_changed(position, 1, 1) else: # Add the new key-value pair - position = max(len(self._items) - 1, 0) self._items[key] = value self._items.move_to_end(key) + position = max(len(self._items) - 1, 0) self.items_changed(position, 0, 1) # O(n) operation 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 29850bdc8..258bed982 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,5 +1,6 @@ import logging import threading +from collections.abc import Callable from typing import Any, ClassVar import gi @@ -67,17 +68,20 @@ class JoinList: def is_empty(self) -> bool: return self.list_store.get_n_items() == 0 - def push(self, value: JoinValue) -> None: + def push( + self, value: JoinValue, after_join: Callable[[JoinValue, JoinValue], None] + ) -> None: """ Add a join request. This method can add multiple join requests if called subsequently for each request. """ 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}") + log.info(f"Join request already exists: {value.url}. Ignoring.") return value.connect("join_finished", self._on_join_finished) + value.connect("join_finished", after_join) self.list_store.append(value) 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 90797f786..4ea89bdf2 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -223,8 +223,7 @@ class ClanList(Gtk.Box): log.debug("Join request: %s", url) clan_uri = ClanURI.from_str(url) value = JoinValue(url=clan_uri) - JoinList.use().push(value) - value.connect("join_finished", self.on_after_join) + JoinList.use().push(value, 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 From 216e5a53d4bc3b6e170bd431552ebfa69dc86f29 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Sun, 3 Mar 2024 15:52:56 +0700 Subject: [PATCH 5/5] clan_vm_manager: Remove superfluous argument to build_vm --- pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py | 1 - 1 file changed, 1 deletion(-) 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 710a81df9..7088fe56e 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 @@ -149,7 +149,6 @@ class VM(GObject.Object): func=vms.run.build_vm, machine=machine, tmpdir=log_dir, - vm=self.data.flake.vm, ) GLib.idle_add(self.vm_status_changed)