From a4ac7d2fdbe92489671050530ced58a8e53696ad Mon Sep 17 00:00:00 2001 From: Qubasa Date: Wed, 6 Dec 2023 18:38:19 +0100 Subject: [PATCH 1/4] Added windows folder --- .../clan_vm_manager/__init__.py | 22 +--- pkgs/clan-vm-manager/clan_vm_manager/app.py | 82 +------------- .../clan_vm_manager/windows/__init__.py | 0 .../clan_vm_manager/windows/join.py | 16 +++ .../clan_vm_manager/windows/overview.py | 103 ++++++++++++++++++ 5 files changed, 128 insertions(+), 95 deletions(-) create mode 100644 pkgs/clan-vm-manager/clan_vm_manager/windows/__init__.py create mode 100644 pkgs/clan-vm-manager/clan_vm_manager/windows/join.py create mode 100644 pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py diff --git a/pkgs/clan-vm-manager/clan_vm_manager/__init__.py b/pkgs/clan-vm-manager/clan_vm_manager/__init__.py index 0671c7c66..9322baa71 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/__init__.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/__init__.py @@ -1,23 +1,11 @@ import argparse + from .app import Application +from clan_cli.clan_uri import ClanURI - -def join_command(args: argparse.Namespace) -> None: - print("Joining the flake") - print(args.clan_uri) - app = Application() - return app.run() - - -def register_join_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("clan_uri", type=str, help="clan URI to join") - parser.set_defaults(func=join_command) - - -def start_app(args: argparse.Namespace) -> None: - app = Application() - return app.run() +from .windows.join import register_join_parser +from .windows.overview import show_overview, OverviewWindow def main() -> None: @@ -32,6 +20,6 @@ def main() -> None: register_join_parser(subparser.add_parser("join", help="join a clan")) # Executed when no command is given - parser.set_defaults(func=start_app) + parser.set_defaults(func=show_overview) args = parser.parse_args() args.func(args) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 3244e33a9..7cc21d112 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -15,96 +15,22 @@ from .ui.clan_join_page import ClanJoinPage from .ui.clan_select_list import ClanEdit, ClanList -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, application: Gtk.Application) -> None: - super().__init__(application=application) - # Initialize the main window - self.set_title("cLAN Manager") - self.connect("delete-event", self.on_quit) - self.set_default_size(800, 600) - - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True) - self.add(vbox) - - # Add a notebook layout - # https://python-gtk-3-tutorial.readthedocs.io/en/latest/layout.html#notebook - self.notebook = Gtk.Notebook() - self.stack = Gtk.Stack() - # self.stack_switcher = Gtk.StackSwitcher() - - self.list_hooks = { - "remount_list": self.remount_list_view, - "remount_edit": self.remount_edit_view, - "set_selected": self.set_selected, - } - clan_list = ClanList(**self.list_hooks, selected_vm=None) # type: ignore - # Add named stacks - self.stack.add_titled(clan_list, "list", "List") - self.stack.add_titled( - ClanJoinPage(stack=self.remount_list_view), "join", "Join" - ) - self.stack.add_titled( - ClanEdit(remount_list=self.remount_list_view, selected_vm=None), - "edit", - "Edit", - ) - - vbox.add(self.stack) - - # Must be called AFTER all components were added - self.show_all() - - def set_selected(self, sel: VMBase | None) -> None: - self.selected_vm = sel - - if self.selected_vm: - print(f"APP selected + {self.selected_vm.name}") - - def remount_list_view(self) -> None: - widget = self.stack.get_child_by_name("list") - print("Remounting ClanListView") - if widget: - widget.destroy() - - clan_list = ClanList(**self.list_hooks, selected_vm=self.selected_vm) # type: ignore - self.stack.add_titled(clan_list, "list", "List") - self.show_all() - self.stack.set_visible_child_name("list") - - def remount_edit_view(self) -> None: - print("Remounting ClanEdit") - widget = self.stack.get_child_by_name("edit") - if widget: - widget.destroy() - - self.stack.add_titled( - ClanEdit(remount_list=self.remount_list_view, selected_vm=self.selected_vm), - "edit", - "Edit", - ) - self.show_all() - self.stack.set_visible_child_name("edit") - - def on_quit(self, *args: Any) -> None: - Gio.Application.quit(self.get_application()) - - class Application(Gtk.Application): - def __init__(self) -> None: + def __init__(self, window: Gtk.ApplicationWindow) -> None: super().__init__( application_id=constants["APPID"], flags=Gio.ApplicationFlags.FLAGS_NONE ) self.init_style() + self.window = window def do_startup(self) -> None: Gtk.Application.do_startup(self) - Gtk.init(sys.argv) + Gtk.init() def do_activate(self) -> None: win = self.props.active_window if not win: - # win = SwitchTreeView(application=self) - win = MainWindow(application=self) + win = self.window(application=self) win.present() # TODO: For css styling diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/__init__.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py new file mode 100644 index 000000000..0bdef7ca7 --- /dev/null +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py @@ -0,0 +1,16 @@ +from ..app import Application +import argparse +from .overview import OverviewWindow +from clan_cli.clan_uri import ClanURI + + +def show_join(args: argparse.Namespace) -> None: + print(f"Joining clan {args.clan_uri}") + app = Application(OverviewWindow) + return app.run() + + +def register_join_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("clan_uri", type=ClanURI, help="clan URI to join") + parser.set_defaults(func=show_join) + diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py new file mode 100644 index 000000000..1885bd744 --- /dev/null +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py @@ -0,0 +1,103 @@ +import sys +from typing import Any + +import gi + +from ..models import VMBase + +gi.require_version("Gtk", "3.0") +from gi.repository import Gio, Gtk + +from ..constants import constants +from ..ui.clan_join_page import ClanJoinPage +from ..ui.clan_select_list import ClanEdit, ClanList +from ..app import Application +from clan_cli.clan_uri import ClanURI +import argparse + +class OverviewWindow(Gtk.ApplicationWindow): + def __init__(self, application: Gtk.Application) -> None: + super().__init__(application=application) + # Initialize the main window + self.set_title("cLAN Manager") + self.connect("delete-event", self.on_quit) + self.set_default_size(800, 600) + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True) + self.add(vbox) + + # Add a notebook layout + # https://python-gtk-3-tutorial.readthedocs.io/en/latest/layout.html#notebook + self.notebook = Gtk.Notebook() + self.stack = Gtk.Stack() + # self.stack_switcher = Gtk.StackSwitcher() + + self.list_hooks = { + "remount_list": self.remount_list_view, + "remount_edit": self.remount_edit_view, + "set_selected": self.set_selected, + } + clan_list = ClanList(**self.list_hooks, selected_vm=None) # type: ignore + # Add named stacks + self.stack.add_titled(clan_list, "list", "List") + self.stack.add_titled( + ClanJoinPage(stack=self.remount_list_view), "join", "Join" + ) + self.stack.add_titled( + ClanEdit(remount_list=self.remount_list_view, selected_vm=None), + "edit", + "Edit", + ) + + vbox.add(self.stack) + + # Must be called AFTER all components were added + self.show_all() + + def set_selected(self, sel: VMBase | None) -> None: + self.selected_vm = sel + + if self.selected_vm: + print(f"APP selected + {self.selected_vm.name}") + + def remount_list_view(self) -> None: + widget = self.stack.get_child_by_name("list") + print("Remounting ClanListView") + if widget: + widget.destroy() + + clan_list = ClanList(**self.list_hooks, selected_vm=self.selected_vm) # type: ignore + self.stack.add_titled(clan_list, "list", "List") + self.show_all() + self.stack.set_visible_child_name("list") + + def remount_edit_view(self) -> None: + print("Remounting ClanEdit") + widget = self.stack.get_child_by_name("edit") + if widget: + widget.destroy() + + self.stack.add_titled( + ClanEdit(remount_list=self.remount_list_view, selected_vm=self.selected_vm), + "edit", + "Edit", + ) + self.show_all() + self.stack.set_visible_child_name("edit") + + def on_quit(self, *args: Any) -> None: + Gio.Application.quit(self.get_application()) + + + +def show_overview(args: argparse.Namespace) -> None: + app = Application(OverviewWindow) + return app.run() + +def register_overview_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("clan_uri", type=ClanURI, help="clan URI to join") + parser.set_defaults(func=show_overview) + + + + From a811c3662818b15b8fff3cb1f291f96d004a1c32 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 8 Dec 2023 11:56:27 +0100 Subject: [PATCH 2/4] Added join window --- .../clan_vm_manager/__init__.py | 8 ++-- pkgs/clan-vm-manager/clan_vm_manager/app.py | 9 +---- .../clan_vm_manager/windows/join.py | 38 +++++++++++++++++-- .../clan_vm_manager/windows/overview.py | 28 ++++++-------- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/__init__.py b/pkgs/clan-vm-manager/clan_vm_manager/__init__.py index 9322baa71..bb665e118 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/__init__.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/__init__.py @@ -1,11 +1,7 @@ import argparse - -from .app import Application -from clan_cli.clan_uri import ClanURI - from .windows.join import register_join_parser -from .windows.overview import show_overview, OverviewWindow +from .windows.overview import register_overview_parser, show_overview def main() -> None: @@ -19,6 +15,8 @@ def main() -> None: ) register_join_parser(subparser.add_parser("join", help="join a clan")) + register_overview_parser(subparser.add_parser("overview", help="overview screen")) + # Executed when no command is given parser.set_defaults(func=show_overview) args = parser.parse_args() diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 7cc21d112..48b39d21d 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -1,18 +1,12 @@ #!/usr/bin/env python3 -import sys -from typing import Any import gi -from .models import VMBase - gi.require_version("Gtk", "3.0") from gi.repository import Gio, Gtk from .constants import constants -from .ui.clan_join_page import ClanJoinPage -from .ui.clan_select_list import ClanEdit, ClanList class Application(Gtk.Application): @@ -30,7 +24,8 @@ class Application(Gtk.Application): def do_activate(self) -> None: win = self.props.active_window if not win: - win = self.window(application=self) + win = self.window + win.set_application(self) win.present() # TODO: For css styling diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py index 0bdef7ca7..bff1bd9fa 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py @@ -1,16 +1,46 @@ -from ..app import Application import argparse -from .overview import OverviewWindow +from typing import Any + +import gi from clan_cli.clan_uri import ClanURI +from ..app import Application + +gi.require_version("Gtk", "3.0") +from gi.repository import Gio, Gtk + + +class JoinWindow(Gtk.ApplicationWindow): + def __init__(self) -> None: + super().__init__() + # Initialize the main window + self.set_title("cLAN Manager") + self.connect("delete-event", self.on_quit) + self.set_default_size(800, 600) + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True) + self.add(vbox) + + # Add a notebook layout + # https://python-gtk-3-tutorial.readthedocs.io/en/latest/layout.html#notebook + self.notebook = Gtk.Notebook() + self.stack = Gtk.Stack() + + vbox.add(self.stack) + + # Must be called AFTER all components were added + self.show_all() + + def on_quit(self, *args: Any) -> None: + Gio.Application.quit(self.get_application()) + def show_join(args: argparse.Namespace) -> None: print(f"Joining clan {args.clan_uri}") - app = Application(OverviewWindow) + app = Application(JoinWindow()) return app.run() def register_join_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument("clan_uri", type=ClanURI, help="clan URI to join") parser.set_defaults(func=show_join) - diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py index 1885bd744..f0dba3b4f 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py @@ -1,4 +1,3 @@ -import sys from typing import Any import gi @@ -6,18 +5,18 @@ import gi from ..models import VMBase gi.require_version("Gtk", "3.0") -from gi.repository import Gio, Gtk - -from ..constants import constants -from ..ui.clan_join_page import ClanJoinPage -from ..ui.clan_select_list import ClanEdit, ClanList -from ..app import Application -from clan_cli.clan_uri import ClanURI import argparse +from gi.repository import Gio, Gtk + +from ..app import Application +from ..ui.clan_join_page import ClanJoinPage +from ..ui.clan_select_list import ClanEdit, ClanList + + class OverviewWindow(Gtk.ApplicationWindow): - def __init__(self, application: Gtk.Application) -> None: - super().__init__(application=application) + def __init__(self) -> None: + super().__init__() # Initialize the main window self.set_title("cLAN Manager") self.connect("delete-event", self.on_quit) @@ -89,15 +88,10 @@ class OverviewWindow(Gtk.ApplicationWindow): Gio.Application.quit(self.get_application()) - def show_overview(args: argparse.Namespace) -> None: - app = Application(OverviewWindow) + app = Application(OverviewWindow()) return app.run() + def register_overview_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("clan_uri", type=ClanURI, help="clan URI to join") parser.set_defaults(func=show_overview) - - - - From 6f80cdb5ebe488e18769f77c3f35dae563316cf7 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 8 Dec 2023 12:18:55 +0100 Subject: [PATCH 3/4] Replaced Status with checkbox --- pkgs/clan-cli/clan_cli/clan_uri.py | 3 ++ .../clan-vm-manager/clan_vm_manager/models.py | 27 +++++--------- .../clan_vm_manager/ui/clan_select_list.py | 35 ++++++++++++------- .../clan_vm_manager/windows/join.py | 2 ++ 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/clan_uri.py b/pkgs/clan-cli/clan_cli/clan_uri.py index 25a3ab2ca..551b933dc 100644 --- a/pkgs/clan-cli/clan_cli/clan_uri.py +++ b/pkgs/clan-cli/clan_cli/clan_uri.py @@ -94,3 +94,6 @@ class ClanURI: urlparams = urllib.parse.urlencode(params.__dict__) return cls(f"clan://file://{path}?{urlparams}") + + def __str__(self) -> str: + return f"ClanURI({self._components.geturl()})" \ No newline at end of file diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models.py b/pkgs/clan-vm-manager/clan_vm_manager/models.py index 6c493814a..426f12eff 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models.py @@ -1,6 +1,5 @@ from collections import OrderedDict from dataclasses import dataclass -from enum import Enum from pathlib import Path from typing import Any @@ -13,22 +12,12 @@ from gi.repository import GdkPixbuf from clan_vm_manager import assets -class Status(Enum): - OFF = "Off" - RUNNING = "Running" - # SUSPENDED = "Suspended" - # UNKNOWN = "Unknown" - - def __str__(self) -> str: - return self.value - - @dataclass(frozen=True) class VMBase: icon: Path | GdkPixbuf.Pixbuf name: str url: str - status: Status + status: bool _path: Path @staticmethod @@ -38,7 +27,7 @@ class VMBase: "Icon": GdkPixbuf.Pixbuf, "Name": str, "URL": str, - "Status": str, + "Online": bool, "_Path": str, } ) @@ -53,7 +42,7 @@ class VMBase: "Icon": str(self.icon), "Name": self.name, "URL": self.url, - "Status": str(self.status), + "Online": self.status, "_Path": str(self._path), } ) @@ -92,7 +81,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]: name="Cybernet Clan", url="clan://cybernet.lol", _path=Path(__file__).parent.parent / "test_democlan", - status=Status.RUNNING, + status=False, ), ), VM( @@ -101,7 +90,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]: name="Zenith Clan", url="clan://zenith.lol", _path=Path(__file__).parent.parent / "test_democlan", - status=Status.OFF, + status=False, ) ), VM( @@ -110,7 +99,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]: name="Firestorm Clan", url="clan://firestorm.lol", _path=Path(__file__).parent.parent / "test_democlan", - status=Status.OFF, + status=False, ), ), VM( @@ -119,7 +108,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]: name="Placeholder Clan", url="clan://demo.lol", _path=Path(__file__).parent.parent / "test_democlan", - status=Status.OFF, + status=True, ), ), ] @@ -132,7 +121,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]: "name": "Demo Clan", "url": "clan://demo.lol", "_path": entry.path, - "status": Status.OFF, + "status": False, } vms.append(VM(base=VMBase(**new_vm))) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py index 82c3a5337..4b542a9ac 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py @@ -74,9 +74,9 @@ class ClanList(Gtk.Box): Is the composition of the ClanListToolbar the clanListView - # ------------------------# - # - Tools < Edit> # - # ------------------------# + # ------------------------ # + # - Tools < Edit> # + # ------------------------ # # - List Items # - <...> # ------------------------# @@ -153,10 +153,14 @@ class ClanListToolbar(Gtk.Toolbar): ) -> None: super().__init__(orientation=Gtk.Orientation.HORIZONTAL) - self.start_button = Gtk.ToolButton(label="Join") + self.start_button = Gtk.ToolButton(label="Start") self.start_button.connect("clicked", on_start_clicked) self.add(self.start_button) + self.stop_button = Gtk.ToolButton(label="Stop") + self.stop_button.connect("clicked", on_stop_clicked) + self.add(self.stop_button) + self.edit_button = Gtk.ToolButton(label="Edit") self.edit_button.connect("clicked", on_edit_clicked) self.add(self.edit_button) @@ -165,9 +169,11 @@ class ClanListToolbar(Gtk.Toolbar): if s: self.edit_button.set_sensitive(True) self.start_button.set_sensitive(True) + self.stop_button.set_sensitive(True) else: self.edit_button.set_sensitive(False) self.start_button.set_sensitive(False) + self.stop_button.set_sensitive(False) class ClanEditToolbar(Gtk.Toolbar): @@ -224,7 +230,6 @@ class ClanListView(Gtk.Box): return selection = self.tree_view.get_selection() idx = self.find_vm(vm) - print(f"Set selected vm: {vm.name} at {idx}") selection.select_path(idx) def insertVM(self, vm: VMBase) -> None: @@ -239,7 +244,6 @@ class ClanListView(Gtk.Box): model, row = selection.get_selected() if row is not None: vm = VMBase(*model[row]) - print(f"Selected {vm.name}") self.on_select_row(vm) def _on_double_click( @@ -259,13 +263,18 @@ def setColRenderers(tree_view: Gtk.TreeView) -> None: if key.startswith("_"): continue - match gtype: - case GdkPixbuf.Pixbuf: - renderer = Gtk.CellRendererPixbuf() - col = Gtk.TreeViewColumn(key, renderer, pixbuf=idx) - case str: # noqa - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn(key, renderer, text=idx) + + if issubclass(gtype, GdkPixbuf.Pixbuf): + renderer = Gtk.CellRendererPixbuf() + col = Gtk.TreeViewColumn(key, renderer, pixbuf=idx) + elif issubclass(gtype, bool): + renderer = Gtk.CellRendererToggle() + col = Gtk.TreeViewColumn(key, renderer, active=idx) + elif issubclass(gtype, str): + renderer = Gtk.CellRendererText() + col = Gtk.TreeViewColumn(key, renderer, text=idx) + else: + raise Exception(f"Unknown type: {gtype}") # CommonSetup for all columns if col: diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py index bff1bd9fa..bb55429a3 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/join.py @@ -26,6 +26,8 @@ class JoinWindow(Gtk.ApplicationWindow): self.notebook = Gtk.Notebook() self.stack = Gtk.Stack() + self.stack.add_titled(Gtk.Label("Join cLan"), "join", "Join") + vbox.add(self.stack) # Must be called AFTER all components were added From b8bc7a3fccf08f63650fe5521dfe764abf11cccb Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 8 Dec 2023 13:46:21 +0100 Subject: [PATCH 4/4] clan_cli: URI parser now only has HTTP and FILE. Also clan:///home/user or clan://~/Downloads is supported --- pkgs/clan-cli/clan_cli/clan_uri.py | 38 +++++++------- pkgs/clan-cli/clan_cli/nix.py | 7 +++ pkgs/clan-cli/tests/test_clan_uri.py | 50 +++++++++++++++++-- .../clan_vm_manager/ui/clan_select_list.py | 18 ++++--- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/clan_uri.py b/pkgs/clan-cli/clan_cli/clan_uri.py index 551b933dc..7da3c6365 100644 --- a/pkgs/clan-cli/clan_cli/clan_uri.py +++ b/pkgs/clan-cli/clan_cli/clan_uri.py @@ -20,14 +20,6 @@ class ClanScheme(Enum): def __str__(self) -> str: return f"HTTP({self.url})" # The __str__ method returns a custom string representation - @member - @dataclass - class HTTPS: - url: str # The url field holds the HTTPS URL - - def __str__(self) -> str: - return f"HTTPS({self.url})" # The __str__ method returns a custom string representation - @member @dataclass class FILE: @@ -78,22 +70,30 @@ class ClanURI: self._components = self._components._replace(query=new_query) self.params = ClanParameters(**params) - # Use the match statement to check the scheme and create a ClanScheme member with the value - match self._components.scheme: - case "http": + comb = ( + self._components.scheme, + self._components.netloc, + self._components.path, + self._components.params, + self._components.query, + self._components.fragment, + ) + + match comb: + case ("http" | "https", _, _, _, _, _): self.scheme = ClanScheme.HTTP.value(self._components.geturl()) # type: ignore - case "https": - self.scheme = ClanScheme.HTTPS.value(self._components.geturl()) # type: ignore - case "file": - self.scheme = ClanScheme.FILE.value(Path(self._components.path)) # type: ignore + case ("file", "", path, "", "", "") | ("", "", path, "", "", ""): # type: ignore + self.scheme = ClanScheme.FILE.value(Path(path)) # type: ignore case _: - raise ClanError(f"Unsupported scheme: {self._components.scheme}") + raise ClanError(f"Unsupported uri components: {comb}") + + def get_internal(self) -> str: + return self._nested_uri @classmethod def from_path(cls, path: Path, params: ClanParameters) -> Self: # noqa urlparams = urllib.parse.urlencode(params.__dict__) - - return cls(f"clan://file://{path}?{urlparams}") + return cls(f"clan://{path}?{urlparams}") def __str__(self) -> str: - return f"ClanURI({self._components.geturl()})" \ No newline at end of file + return f"ClanURI({self._components.geturl()})" diff --git a/pkgs/clan-cli/clan_cli/nix.py b/pkgs/clan-cli/clan_cli/nix.py index b6ee3677c..c90611e21 100644 --- a/pkgs/clan-cli/clan_cli/nix.py +++ b/pkgs/clan-cli/clan_cli/nix.py @@ -82,6 +82,13 @@ def nix_eval(flags: list[str]) -> list[str]: return default_flags + flags +def nix_metadata(flake: str) -> dict[str, Any]: + cmd = nix_command(["flake", "metadata", "--json", flake]) + proc = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) + data = json.loads(proc.stdout) + return data + + @deal.raises(ClanError) def nix_shell(packages: list[str], cmd: list[str]) -> list[str]: # we cannot use nix-shell inside the nix sandbox diff --git a/pkgs/clan-cli/tests/test_clan_uri.py b/pkgs/clan-cli/tests/test_clan_uri.py index e2b2e5e29..9170c8279 100644 --- a/pkgs/clan-cli/tests/test_clan_uri.py +++ b/pkgs/clan-cli/tests/test_clan_uri.py @@ -2,7 +2,7 @@ from pathlib import Path import pytest -from clan_cli.clan_uri import ClanScheme, ClanURI +from clan_cli.clan_uri import ClanParameters, ClanScheme, ClanURI from clan_cli.errors import ClanError @@ -17,7 +17,7 @@ def test_local_uri() -> None: def test_unsupported_schema() -> None: - with pytest.raises(ClanError, match="Unsupported scheme: ftp"): + with pytest.raises(ClanError, match="Unsupported uri components: .*"): # Create a ClanURI object from an unsupported URI ClanURI("clan://ftp://ftp.example.com") @@ -27,12 +27,24 @@ def test_is_remote() -> None: uri = ClanURI("clan://https://example.com") match uri.scheme: - case ClanScheme.HTTPS.value(url): + case ClanScheme.HTTP.value(url): assert url == "https://example.com" # type: ignore case _: assert False +def test_direct_local_path() -> None: + # Create a ClanURI object from a remote URI + uri = ClanURI("clan://~/Downloads") + assert uri.get_internal() == "~/Downloads" + + +def test_direct_local_path2() -> None: + # Create a ClanURI object from a remote URI + uri = ClanURI("clan:///home/user/Downloads") + assert uri.get_internal() == "/home/user/Downloads" + + def test_remote_with_clanparams() -> None: # Create a ClanURI object from a remote URI with parameters uri = ClanURI("clan://https://example.com") @@ -40,19 +52,47 @@ def test_remote_with_clanparams() -> None: assert uri.params.flake_attr == "defaultVM" match uri.scheme: - case ClanScheme.HTTPS.value(url): + case ClanScheme.HTTP.value(url): assert url == "https://example.com" # type: ignore case _: assert False +def test_from_path_with_custom() -> None: + # Create a ClanURI object from a remote URI with parameters + uri_str = Path("/home/user/Downloads") + params = ClanParameters(flake_attr="myVM") + uri = ClanURI.from_path(uri_str, params) + assert uri.params.flake_attr == "myVM" + + match uri.scheme: + case ClanScheme.FILE.value(path): + assert path == Path("/home/user/Downloads") # type: ignore + case _: + assert False + + +def test_from_path_with_default() -> None: + # Create a ClanURI object from a remote URI with parameters + uri_str = Path("/home/user/Downloads") + params = ClanParameters() + uri = ClanURI.from_path(uri_str, params) + assert uri.params.flake_attr == "defaultVM" + + match uri.scheme: + case ClanScheme.FILE.value(path): + assert path == Path("/home/user/Downloads") # type: ignore + case _: + assert False + + def test_remote_with_all_params() -> None: # Create a ClanURI object from a remote URI with parameters uri = ClanURI("clan://https://example.com?flake_attr=myVM&password=1234") assert uri.params.flake_attr == "myVM" match uri.scheme: - case ClanScheme.HTTPS.value(url): + case ClanScheme.HTTP.value(url): assert url == "https://example.com?password=1234" # type: ignore case _: assert False diff --git a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py index 4b542a9ac..08bd9769b 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py @@ -89,12 +89,14 @@ class ClanList(Gtk.Box): remount_edit: Callable[[], None], set_selected: Callable[[VMBase | None], None], selected_vm: VMBase | None, + show_toolbar: bool = True, ) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True) self.remount_edit_view = remount_edit self.remount_list_view = remount_list self.set_selected = set_selected + self.show_toolbar = show_toolbar # TODO: We should use somekind of useState hook here. # that updates the list of VMs when the user changes something @@ -109,9 +111,10 @@ class ClanList(Gtk.Box): "on_stop_clicked": self.on_stop_clicked, "on_edit_clicked": self.on_edit_clicked, } - self.toolbar = ClanListToolbar(**button_hooks) - self.toolbar.set_is_selected(self.selected_vm is not None) - self.add(self.toolbar) + if show_toolbar: + self.toolbar = ClanListToolbar(**button_hooks) + self.toolbar.set_is_selected(self.selected_vm is not None) + self.add(self.toolbar) self.list_hooks = { "on_select_row": self.on_select_vm, @@ -134,10 +137,11 @@ class ClanList(Gtk.Box): def on_select_vm(self, vm: VMBase) -> None: print(f"on_select_vm: {vm.name}") - if vm is None: - self.toolbar.set_is_selected(False) - else: - self.toolbar.set_is_selected(True) + if self.show_toolbar: + if vm is None: + self.toolbar.set_is_selected(False) + else: + self.toolbar.set_is_selected(True) self.set_selected(vm) self.selected_vm = vm