Add command entrypoints

This commit is contained in:
Exidex 2023-12-27 21:25:13 +01:00
parent 0745e52cd4
commit 7b6d8b8a09
25 changed files with 296 additions and 107 deletions

View file

@ -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([]),

View file

@ -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"

View file

@ -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": {

View file

@ -6,7 +6,7 @@
"target": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"types": ["@project-gauntlet/typings"]
"types": ["@project-gauntlet/typings", "@project-gauntlet/deno"]
},
"lib": ["ES2020"]
}

View file

@ -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 };
}

View file

@ -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"]
environment = ["RUST_LOG"]
system=["systemMemoryInfo"]

View file

@ -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"
}

View file

@ -0,0 +1,3 @@
const systemMemoryInfo = Deno.systemMemoryInfo();
console.dir(systemMemoryInfo)

View file

@ -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"

View file

@ -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<string, any>, newObj: Record<string, any>): 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);

View file

@ -6,7 +6,7 @@
"target": "ES2022",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"types": ["@project-gauntlet/typings"]
"types": ["@project-gauntlet/typings", "@project-gauntlet/deno"]
},
"lib": ["ES2020"]
}

16
js/typings/index.d.ts vendored
View file

@ -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 = {

View file

@ -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<()>;

View file

@ -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)]

View file

@ -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<NativeUiSearchResult>),
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)

View file

@ -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<Message> {
on_open_view: Box<dyn Fn(OpenViewEvent) -> Message>,
on_run_command: Box<dyn Fn(RunCommandEvent) -> Message>,
search_results: Vec<NativeUiSearchResult>,
}
pub fn search_list<Message>(
search_results: Vec<NativeUiSearchResult>,
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<Message> {
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<Message> SearchList<Message> {
pub fn new(search_results: Vec<NativeUiSearchResult>, on_open_view: impl Fn(OpenViewEvent) -> Message + 'static) -> Self {
pub fn new(
search_results: Vec<NativeUiSearchResult>,
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<Message> Component<Message, GauntletRenderer> for SearchList<Message> {
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<Message> Component<Message, GauntletRenderer> for SearchList<Message> {
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()
})

View file

@ -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<DBusUiWidget>,
}
#[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<DBusUiPropertyValue>,
}
#[derive(Debug, Serialize, Deserialize, Type)]
pub enum DBusEntrypointType {
Command,
View,
}
pub type DbusUiWidgetId = u32;
pub type DbusUiEventName = String;

View file

@ -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)
);

View file

@ -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<anyhow::Error> 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<()>;

View file

@ -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<String, IntermediatePrope
DBusUiPropertyContainer(properties)
}
#[derive(Debug, Clone)]
pub enum PluginEntrypointType {
Command,
View,
}
pub fn entrypoint_to_str(value: PluginEntrypointType) -> &'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)
}
}
}

View file

@ -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?;
}

View file

@ -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<ModuleSpecifier, anyhow::Error> {
static PLUGIN_VIEW_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^gauntlet:view\?(?<entrypoint_id>[a-zA-Z0-9_-]+)$").expect("invalid regex"));
static PLUGIN_ENTRYPOINT_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^gauntlet:entrypoint\?(?<entrypoint_id>[a-zA-Z0-9_-]+)$").expect("invalid regex"));
static PLUGIN_MODULE_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^gauntlet:module\?(?<entrypoint_id>[a-zA-Z0-9_-]+)$").expect("invalid regex"));
static PATH_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\./(?<js_module>[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<String, String>) -> anyhow::Result<ModuleSource> {
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()

View file

@ -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<PluginConfigEntrypoint>,
#[serde(default)]
supported_system: Vec<PluginConfigSupportedSystem>,
@ -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)]

View file

@ -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(),

View file

@ -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),