From 6f80cee971eff29c7531b65d5ef7bc1105c56172 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Sun, 3 Mar 2024 12:47:18 +0700 Subject: [PATCH] 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 3f126b2e..16bb9698 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 e186b301..e7ce8a31 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 9bb219f3..8aa2c0ff 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")