clan_cli: history_add now returns newly added HistoryEntry. clan-vm-manager: Join now uses signals instead of callbacks.
This commit is contained in:
parent
bf214011cf
commit
127009b303
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user