From 7b6d8b8a09e89ab84cedbc4d9e61dd09696ff18d Mon Sep 17 00:00:00 2001 From: Exidex <16986685+Exidex@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:25:13 +0100 Subject: [PATCH] Add command entrypoints --- js/binary/src/main.ts | 5 +- js/core/package.json | 1 + js/core/src/init.ts | 15 ++++-- js/core/tsconfig.json | 2 +- js/core/typings/index.d.ts | 2 +- js/dev_plugin/gauntlet.toml | 29 ++++++---- js/dev_plugin/package.json | 2 +- js/dev_plugin/src/command-a.ts | 3 ++ js/react_renderer/package.json | 1 + js/react_renderer/src/renderer.tsx | 27 +++------- js/react_renderer/tsconfig.json | 2 +- js/typings/index.d.ts | 16 +++--- rust/client/src/dbus.rs | 7 ++- rust/client/src/model.rs | 7 +++ rust/client/src/ui/mod.rs | 53 +++++++++++++++---- rust/client/src/ui/search_list.rs | 41 ++++++++++++-- rust/common/src/dbus.rs | 18 +++++-- rust/server/db_migrations/1_initial.sql | 1 + rust/server/src/dbus.rs | 23 +++++--- rust/server/src/model.rs | 47 ++++++++++++---- rust/server/src/plugins/data_db_repository.rs | 6 ++- rust/server/src/plugins/js.rs | 53 +++++++++++++------ rust/server/src/plugins/loader.rs | 19 ++++++- rust/server/src/plugins/mod.rs | 11 +++- rust/server/src/search.rs | 12 ++++- 25 files changed, 296 insertions(+), 107 deletions(-) create mode 100644 js/dev_plugin/src/command-a.ts diff --git a/js/binary/src/main.ts b/js/binary/src/main.ts index 3f8d628..d71586c 100644 --- a/js/binary/src/main.ts +++ b/js/binary/src/main.ts @@ -7,13 +7,14 @@ import { parse as parseToml } from "toml"; import { z } from "zod"; const Config = z.strictObject({ - metadata: z.strictObject({ + gauntlet: z.strictObject({ name: z.string() }), entrypoint: z.array(z.strictObject({ id: z.string(), name: z.string(), - path: z.string() + path: z.string(), + type: z.enum(["command", "view"]) })), permissions: z.strictObject({ environment: z.array(z.string()).default([]), diff --git a/js/core/package.json b/js/core/package.json index 2229db5..390aee9 100644 --- a/js/core/package.json +++ b/js/core/package.json @@ -10,6 +10,7 @@ "@rollup/plugin-typescript": "^11.1.5", "@types/react": "^18.2.35", "@project-gauntlet/typings": "*", + "@project-gauntlet/deno": "*", "rollup": "^4.3.0", "tslib": "^2.6.2", "typescript": "^5.2.2" diff --git a/js/core/src/init.ts b/js/core/src/init.ts index 9bde6d0..d8aa096 100644 --- a/js/core/src/init.ts +++ b/js/core/src/init.ts @@ -82,17 +82,22 @@ async function runLoop() { } break; } - case "ViewCreated": { + case "OpenView": { try { - const view: FC = (await import(`gauntlet:view?${pluginEvent.viewName}`)).default; + const view: FC = (await import(`gauntlet:entrypoint?${pluginEvent.entrypointId}`)).default; const {render} = await import("gauntlet:renderer"); - latestRootUiWidget = render(pluginEvent.reconcilerMode, view); + latestRootUiWidget = render(pluginEvent.frontend, view); } catch (e) { - console.error("Error occurred when rendering view", pluginEvent.viewName, e) + console.error("Error occurred when rendering view", pluginEvent.entrypointId, e) } break; } - case "ViewDestroyed": { + case "RunCommand": { + try { + await import(`gauntlet:entrypoint?${pluginEvent.entrypointId}`) + } catch (e) { + console.error("Error occurred when running a command", pluginEvent.entrypointId, e) + } break; } case "PluginCommand": { diff --git a/js/core/tsconfig.json b/js/core/tsconfig.json index 35e09eb..7f14964 100644 --- a/js/core/tsconfig.json +++ b/js/core/tsconfig.json @@ -6,7 +6,7 @@ "target": "ES2022", "moduleResolution": "bundler", "jsx": "react-jsx", - "types": ["@project-gauntlet/typings"] + "types": ["@project-gauntlet/typings", "@project-gauntlet/deno"] }, "lib": ["ES2020"] } \ No newline at end of file diff --git a/js/core/typings/index.d.ts b/js/core/typings/index.d.ts index 0cee912..fdc9558 100644 --- a/js/core/typings/index.d.ts +++ b/js/core/typings/index.d.ts @@ -1,6 +1,6 @@ declare module "gauntlet:renderer" { import { FC } from "react"; - const render: (mode: "mutation" | "persistent", component: FC) => RootUiWidget; + const render: (frontend: string, component: FC) => RootUiWidget; export { render }; } \ No newline at end of file diff --git a/js/dev_plugin/gauntlet.toml b/js/dev_plugin/gauntlet.toml index 87b129c..a585350 100644 --- a/js/dev_plugin/gauntlet.toml +++ b/js/dev_plugin/gauntlet.toml @@ -1,18 +1,27 @@ -[metadata] -name='Interesting Plugin Name' +[gauntlet] +name = 'Interesting Plugin Name' [[entrypoint]] -id='detail-view' -name='Detail view' -path='src/detail-view.tsx' +id = 'detail-view' +name = 'Detail view' +path = 'src/detail-view.tsx' +type = 'view' [[entrypoint]] -id='form-view' -name='Form view' -path='src/form-view.tsx' +id = 'form-view' +name = 'Form view' +path = 'src/form-view.tsx' +type = 'view' + +[[entrypoint]] +id = 'command-a' +name = 'Command A' +path = 'src/command-a.ts' +type = 'command' [[supported_system]] -os='linux' +os = 'linux' [permissions] -environment=["RUST_LOG"] \ No newline at end of file +environment = ["RUST_LOG"] +system=["systemMemoryInfo"] \ No newline at end of file diff --git a/js/dev_plugin/package.json b/js/dev_plugin/package.json index 7d1b20e..c976a55 100644 --- a/js/dev_plugin/package.json +++ b/js/dev_plugin/package.json @@ -6,11 +6,11 @@ }, "devDependencies": { "@types/react": "^18.2.14", + "@project-gauntlet/deno": "*", "@project-gauntlet/binary": "*" }, "dependencies": { "@project-gauntlet/api": "*", - "@project-gauntlet/deno": "*", "@types/lodash": "^4.14.196", "lodash": "^4.17.21" } diff --git a/js/dev_plugin/src/command-a.ts b/js/dev_plugin/src/command-a.ts new file mode 100644 index 0000000..ca815f7 --- /dev/null +++ b/js/dev_plugin/src/command-a.ts @@ -0,0 +1,3 @@ +const systemMemoryInfo = Deno.systemMemoryInfo(); + +console.dir(systemMemoryInfo) \ No newline at end of file diff --git a/js/react_renderer/package.json b/js/react_renderer/package.json index 0e8ad3c..e442c27 100644 --- a/js/react_renderer/package.json +++ b/js/react_renderer/package.json @@ -15,6 +15,7 @@ "@types/react": "^18.2.35", "@types/react-reconciler": "^0.28.6", "@project-gauntlet/typings": "*", + "@project-gauntlet/deno": "*", "rollup": "^4.3.0", "tslib": "^2.6.2", "typescript": "^5.2.2" diff --git a/js/react_renderer/src/renderer.tsx b/js/react_renderer/src/renderer.tsx index 4a205ca..3ce2df3 100644 --- a/js/react_renderer/src/renderer.tsx +++ b/js/react_renderer/src/renderer.tsx @@ -30,7 +30,7 @@ function createWidget(hostContext: HostContext, type: ComponentType, properties: return instance } -export const createHostConfig = (options: { mode: "mutation" | "persistent" }): HostConfig< +export const createHostConfig = (): HostConfig< ComponentType, PropsWithChildren, RootUiWidget, @@ -159,7 +159,7 @@ export const createHostConfig = (options: { mode: "mutation" | "persistent" }): /* persistence items */ - supportsPersistence: isPersistentMode(options.mode), + supportsPersistence: true, cloneInstance( instance: Instance, @@ -171,8 +171,6 @@ export const createHostConfig = (options: { mode: "mutation" | "persistent" }): keepChildren: boolean, recyclableInstance: null | Instance, ): Instance { - assertPersistentMode(options.mode); - InternalApi.op_log_trace("renderer_js_persistence", `cloneInstance is called, instance: ${Deno.inspect(instance)}, updatePayload: ${Deno.inspect(updatePayload)}, type: ${type}, oldProps: ${Deno.inspect(oldProps)}, newProps: ${Deno.inspect(newProps)}, keepChildren: ${keepChildren}, recyclableInstance: ${Deno.inspect(recyclableInstance)}`) // TODO validate @@ -200,26 +198,22 @@ export const createHostConfig = (options: { mode: "mutation" | "persistent" }): }, createContainerChildSet(container: RootUiWidget): ChildSet { - assertPersistentMode(options.mode); InternalApi.op_log_trace("renderer_js_persistence", `createContainerChildSet is called, container: ${Deno.inspect(container)}`) return [] }, appendChildToContainerChildSet(childSet: ChildSet, child: Instance | TextInstance): void { - assertPersistentMode(options.mode); InternalApi.op_log_trace("renderer_js_persistence", `appendChildToContainerChildSet is called, childSet: ${Deno.inspect(childSet)}, child: ${Deno.inspect(child)}`) childSet.push(child); }, finalizeContainerChildren(container: RootUiWidget, newChildren: ChildSet): void { - assertPersistentMode(options.mode); InternalApi.op_log_trace("renderer_js_persistence", `finalizeContainerChildren is called, container: ${Deno.inspect(container)}, newChildren: ${Deno.inspect(newChildren)}`) }, replaceContainerChildren(container: RootUiWidget, newChildren: ChildSet): void { - assertPersistentMode(options.mode); InternalApi.op_log_trace("renderer_js_persistence", `replaceContainerChildren is called, container: ${Deno.inspect(container)}, newChildren: ${Deno.inspect(newChildren)}`) container.widgetChildren = newChildren InternalApi.op_react_replace_container_children(container, newChildren) @@ -244,13 +238,6 @@ export const createHostConfig = (options: { mode: "mutation" | "persistent" }): supportsHydration: false }); -const isPersistentMode = (mode: "mutation" | "persistent") => mode === "persistent"; -const assertPersistentMode = (mode: "mutation" | "persistent") => { - if (!isPersistentMode(mode)) { - throw new Error("Wrong reconciler mode") - } -} - function shallowDiff(oldObj: Record, newObj: Record): string[] | null { const uniqueProps = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]); const diff = Array.from(uniqueProps) @@ -289,13 +276,13 @@ const createTracedHostConfig = (hostConfig: any) => new Proxy(hostConfig, { } }); -export function render(mode: "mutation" | "persistent", View: React.FC): RootUiWidget { - if (mode === "mutation") { - // TODO reimplement but for specific frontend, it seems it is not feasible to do generic implementation - throw new Error("NOT IMPLEMENTED") +export function render(frontend: string, View: React.FC): RootUiWidget { + // specific frontend are implemented separately, it seems it is not feasible to do generic implementation + if (frontend !== "default") { + throw new Error("NOT SUPPORTED") } - const hostConfig = createHostConfig({mode}); + const hostConfig = createHostConfig(); // const reconciler = ReactReconciler(createTracedHostConfig(hostConfig)); const reconciler = ReactReconciler(hostConfig); diff --git a/js/react_renderer/tsconfig.json b/js/react_renderer/tsconfig.json index 35e09eb..7f14964 100644 --- a/js/react_renderer/tsconfig.json +++ b/js/react_renderer/tsconfig.json @@ -6,7 +6,7 @@ "target": "ES2022", "moduleResolution": "bundler", "jsx": "react-jsx", - "types": ["@project-gauntlet/typings"] + "types": ["@project-gauntlet/typings", "@project-gauntlet/deno"] }, "lib": ["ES2020"] } \ No newline at end of file diff --git a/js/typings/index.d.ts b/js/typings/index.d.ts index 1fbbf0d..8bf5d10 100644 --- a/js/typings/index.d.ts +++ b/js/typings/index.d.ts @@ -11,7 +11,7 @@ interface Deno { }; } -type PluginEvent = ViewEvent | ViewCreated | ViewDestroyed | PluginCommand +type PluginEvent = ViewEvent | RunCommand | OpenView | PluginCommand type ViewEvent = { type: "ViewEvent" @@ -20,14 +20,14 @@ type ViewEvent = { eventArguments: PropertyValue[] } -type ViewCreated = { - type: "ViewCreated" - reconcilerMode: string - viewName: string +type OpenView = { + type: "OpenView" + frontend: string + entrypointId: string } - -type ViewDestroyed = { - type: "ViewDestroyed" +type RunCommand = { + type: "RunCommand" + entrypointId: string } type PluginCommand = { diff --git a/rust/client/src/dbus.rs b/rust/client/src/dbus.rs index 84bc868..c3f53d0 100644 --- a/rust/client/src/dbus.rs +++ b/rust/client/src/dbus.rs @@ -1,6 +1,6 @@ use zbus::DBusError; -use common::dbus::{DbusEventViewCreated, DbusEventViewEvent, DBusSearchResult, DBusUiWidget}; +use common::dbus::{DbusEventOpenView, DbusEventRunCommand, DbusEventViewEvent, DBusSearchResult, DBusUiWidget}; use common::model::PluginId; use utils::channel::RequestSender; @@ -13,7 +13,10 @@ pub struct DbusClient { #[zbus::dbus_interface(name = "dev.projectgauntlet.Client")] impl DbusClient { #[dbus_interface(signal)] - pub async fn view_created_signal(signal_ctxt: &zbus::SignalContext<'_>, plugin_id: &str, event: DbusEventViewCreated) -> zbus::Result<()>; + pub async fn open_view_signal(signal_ctxt: &zbus::SignalContext<'_>, plugin_id: &str, event: DbusEventOpenView) -> zbus::Result<()>; + + #[dbus_interface(signal)] + pub async fn run_command_signal(signal_ctxt: &zbus::SignalContext<'_>, plugin_id: &str, event: DbusEventRunCommand) -> zbus::Result<()>; #[dbus_interface(signal)] pub async fn view_event_signal(signal_ctxt: &zbus::SignalContext<'_>, plugin_id: &str, event: DbusEventViewEvent) -> zbus::Result<()>; diff --git a/rust/client/src/model.rs b/rust/client/src/model.rs index 837ae8f..84a2f53 100644 --- a/rust/client/src/model.rs +++ b/rust/client/src/model.rs @@ -12,6 +12,13 @@ pub struct NativeUiSearchResult { pub plugin_name: String, pub entrypoint_id: EntrypointId, pub entrypoint_name: String, + pub entrypoint_type: SearchResultEntrypointType, +} + +#[derive(Debug, Clone)] +pub enum SearchResultEntrypointType { + Command, + View, } #[derive(Debug)] diff --git a/rust/client/src/ui/mod.rs b/rust/client/src/ui/mod.rs index 8e4983a..79535bf 100644 --- a/rust/client/src/ui/mod.rs +++ b/rust/client/src/ui/mod.rs @@ -11,12 +11,12 @@ use iced_aw::graphics::icons; use tokio::sync::RwLock as TokioRwLock; use zbus::{Connection, InterfaceRef}; -use common::dbus::DbusEventViewCreated; +use common::dbus::{DBusEntrypointType, DbusEventOpenView, DbusEventRunCommand}; use common::model::{EntrypointId, PluginId}; use utils::channel::{channel, RequestReceiver}; use crate::dbus::{DbusClient, DbusServerProxyProxy}; -use crate::model::{NativeUiRequestData, NativeUiResponseData, NativeUiSearchResult}; +use crate::model::{NativeUiRequestData, NativeUiResponseData, NativeUiSearchResult, SearchResultEntrypointType}; use crate::ui::plugin_container::{ClientContext, plugin_container}; use crate::ui::search_list::search_list; use crate::ui::theme::{ContainerStyle, Element, GauntletTheme}; @@ -53,6 +53,10 @@ pub enum AppMsg { plugin_id: PluginId, entrypoint_id: EntrypointId, }, + RunCommand { + plugin_id: PluginId, + entrypoint_id: EntrypointId, + }, PromptChanged(String), SetSearchResults(Vec), IcedEvent(Event), @@ -151,14 +155,14 @@ impl Application for AppModel { let dbus_client = self.dbus_client.clone(); let open_view = Command::perform(async move { - let event_view_created = DbusEventViewCreated { - reconciler_mode: "persistent".to_owned(), - view_name: entrypoint_id.to_string(), // TODO what was view_name supposed to be? + let event_open_view = DbusEventOpenView { + frontend: "default".to_owned(), + entrypoint_id: entrypoint_id.to_string(), }; let signal_context = dbus_client.signal_context(); - DbusClient::view_created_signal(signal_context, &plugin_id.to_string(), event_view_created) + DbusClient::open_view_signal(signal_context, &plugin_id.to_string(), event_open_view) .await .unwrap(); }, |_| AppMsg::Noop); @@ -168,6 +172,26 @@ impl Application for AppModel { open_view ]) } + AppMsg::RunCommand { plugin_id, entrypoint_id } => { + let dbus_client = self.dbus_client.clone(); + + let run_command = Command::perform(async move { + let event_run_command = DbusEventRunCommand { + entrypoint_id: entrypoint_id.to_string(), + }; + + let signal_context = dbus_client.signal_context(); + + DbusClient::run_command_signal(signal_context, &plugin_id.to_string(), event_run_command) + .await + .unwrap(); + }, |_| AppMsg::Noop); + + Command::batch([ + run_command, + iced::window::close(), + ]) + } AppMsg::PromptChanged(new_prompt) => { match self.state.last_mut().expect("state is supposed to always have at least one item") { NavState::SearchView { prompt } => { @@ -185,6 +209,10 @@ impl Application for AppModel { plugin_name: search_result.plugin_name, entrypoint_id: EntrypointId::new(search_result.entrypoint_id), entrypoint_name: search_result.entrypoint_name, + entrypoint_type: match search_result.entrypoint_type { + DBusEntrypointType::Command => SearchResultEntrypointType::Command, + DBusEntrypointType::View => SearchResultEntrypointType::View, + }, }) .collect(); @@ -258,12 +286,17 @@ impl Application for AppModel { let search_results = self.search_results.iter().cloned().collect(); - let search_list = search_list(search_results, |event| { - AppMsg::OpenView { + let search_list = search_list( + search_results, + |event| AppMsg::OpenView { plugin_id: event.plugin_id, entrypoint_id: event.entrypoint_id, - } - }); + }, + |event| AppMsg::RunCommand { + plugin_id: event.plugin_id, + entrypoint_id: event.entrypoint_id, + }, + ); let list: Element<_> = scrollable(search_list) .width(Length::Fill) diff --git a/rust/client/src/ui/search_list.rs b/rust/client/src/ui/search_list.rs index 2190fcb..9f6763c 100644 --- a/rust/client/src/ui/search_list.rs +++ b/rust/client/src/ui/search_list.rs @@ -8,19 +8,21 @@ use iced::widget::text; use common::model::{EntrypointId, PluginId}; -use crate::model::NativeUiSearchResult; +use crate::model::{NativeUiSearchResult, SearchResultEntrypointType}; use crate::ui::theme::{ButtonStyle, Element, GauntletRenderer, TextStyle}; pub struct SearchList { on_open_view: Box Message>, + on_run_command: Box Message>, search_results: Vec, } pub fn search_list( search_results: Vec, - on_open_view: impl Fn(OpenViewEvent) -> Message + 'static + on_open_view: impl Fn(OpenViewEvent) -> Message + 'static, + on_run_command: impl Fn(RunCommandEvent) -> Message + 'static ) -> SearchList { - SearchList::new(search_results, on_open_view) + SearchList::new(search_results, on_open_view, on_run_command) } pub struct OpenViewEvent { @@ -28,8 +30,17 @@ pub struct OpenViewEvent { pub entrypoint_id: EntrypointId, } +pub struct RunCommandEvent { + pub plugin_id: PluginId, + pub entrypoint_id: EntrypointId, +} + #[derive(Debug, Clone)] pub enum Event { + RunCommand { + plugin_id: PluginId, + entrypoint_id: EntrypointId, + }, OpenView { plugin_id: PluginId, entrypoint_id: EntrypointId, @@ -37,10 +48,15 @@ pub enum Event { } impl SearchList { - pub fn new(search_results: Vec, on_open_view: impl Fn(OpenViewEvent) -> Message + 'static) -> Self { + pub fn new( + search_results: Vec, + on_open_view: impl Fn(OpenViewEvent) -> Message + 'static, + on_run_command: impl Fn(RunCommandEvent) -> Message + 'static + ) -> Self { Self { search_results, on_open_view: Box::new(on_open_view), + on_run_command: Box::new(on_run_command), } } } @@ -59,6 +75,10 @@ impl Component for SearchList { let event = OpenViewEvent { plugin_id, entrypoint_id, }; Some((self.on_open_view)(event)) } + Event::RunCommand { plugin_id, entrypoint_id } => { + let event = RunCommandEvent { plugin_id, entrypoint_id, }; + Some((self.on_run_command)(event)) + } } } @@ -84,10 +104,21 @@ impl Component for SearchList { sub_text, ]).into(); + let event = match search_result.entrypoint_type { + SearchResultEntrypointType::Command => Event::RunCommand { + entrypoint_id: search_result.entrypoint_id.clone(), + plugin_id: search_result.plugin_id.clone() + }, + SearchResultEntrypointType::View => Event::OpenView { + entrypoint_id: search_result.entrypoint_id.clone(), + plugin_id: search_result.plugin_id.clone() + } + }; + button(button_content) .width(Length::Fill) .style(ButtonStyle::EntrypointItem) - .on_press(Event::OpenView { entrypoint_id: search_result.entrypoint_id.clone(), plugin_id: search_result.plugin_id.clone() }) + .on_press(event) .padding(Padding::new(5.0)) .into() }) diff --git a/rust/common/src/dbus.rs b/rust/common/src/dbus.rs index 68655db..8e41237 100644 --- a/rust/common/src/dbus.rs +++ b/rust/common/src/dbus.rs @@ -10,6 +10,7 @@ pub struct DBusSearchResult { pub plugin_name: String, pub entrypoint_id: String, pub entrypoint_name: String, + pub entrypoint_type: DBusEntrypointType, } #[derive(Debug, Serialize, Deserialize, Type)] @@ -25,6 +26,7 @@ pub struct DBusEntrypoint { pub entrypoint_id: String, pub entrypoint_name: String, pub enabled: bool, + pub entrypoint_type: DBusEntrypointType, } #[derive(Debug, DeserializeDict, SerializeDict, Type)] @@ -36,11 +38,15 @@ pub struct DBusUiWidget { pub widget_children: Vec, } +#[derive(Debug, Deserialize, Serialize, Type)] +pub struct DbusEventOpenView { + pub frontend: String, + pub entrypoint_id: String, +} #[derive(Debug, Deserialize, Serialize, Type)] -pub struct DbusEventViewCreated { - pub reconciler_mode: String, - pub view_name: String, +pub struct DbusEventRunCommand { + pub entrypoint_id: String, } #[derive(Debug, Deserialize, Serialize, Type)] @@ -50,6 +56,12 @@ pub struct DbusEventViewEvent { pub event_arguments: Vec, } +#[derive(Debug, Serialize, Deserialize, Type)] +pub enum DBusEntrypointType { + Command, + View, +} + pub type DbusUiWidgetId = u32; pub type DbusUiEventName = String; diff --git a/rust/server/db_migrations/1_initial.sql b/rust/server/db_migrations/1_initial.sql index f7bd577..6616598 100644 --- a/rust/server/db_migrations/1_initial.sql +++ b/rust/server/db_migrations/1_initial.sql @@ -14,6 +14,7 @@ CREATE TABLE plugin_entrypoint plugin_id TEXT NOT NULL REFERENCES plugin (id) ON DELETE CASCADE, name TEXT NOT NULL, enabled BOOLEAN NOT NULL, + type TEXT NOT NULL, PRIMARY KEY (id, plugin_id) ); diff --git a/rust/server/src/dbus.rs b/rust/server/src/dbus.rs index 461d5c1..a879579 100644 --- a/rust/server/src/dbus.rs +++ b/rust/server/src/dbus.rs @@ -1,8 +1,11 @@ use std::fmt::Debug; + use zbus::DBusError; -use common::dbus::{DbusEventViewCreated, DbusEventViewEvent, DBusPlugin, DBusSearchResult, DBusUiWidget}; +use common::dbus::{DBusEntrypointType, DbusEventOpenView, DbusEventRunCommand, DbusEventViewEvent, DBusPlugin, DBusSearchResult, DBusUiWidget}; use common::model::{EntrypointId, PluginId}; + +use crate::model::PluginEntrypointType; use crate::plugins::ApplicationManager; use crate::search::SearchIndex; @@ -18,6 +21,10 @@ impl DbusServer { .into_iter() .map(|item| { DBusSearchResult { + entrypoint_type: match item.entrypoint_type { + PluginEntrypointType::Command => DBusEntrypointType::Command, + PluginEntrypointType::View => DBusEntrypointType::View, + }, entrypoint_name: item.entrypoint_name, entrypoint_id: item.entrypoint_id, plugin_name: item.plugin_name, @@ -37,7 +44,6 @@ pub struct DbusManagementServer { #[zbus::dbus_interface(name = "dev.projectgauntlet.Server.Management")] impl DbusManagementServer { - #[dbus_interface(signal)] pub async fn remote_plugin_download_finished_signal(signal_ctxt: &zbus::SignalContext<'_>, plugin_id: &str) -> zbus::Result<()>; @@ -45,7 +51,7 @@ impl DbusManagementServer { &mut self, #[zbus(signal_context)] signal_context: zbus::SignalContext<'_>, - plugin_id: &str + plugin_id: &str, ) -> Result<()> { self.application_manager.download_and_add_plugin(signal_context, PluginId::from_string(plugin_id)) .await @@ -89,13 +95,16 @@ impl From for ServerError { #[zbus::dbus_proxy( - default_service = "dev.projectgauntlet.Gauntlet.Client", - default_path = "/dev/projectgauntlet/Client", - interface = "dev.projectgauntlet.Client", +default_service = "dev.projectgauntlet.Gauntlet.Client", +default_path = "/dev/projectgauntlet/Client", +interface = "dev.projectgauntlet.Client", )] trait DbusClientProxy { #[dbus_proxy(signal)] - fn view_created_signal(&self, plugin_id: &str, event: DbusEventViewCreated) -> zbus::Result<()>; + fn open_view_signal(&self, plugin_id: &str, event: DbusEventOpenView) -> zbus::Result<()>; + + #[dbus_proxy(signal)] + fn run_command_signal(&self, plugin_id: &str, event: DbusEventRunCommand) -> zbus::Result<()>; #[dbus_proxy(signal)] fn view_event_signal(&self, plugin_id: &str, event: DbusEventViewEvent) -> zbus::Result<()>; diff --git a/rust/server/src/model.rs b/rust/server/src/model.rs index febe336..203d453 100644 --- a/rust/server/src/model.rs +++ b/rust/server/src/model.rs @@ -24,13 +24,16 @@ pub type UiWidgetId = u32; #[derive(Deserialize, Serialize)] #[serde(tag = "type")] pub enum JsUiEvent { - ViewCreated { - #[serde(rename = "reconcilerMode")] - reconciler_mode: String, - #[serde(rename = "viewName")] - view_name: String + OpenView { + #[serde(rename = "frontend")] + frontend: String, + #[serde(rename = "entrypointId")] + entrypoint_id: String + }, + RunCommand { + #[serde(rename = "entrypointId")] + entrypoint_id: String }, - ViewDestroyed, ViewEvent { #[serde(rename = "widgetId")] widget_id: UiWidgetId, @@ -75,9 +78,12 @@ pub struct JsUiWidget<'a> { #[derive(Debug)] pub enum IntermediateUiEvent { - ViewCreated { - reconciler_mode: String, - view_name: String + OpenView { + frontend: String, + entrypoint_id: String + }, + RunCommand { + entrypoint_id: String }, ViewEvent { widget_id: UiWidgetId, @@ -160,3 +166,26 @@ fn from_intermediate_to_dbus_properties(value: HashMap &'static str { + match value { + PluginEntrypointType::Command => "command", + PluginEntrypointType::View => "view", + } +} + +pub fn entrypoint_from_str(value: &str) -> PluginEntrypointType { + match value { + "command" => PluginEntrypointType::Command, + "view" => PluginEntrypointType::View, + _ => { + panic!("index contains illegal entrypoint_type: {}", value) + } + } +} \ No newline at end of file diff --git a/rust/server/src/plugins/data_db_repository.rs b/rust/server/src/plugins/data_db_repository.rs index 02885d8..95b774c 100644 --- a/rust/server/src/plugins/data_db_repository.rs +++ b/rust/server/src/plugins/data_db_repository.rs @@ -51,6 +51,8 @@ pub struct GetPluginEntrypoint { pub plugin_id: String, pub name: String, pub enabled: bool, + #[sqlx(rename = "type")] + pub entrypoint_type: String, } #[derive(Deserialize, Serialize)] @@ -70,6 +72,7 @@ pub struct SavePlugin { pub struct SavePluginEntrypoint { pub id: String, pub name: String, + pub entrypoint_type: String, } #[derive(Deserialize, Serialize)] @@ -254,11 +257,12 @@ impl DataDbRepository { for entrypoint in plugin.entrypoints { // language=SQLite - sqlx::query("INSERT INTO plugin_entrypoint VALUES(?1, ?2, ?3, ?4)") + sqlx::query("INSERT INTO plugin_entrypoint VALUES(?1, ?2, ?3, ?4, ?5)") .bind(entrypoint.id) .bind(&plugin.id) .bind(entrypoint.name) .bind(true) + .bind(entrypoint.entrypoint_type) .execute(&mut *tx) .await?; } diff --git a/rust/server/src/plugins/js.rs b/rust/server/src/plugins/js.rs index ad94982..6955a8d 100644 --- a/rust/server/src/plugins/js.rs +++ b/rust/server/src/plugins/js.rs @@ -19,7 +19,7 @@ use regex::Regex; use common::model::PluginId; use component_model::{Children, Component, create_component_model, PropertyType}; -use crate::dbus::{DbusClientProxyProxy, ViewCreatedSignal, ViewEventSignal}; +use crate::dbus::{DbusClientProxyProxy, OpenViewSignal, RunCommandSignal, ViewEventSignal}; use crate::model::{from_dbus_to_intermediate_value, IntermediatePropertyValue, IntermediateUiEvent, IntermediateUiWidget, JsPropertyValue, JsUiEvent, JsUiRequestData, JsUiResponseData, JsUiWidget}; use crate::plugins::run_status::RunStatusGuard; @@ -63,9 +63,9 @@ pub async fn start_plugin_runtime(data: PluginRuntimeData, run_status_guard: Run let component_model = create_component_model(); let plugin_id = data.id.clone(); - let view_created_signal = client_proxy.receive_view_created_signal() + let run_command_signal = client_proxy.receive_run_command_signal() .await? - .filter_map(move |signal: ViewCreatedSignal| { + .filter_map(move |signal: RunCommandSignal| { let plugin_id = plugin_id.clone(); async move { let signal = signal.args().unwrap(); @@ -73,9 +73,27 @@ pub async fn start_plugin_runtime(data: PluginRuntimeData, run_status_guard: Run if PluginId::from_string(signal.plugin_id) != plugin_id { None } else { - Some(IntermediateUiEvent::ViewCreated { - reconciler_mode: signal.event.reconciler_mode, - view_name: signal.event.view_name, + Some(IntermediateUiEvent::RunCommand { + entrypoint_id: signal.event.entrypoint_id, + }) + } + } + }); + + let plugin_id = data.id.clone(); + let open_view_signal = client_proxy.receive_open_view_signal() + .await? + .filter_map(move |signal: OpenViewSignal| { + let plugin_id = plugin_id.clone(); + async move { + let signal = signal.args().unwrap(); + + if PluginId::from_string(signal.plugin_id) != plugin_id { + None + } else { + Some(IntermediateUiEvent::OpenView { + frontend: signal.event.frontend, + entrypoint_id: signal.event.entrypoint_id, }) } } @@ -139,7 +157,7 @@ pub async fn start_plugin_runtime(data: PluginRuntimeData, run_status_guard: Run } }); - let event_stream = (view_event_signal, view_created_signal, command_stream).merge(); + let event_stream = (run_command_signal, view_event_signal, open_view_signal, command_stream).merge(); let event_stream = Box::pin(event_stream); let thread_fn = move || { @@ -264,15 +282,15 @@ impl ModuleLoader for CustomModuleLoader { referrer: &str, kind: ResolutionKind, ) -> Result { - static PLUGIN_VIEW_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^gauntlet:view\?(?[a-zA-Z0-9_-]+)$").expect("invalid regex")); + static PLUGIN_ENTRYPOINT_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^gauntlet:entrypoint\?(?[a-zA-Z0-9_-]+)$").expect("invalid regex")); static PLUGIN_MODULE_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^gauntlet:module\?(?[a-zA-Z0-9_-]+)$").expect("invalid regex")); static PATH_PATTERN: Lazy = Lazy::new(|| Regex::new(r"^\./(?[a-zA-Z0-9_-]+)\.js$").expect("invalid regex")); - if PLUGIN_VIEW_PATTERN.is_match(specifier) { + if PLUGIN_ENTRYPOINT_PATTERN.is_match(specifier) { return Ok(specifier.parse()?); } - if PLUGIN_VIEW_PATTERN.is_match(referrer) || PLUGIN_MODULE_PATTERN.is_match(referrer) { + if PLUGIN_ENTRYPOINT_PATTERN.is_match(referrer) || PLUGIN_MODULE_PATTERN.is_match(referrer) { if let Some(captures) = PATH_PATTERN.captures(specifier) { return Ok(format!("gauntlet:module?{}", &captures["js_module"]).parse()?); } @@ -301,7 +319,7 @@ impl ModuleLoader for CustomModuleLoader { let mut specifier = module_specifier.clone(); specifier.set_query(None); - if &specifier == &"gauntlet:view".parse().unwrap() || &specifier == &"gauntlet:module".parse().unwrap() { + if &specifier == &"gauntlet:entrypoint".parse().unwrap() || &specifier == &"gauntlet:module".parse().unwrap() { let module = get_js_code(module_specifier, &self.code.js); return futures::future::ready(module).boxed_local(); @@ -312,9 +330,9 @@ impl ModuleLoader for CustomModuleLoader { } fn get_js_code(module_specifier: &ModuleSpecifier, js: &HashMap) -> anyhow::Result { - let view_name = module_specifier.query().expect("invalid specifier, should be validated earlier"); + let entrypoint_id = module_specifier.query().expect("invalid specifier, should be validated earlier"); - let js = js.get(view_name).ok_or(anyhow!("no code provided for view: {:?}", view_name))?; + let js = js.get(entrypoint_id).ok_or(anyhow!("no code provided for view: {:?}", entrypoint_id))?; let module = ModuleSource::new(ModuleType::JavaScript, js.clone().into(), module_specifier); @@ -588,9 +606,12 @@ async fn make_request_async(plugin_id: PluginId, dbus_client: DbusClientProxyPro fn from_intermediate_to_js_event(event: IntermediateUiEvent) -> JsUiEvent { match event { - IntermediateUiEvent::ViewCreated { reconciler_mode, view_name } => JsUiEvent::ViewCreated { - reconciler_mode, - view_name, + IntermediateUiEvent::OpenView { frontend, entrypoint_id } => JsUiEvent::OpenView { + frontend, + entrypoint_id, + }, + IntermediateUiEvent::RunCommand { entrypoint_id } => JsUiEvent::RunCommand { + entrypoint_id }, IntermediateUiEvent::ViewEvent { widget_id, event_name, event_arguments } => { let event_arguments = event_arguments.into_iter() diff --git a/rust/server/src/plugins/loader.rs b/rust/server/src/plugins/loader.rs index b8d7cff..96585bd 100644 --- a/rust/server/src/plugins/loader.rs +++ b/rust/server/src/plugins/loader.rs @@ -10,6 +10,7 @@ use serde::Deserialize; use common::model::PluginId; use crate::dbus::DbusManagementServer; +use crate::model::{entrypoint_to_str, PluginEntrypointType}; use crate::plugins::data_db_repository::{Code, DataDbRepository, PluginPermissions, SavePlugin, SavePluginEntrypoint}; pub struct PluginLoader { @@ -160,13 +161,17 @@ impl PluginLoader { tracing::debug!("Plugin config read: {:?}", config); - let plugin_name = config.metadata.name; + let plugin_name = config.gauntlet.name; let entrypoints: Vec<_> = config.entrypoint .into_iter() .map(|entrypoint| SavePluginEntrypoint { id: entrypoint.id, name: entrypoint.name, + entrypoint_type: entrypoint_to_str(match entrypoint.entrypoint_type { + PluginConfigEntrypointTypes::Command => PluginEntrypointType::Command, + PluginConfigEntrypointTypes::View => PluginEntrypointType::View, + }).to_owned() }) .collect(); @@ -203,7 +208,7 @@ struct PluginDirData { #[derive(Debug, Deserialize)] struct PluginConfig { - metadata: PluginConfigMetadata, + gauntlet: PluginConfigMetadata, entrypoint: Vec, #[serde(default)] supported_system: Vec, @@ -217,6 +222,16 @@ struct PluginConfigEntrypoint { name: String, #[allow(unused)] // used when building plugin path: String, + #[serde(rename = "type")] + entrypoint_type: PluginConfigEntrypointTypes, +} + +#[derive(Debug, Deserialize)] +pub enum PluginConfigEntrypointTypes { + #[serde(rename = "command")] + Command, + #[serde(rename = "view")] + View, } #[derive(Debug, Deserialize)] diff --git a/rust/server/src/plugins/mod.rs b/rust/server/src/plugins/mod.rs index 828faec..6866aa6 100644 --- a/rust/server/src/plugins/mod.rs +++ b/rust/server/src/plugins/mod.rs @@ -1,8 +1,10 @@ -use common::dbus::{DBusEntrypoint, DBusPlugin}; +use common::dbus::{DBusEntrypoint, DBusEntrypointType, DBusPlugin}; use common::model::{EntrypointId, PluginId}; + use crate::dirs::Dirs; +use crate::model::{entrypoint_from_str, PluginEntrypointType}; use crate::plugins::config_reader::ConfigReader; -use crate::plugins::data_db_repository::{DataDbRepository}; +use crate::plugins::data_db_repository::DataDbRepository; use crate::plugins::js::{PluginCode, PluginCommand, PluginCommandData, PluginPermissions, PluginRuntimeData, start_plugin_runtime}; use crate::plugins::loader::PluginLoader; use crate::plugins::run_status::RunStatusHolder; @@ -70,6 +72,10 @@ impl ApplicationManager { enabled: entrypoint.enabled, entrypoint_id: entrypoint.id, entrypoint_name: entrypoint.name, + entrypoint_type: match entrypoint_from_str(&entrypoint.entrypoint_type) { + PluginEntrypointType::Command => DBusEntrypointType::Command, + PluginEntrypointType::View => DBusEntrypointType::View, + } }) .collect(); @@ -219,6 +225,7 @@ impl ApplicationManager { .filter(|entrypoint| entrypoint.enabled) .map(|entrypoint| { SearchItem { + entrypoint_type: entrypoint_from_str(&entrypoint.entrypoint_type), entrypoint_name: entrypoint.name.to_owned(), entrypoint_id: entrypoint.id.to_string(), plugin_name: plugin.name.to_owned(), diff --git a/rust/server/src/search.rs b/rust/server/src/search.rs index 2217c56..d8317cc 100644 --- a/rust/server/src/search.rs +++ b/rust/server/src/search.rs @@ -3,12 +3,14 @@ use tantivy::collector::TopDocs; use tantivy::query::{AllQuery, BooleanQuery, FuzzyTermQuery, Query}; use tantivy::schema::*; use tantivy::tokenizer::TokenizerManager; +use crate::model::{entrypoint_from_str, entrypoint_to_str, PluginEntrypointType}; #[derive(Clone)] pub struct SearchIndex { index: Index, index_reader: IndexReader, + entrypoint_type: Field, entrypoint_name: Field, entrypoint_id: Field, plugin_name: Field, @@ -20,6 +22,7 @@ impl SearchIndex { let schema = { let mut schema_builder = Schema::builder(); + schema_builder.add_text_field("entrypoint_type", STORED); schema_builder.add_text_field("entrypoint_name", TEXT | STORED); schema_builder.add_text_field("entrypoint_id", STORED); schema_builder.add_text_field("plugin_name", TEXT | STORED); @@ -28,6 +31,7 @@ impl SearchIndex { schema_builder.build() }; + let entrypoint_type = schema.get_field("entrypoint_type").expect("entrypoint_type field should exist"); let entrypoint_name = schema.get_field("entrypoint_name").expect("entrypoint_name field should exist"); let entrypoint_id = schema.get_field("entrypoint_id").expect("entrypoint_id field should exist"); let plugin_name = schema.get_field("plugin_name").expect("plugin_name field should exist"); @@ -43,6 +47,7 @@ impl SearchIndex { Ok(Self { index, index_reader, + entrypoint_type, entrypoint_name, entrypoint_id, plugin_name, @@ -55,11 +60,12 @@ impl SearchIndex { index_writer.delete_all_documents()?; - println!("{:?}", search_items); + tracing::debug!("Reloading search index using following data: {:?}", search_items); for search_item in search_items { index_writer.add_document(doc!( self.entrypoint_name => search_item.entrypoint_name, + self.entrypoint_type => entrypoint_to_str(search_item.entrypoint_type), self.entrypoint_id => search_item.entrypoint_id, self.plugin_name => search_item.plugin_name, self.plugin_id => search_item.plugin_id, @@ -83,6 +89,7 @@ impl SearchIndex { SearchHandle { searcher, query_parser, + entrypoint_type: self.entrypoint_type, entrypoint_name: self.entrypoint_name, entrypoint_id: self.entrypoint_id, plugin_name: self.plugin_name, @@ -93,6 +100,7 @@ impl SearchIndex { #[derive(Clone, Debug)] pub struct SearchItem { + pub entrypoint_type: PluginEntrypointType, pub entrypoint_name: String, pub entrypoint_id: String, pub plugin_name: String, @@ -104,6 +112,7 @@ pub struct SearchHandle { query_parser: QueryParser, entrypoint_name: Field, + entrypoint_type: Field, entrypoint_id: Field, plugin_name: Field, plugin_id: Field, @@ -155,6 +164,7 @@ impl SearchHandle { .expect("index should contain just searched results"); SearchItem { + entrypoint_type: entrypoint_from_str(&get_str_field(&retrieved_doc, self.entrypoint_type)), entrypoint_name: get_str_field(&retrieved_doc, self.entrypoint_name), entrypoint_id: get_str_field(&retrieved_doc, self.entrypoint_id), plugin_name: get_str_field(&retrieved_doc, self.plugin_name),