mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-09 18:53:38 +02:00
Install plugins from Yaak plugin registry (#230)
This commit is contained in:
@@ -36,12 +36,13 @@ use yaak_models::models::{
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
||||
use yaak_plugins::events::{
|
||||
BootResponse, CallHttpRequestActionRequest, FilterResponse,
|
||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent,
|
||||
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose,
|
||||
CallHttpRequestActionRequest, FilterResponse, GetHttpAuthenticationConfigResponse,
|
||||
GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse,
|
||||
GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload, JsonPrimitive,
|
||||
PluginWindowContext, RenderPurpose,
|
||||
};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::plugin_meta::PluginMetadata;
|
||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||
use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_templates::format::format_json;
|
||||
@@ -1039,14 +1040,13 @@ async fn cmd_plugin_info<R: Runtime>(
|
||||
id: &str,
|
||||
app_handle: AppHandle<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> YaakResult<BootResponse> {
|
||||
) -> YaakResult<PluginMetadata> {
|
||||
let plugin = app_handle.db().get_plugin(id)?;
|
||||
Ok(plugin_manager
|
||||
.get_plugin_by_dir(plugin.directory.as_str())
|
||||
.await
|
||||
.ok_or(GenericError("Failed to find plugin for info".to_string()))?
|
||||
.info()
|
||||
.await)
|
||||
.info())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -86,8 +86,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
environment.as_ref(),
|
||||
&cb,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to render http request");
|
||||
.await
|
||||
.expect("Failed to render http request");
|
||||
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
||||
http_request,
|
||||
}))
|
||||
@@ -115,7 +115,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: format!(
|
||||
"Plugin error from {}: {}",
|
||||
plugin_handle.name().await,
|
||||
plugin_handle.info().name,
|
||||
resp.error
|
||||
),
|
||||
color: Some(Color::Danger),
|
||||
@@ -188,7 +188,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
cookie_jar,
|
||||
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
||||
)
|
||||
.await;
|
||||
.await;
|
||||
|
||||
let http_response = match result {
|
||||
Ok(r) => r,
|
||||
@@ -257,17 +257,17 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
None
|
||||
}
|
||||
InternalEventPayload::SetKeyValueRequest(req) => {
|
||||
let name = plugin_handle.name().await;
|
||||
let name = plugin_handle.info().name;
|
||||
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
|
||||
Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {}))
|
||||
}
|
||||
InternalEventPayload::GetKeyValueRequest(req) => {
|
||||
let name = plugin_handle.name().await;
|
||||
let name = plugin_handle.info().name;
|
||||
let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value);
|
||||
Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value }))
|
||||
}
|
||||
InternalEventPayload::DeleteKeyValueRequest(req) => {
|
||||
let name = plugin_handle.name().await;
|
||||
let name = plugin_handle.info().name;
|
||||
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key).unwrap();
|
||||
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted }))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use log::{info, warn};
|
||||
use std::collections::HashMap;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Url};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
|
||||
use yaak_plugins::api::get_plugin;
|
||||
use yaak_plugins::events::{Color, ShowToastRequest};
|
||||
use yaak_plugins::install::download_and_install;
|
||||
|
||||
@@ -23,14 +22,10 @@ pub(crate) async fn handle_deep_link<R: Runtime>(
|
||||
"install-plugin" => {
|
||||
let name = query_map.get("name").unwrap();
|
||||
let version = query_map.get("version").cloned();
|
||||
let plugin_version = get_plugin(&app_handle, &name, version).await?;
|
||||
_ = window.set_focus();
|
||||
let confirmed_install = app_handle
|
||||
.dialog()
|
||||
.message(format!(
|
||||
"Install plugin {}@{}?",
|
||||
plugin_version.name, plugin_version.version
|
||||
))
|
||||
.message(format!("Install plugin {name} {version:?}?",))
|
||||
.kind(MessageDialogKind::Info)
|
||||
.buttons(MessageDialogButtons::OkCustom("Install".to_string()))
|
||||
.blocking_show();
|
||||
@@ -39,14 +34,11 @@ pub(crate) async fn handle_deep_link<R: Runtime>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
download_and_install(window, &plugin_version).await?;
|
||||
let pv = download_and_install(window, name, version).await?;
|
||||
app_handle.emit(
|
||||
"show_toast",
|
||||
ShowToastRequest {
|
||||
message: format!(
|
||||
"Installed {}@{}",
|
||||
plugin_version.name, plugin_version.version
|
||||
),
|
||||
message: format!("Installed {name}@{}", pv.version),
|
||||
color: Some(Color::Success),
|
||||
icon: None,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::connection_or_tx::ConnectionOrTx;
|
||||
use crate::error::Error::RowNotFound;
|
||||
use crate::error::Error::DBRowNotFound;
|
||||
use crate::models::{AnyModel, UpsertModelInfo};
|
||||
use crate::util::{ModelChangeEvent, ModelPayload, UpdateSource};
|
||||
use rusqlite::OptionalExtension;
|
||||
@@ -26,7 +26,7 @@ impl<'a> DbContext<'a> {
|
||||
{
|
||||
match self.find_optional::<M>(col, value) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(RowNotFound),
|
||||
None => Err(DBRowNotFound(format!("{:?}", M::table_name()))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ pub enum Error {
|
||||
#[error("Multiple base environments for {0}. Delete duplicates before continuing.")]
|
||||
MultipleBaseEnvironments(String),
|
||||
|
||||
#[error("Row not found")]
|
||||
RowNotFound,
|
||||
#[error("Database row not found: {0}")]
|
||||
DBRowNotFound(String),
|
||||
|
||||
#[error("unknown error")]
|
||||
Unknown,
|
||||
|
||||
@@ -11,7 +11,7 @@ use sea_query::{IntoColumnRef, IntoIden, IntoTableRef, Order, SimpleExpr, enum_d
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::str::FromStr;
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -123,7 +123,7 @@ pub struct Settings {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for Settings {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
SettingsIden::Table
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ pub struct Workspace {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for Workspace {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
WorkspaceIden::Table
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ pub struct WorkspaceMeta {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for WorkspaceMeta {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
WorkspaceMetaIden::Table
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ pub struct CookieJar {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for CookieJar {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
CookieJarIden::Table
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@ pub struct Environment {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for Environment {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
EnvironmentIden::Table
|
||||
}
|
||||
|
||||
@@ -655,7 +655,7 @@ pub struct Folder {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for Folder {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
FolderIden::Table
|
||||
}
|
||||
|
||||
@@ -786,7 +786,7 @@ pub struct HttpRequest {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for HttpRequest {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
HttpRequestIden::Table
|
||||
}
|
||||
|
||||
@@ -913,7 +913,7 @@ pub struct WebsocketConnection {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for WebsocketConnection {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
WebsocketConnectionIden::Table
|
||||
}
|
||||
|
||||
@@ -1027,7 +1027,7 @@ pub struct WebsocketRequest {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for WebsocketRequest {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
WebsocketRequestIden::Table
|
||||
}
|
||||
|
||||
@@ -1152,7 +1152,7 @@ pub struct WebsocketEvent {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for WebsocketEvent {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
WebsocketEventIden::Table
|
||||
}
|
||||
|
||||
@@ -1269,7 +1269,7 @@ pub struct HttpResponse {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for HttpResponse {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
HttpResponseIden::Table
|
||||
}
|
||||
|
||||
@@ -1377,7 +1377,7 @@ pub struct GraphQlIntrospection {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for GraphQlIntrospection {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
GraphQlIntrospectionIden::Table
|
||||
}
|
||||
|
||||
@@ -1461,7 +1461,7 @@ pub struct GrpcRequest {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for GrpcRequest {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
GrpcRequestIden::Table
|
||||
}
|
||||
|
||||
@@ -1588,7 +1588,7 @@ pub struct GrpcConnection {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for GrpcConnection {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
GrpcConnectionIden::Table
|
||||
}
|
||||
|
||||
@@ -1708,7 +1708,7 @@ pub struct GrpcEvent {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for GrpcEvent {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
GrpcEventIden::Table
|
||||
}
|
||||
|
||||
@@ -1799,7 +1799,7 @@ pub struct Plugin {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for Plugin {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
PluginIden::Table
|
||||
}
|
||||
|
||||
@@ -1881,7 +1881,7 @@ pub struct SyncState {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for SyncState {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
SyncStateIden::Table
|
||||
}
|
||||
|
||||
@@ -1964,7 +1964,7 @@ pub struct KeyValue {
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for KeyValue {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
fn table_name() -> impl IntoTableRef + Debug {
|
||||
KeyValueIden::Table
|
||||
}
|
||||
|
||||
@@ -2181,7 +2181,7 @@ impl AnyModel {
|
||||
}
|
||||
|
||||
pub trait UpsertModelInfo {
|
||||
fn table_name() -> impl IntoTableRef;
|
||||
fn table_name() -> impl IntoTableRef + Debug;
|
||||
fn id_column() -> impl IntoIden + Eq + Clone;
|
||||
fn generate_id() -> String;
|
||||
fn order_by() -> (impl IntoColumnRef, Order);
|
||||
|
||||
8
src-tauri/yaak-plugins/bindings/gen_api.ts
Normal file
8
src-tauri/yaak-plugins/bindings/gen_api.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginVersion } from "./gen_search.js";
|
||||
|
||||
export type PluginNameVersion = { name: string, version: string, };
|
||||
|
||||
export type PluginSearchResponse = { plugins: Array<PluginVersion>, };
|
||||
|
||||
export type PluginUpdatesResponse = { plugins: Array<PluginNameVersion>, };
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginSearchResponse = { results: Array<PluginVersion>, };
|
||||
export type PluginMetadata = { version: string, name: string, displayName: string, description: string | null, homepageUrl: string | null, repositoryUrl: string | null, };
|
||||
|
||||
export type PluginVersion = { id: string, version: string, description: string | null, displayName: string, homepageUrl: string | null, repositoryUrl: string, checksum: string, readme: string | null, yanked: boolean, };
|
||||
export type PluginVersion = { id: string, version: string, description: string | null, name: string, displayName: string, homepageUrl: string | null, repositoryUrl: string | null, checksum: string, readme: string | null, yanked: boolean, };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const COMMANDS: &[&str] = &["search", "install"];
|
||||
const COMMANDS: &[&str] = &["search", "install", "updates"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { PluginSearchResponse, PluginVersion } from './bindings/gen_search';
|
||||
import { PluginSearchResponse, PluginUpdatesResponse } from './bindings/gen_api';
|
||||
|
||||
export * from './bindings/gen_models';
|
||||
export * from './bindings/gen_events';
|
||||
@@ -9,6 +9,10 @@ export async function searchPlugins(query: string) {
|
||||
return invoke<PluginSearchResponse>('plugin:yaak-plugins|search', { query });
|
||||
}
|
||||
|
||||
export async function installPlugin(plugin: PluginVersion) {
|
||||
return invoke<string>('plugin:yaak-plugins|install', { plugin });
|
||||
export async function installPlugin(name: string, version: string | null) {
|
||||
return invoke<string>('plugin:yaak-plugins|install', { name, version });
|
||||
}
|
||||
|
||||
export async function checkPluginUpdates() {
|
||||
return invoke<PluginUpdatesResponse>('plugin:yaak-plugins|updates', {});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = ["allow-search", "allow-install"]
|
||||
permissions = ["allow-search", "allow-install", "allow-updates"]
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use crate::error::Error::ApiErr;
|
||||
use crate::commands::{PluginSearchResponse, PluginVersion};
|
||||
use crate::error::Result;
|
||||
use crate::plugin_meta::get_plugin_meta;
|
||||
use log::{info, warn};
|
||||
use reqwest::{Response, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use log::info;
|
||||
use tauri::{AppHandle, Runtime, is_dev};
|
||||
use ts_rs::TS;
|
||||
use yaak_common::api_client::yaak_api_client;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use crate::error::Error::ApiErr;
|
||||
|
||||
pub async fn get_plugin<R: Runtime>(
|
||||
@@ -44,6 +51,38 @@ pub async fn download_plugin_archive<R: Runtime>(
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn check_plugin_updates<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
) -> Result<PluginUpdatesResponse> {
|
||||
let name_versions: Vec<PluginNameVersion> = app_handle
|
||||
.db()
|
||||
.list_plugins()?
|
||||
.into_iter()
|
||||
.filter_map(|p| match get_plugin_meta(&Path::new(&p.directory)) {
|
||||
Ok(m) => Some(PluginNameVersion {
|
||||
name: m.name,
|
||||
version: m.version,
|
||||
}),
|
||||
Err(e) => {
|
||||
warn!("Failed to get plugin metadata: {}", e);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let url = base_url("/updates");
|
||||
let body = serde_json::to_vec(&PluginUpdatesResponse {
|
||||
plugins: name_versions,
|
||||
})?;
|
||||
let resp = yaak_api_client(app_handle)?.post(url.clone()).body(body).send().await?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(ApiErr(format!("{} response to {}", resp.status(), url.to_string())));
|
||||
}
|
||||
|
||||
let results: PluginUpdatesResponse = resp.json().await?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn search_plugins<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
query: &str,
|
||||
@@ -65,3 +104,41 @@ fn base_url(path: &str) -> Url {
|
||||
};
|
||||
Url::from_str(&format!("{base_url}{path}")).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginVersion {
|
||||
pub id: String,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub homepage_url: Option<String>,
|
||||
pub repository_url: Option<String>,
|
||||
pub checksum: String,
|
||||
pub readme: Option<String>,
|
||||
pub yanked: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_api.ts")]
|
||||
pub struct PluginSearchResponse {
|
||||
pub plugins: Vec<PluginVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_api.ts")]
|
||||
pub struct PluginNameVersion {
|
||||
name: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_api.ts")]
|
||||
pub struct PluginUpdatesResponse {
|
||||
pub plugins: Vec<PluginNameVersion>,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::api::search_plugins;
|
||||
use crate::api::{
|
||||
PluginSearchResponse, PluginUpdatesResponse, check_plugin_updates, search_plugins,
|
||||
};
|
||||
use crate::error::Result;
|
||||
use crate::install::download_and_install;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Runtime, WebviewWindow, command};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn search<R: Runtime>(
|
||||
@@ -16,30 +16,14 @@ pub(crate) async fn search<R: Runtime>(
|
||||
#[command]
|
||||
pub(crate) async fn install<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin: PluginVersion,
|
||||
) -> Result<String> {
|
||||
download_and_install(&window, &plugin).await
|
||||
name: &str,
|
||||
version: Option<String>,
|
||||
) -> Result<()> {
|
||||
download_and_install(&window, name, version).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginSearchResponse {
|
||||
pub results: Vec<PluginVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginVersion {
|
||||
pub id: String,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub homepage_url: Option<String>,
|
||||
pub repository_url: Option<String>,
|
||||
pub checksum: String,
|
||||
pub readme: Option<String>,
|
||||
pub yanked: bool,
|
||||
#[command]
|
||||
pub(crate) async fn updates<R: Runtime>(app_handle: AppHandle<R>) -> Result<PluginUpdatesResponse> {
|
||||
check_plugin_updates(&app_handle).await
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
use crate::api::download_plugin_archive;
|
||||
use crate::api::{PluginVersion, download_plugin_archive, get_plugin};
|
||||
use crate::checksum::compute_checksum;
|
||||
use crate::commands::PluginVersion;
|
||||
use crate::error::Error::PluginErr;
|
||||
use crate::error::Result;
|
||||
use crate::events::PluginWindowContext;
|
||||
use crate::manager::PluginManager;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
use std::fs::create_dir_all;
|
||||
use std::fs::{create_dir_all, remove_dir_all};
|
||||
use std::io::Cursor;
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use yaak_models::models::Plugin;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{UpdateSource, generate_id};
|
||||
use yaak_models::util::UpdateSource;
|
||||
|
||||
pub async fn download_and_install<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
plugin_version: &PluginVersion,
|
||||
) -> Result<String> {
|
||||
name: &str,
|
||||
version: Option<String>,
|
||||
) -> Result<PluginVersion> {
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
let plugin_version = get_plugin(window.app_handle(), name, version).await?;
|
||||
let resp = download_plugin_archive(window.app_handle(), &plugin_version).await?;
|
||||
let bytes = resp.bytes().await?;
|
||||
|
||||
@@ -32,17 +34,19 @@ pub async fn download_and_install<R: Runtime>(
|
||||
|
||||
info!("Checksum matched {}", checksum);
|
||||
|
||||
let plugin_dir = window.path().app_data_dir()?.join("plugins").join(generate_id());
|
||||
let plugin_dir = plugin_manager.installed_plugin_dir.join(name);
|
||||
let plugin_dir_str = plugin_dir.to_str().unwrap().to_string();
|
||||
|
||||
// Re-create the plugin directory
|
||||
let _ = remove_dir_all(&plugin_dir);
|
||||
create_dir_all(&plugin_dir)?;
|
||||
|
||||
zip_extract::extract(Cursor::new(&bytes), &plugin_dir, true)?;
|
||||
info!("Extracted plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &plugin_dir_str).await?;
|
||||
|
||||
let p = window.db().upsert_plugin(
|
||||
window.db().upsert_plugin(
|
||||
&Plugin {
|
||||
id: plugin_version.id.clone(),
|
||||
checked_at: Some(Utc::now().naive_utc()),
|
||||
@@ -56,5 +60,5 @@ pub async fn download_and_install<R: Runtime>(
|
||||
|
||||
info!("Installed plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||
|
||||
Ok(p.id)
|
||||
Ok(plugin_version)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::commands::{install, search};
|
||||
use crate::commands::{install, search, updates};
|
||||
use crate::manager::PluginManager;
|
||||
use log::info;
|
||||
use std::process::exit;
|
||||
@@ -18,10 +18,11 @@ mod util;
|
||||
mod checksum;
|
||||
pub mod api;
|
||||
pub mod install;
|
||||
pub mod plugin_meta;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("yaak-plugins")
|
||||
.invoke_handler(generate_handler![search, install])
|
||||
.invoke_handler(generate_handler![search, install, updates])
|
||||
.setup(|app_handle, _| {
|
||||
let manager = PluginManager::new(app_handle.clone());
|
||||
app_handle.manage(manager.clone());
|
||||
|
||||
@@ -39,7 +39,7 @@ pub struct PluginManager {
|
||||
kill_tx: tokio::sync::watch::Sender<bool>,
|
||||
ws_service: Arc<PluginRuntimeServerWebsocket>,
|
||||
vendored_plugin_dir: PathBuf,
|
||||
installed_plugin_dir: PathBuf,
|
||||
pub(crate) installed_plugin_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -62,8 +62,11 @@ impl PluginManager {
|
||||
.resolve("vendored/plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let installed_plugin_dir =
|
||||
app_handle.path().app_data_dir().expect("failed to get app data dir");
|
||||
let installed_plugin_dir = app_handle
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.expect("failed to get app data dir")
|
||||
.join("installed-plugins");
|
||||
|
||||
let plugin_manager = PluginManager {
|
||||
plugins: Default::default(),
|
||||
@@ -209,7 +212,7 @@ impl PluginManager {
|
||||
None => return Err(ClientNotInitializedErr),
|
||||
Some(tx) => tx,
|
||||
};
|
||||
let plugin_handle = PluginHandle::new(dir, tx.clone());
|
||||
let plugin_handle = PluginHandle::new(dir, tx.clone())?;
|
||||
let dir_path = Path::new(dir);
|
||||
let is_vendored = dir_path.starts_with(self.vendored_plugin_dir.as_path());
|
||||
let is_installed = dir_path.starts_with(self.installed_plugin_dir.as_path());
|
||||
@@ -231,14 +234,11 @@ impl PluginManager {
|
||||
// Add the new plugin
|
||||
self.plugins.lock().await.push(plugin_handle.clone());
|
||||
|
||||
let resp = match event.payload {
|
||||
let _ = match event.payload {
|
||||
InternalEventPayload::BootResponse(resp) => resp,
|
||||
_ => return Err(UnknownEventErr),
|
||||
};
|
||||
|
||||
// Set the boot response
|
||||
plugin_handle.set_boot_response(&resp).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ impl PluginManager {
|
||||
|
||||
pub async fn get_plugin_by_name(&self, name: &str) -> Option<PluginHandle> {
|
||||
for plugin in self.plugins.lock().await.iter().cloned() {
|
||||
let info = plugin.info().await;
|
||||
let info = plugin.info();
|
||||
if info.name == name {
|
||||
return Some(plugin);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,35 @@
|
||||
use crate::error::Result;
|
||||
use crate::events::{BootResponse, InternalEvent, InternalEventPayload, PluginWindowContext};
|
||||
use crate::events::{InternalEvent, InternalEventPayload, PluginWindowContext};
|
||||
use crate::plugin_meta::{PluginMetadata, get_plugin_meta};
|
||||
use crate::util::gen_id;
|
||||
use log::info;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PluginHandle {
|
||||
pub ref_id: String,
|
||||
pub dir: String,
|
||||
pub(crate) to_plugin_tx: Arc<Mutex<mpsc::Sender<InternalEvent>>>,
|
||||
pub(crate) boot_resp: Arc<Mutex<BootResponse>>,
|
||||
pub(crate) metadata: PluginMetadata,
|
||||
}
|
||||
|
||||
impl PluginHandle {
|
||||
pub fn new(dir: &str, tx: mpsc::Sender<InternalEvent>) -> Self {
|
||||
pub fn new(dir: &str, tx: mpsc::Sender<InternalEvent>) -> Result<Self> {
|
||||
let ref_id = gen_id();
|
||||
let metadata = get_plugin_meta(&Path::new(dir))?;
|
||||
|
||||
PluginHandle {
|
||||
Ok(PluginHandle {
|
||||
ref_id: ref_id.clone(),
|
||||
dir: dir.to_string(),
|
||||
to_plugin_tx: Arc::new(Mutex::new(tx)),
|
||||
boot_resp: Arc::new(Mutex::new(BootResponse::default())),
|
||||
}
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn name(&self) -> String {
|
||||
self.boot_resp.lock().await.name.clone()
|
||||
}
|
||||
|
||||
pub async fn info(&self) -> BootResponse {
|
||||
let resp = &*self.boot_resp.lock().await;
|
||||
resp.clone()
|
||||
pub fn info(&self) -> PluginMetadata {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
pub fn build_event_to_send(
|
||||
@@ -72,9 +69,4 @@ impl PluginHandle {
|
||||
self.to_plugin_tx.lock().await.send(event.to_owned()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_boot_response(&self, resp: &BootResponse) {
|
||||
let mut boot_resp = self.boot_resp.lock().await;
|
||||
*boot_resp = resp.clone();
|
||||
}
|
||||
}
|
||||
|
||||
64
src-tauri/yaak-plugins/src/plugin_meta.rs
Normal file
64
src-tauri/yaak-plugins/src/plugin_meta.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::error::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginMetadata {
|
||||
pub version: String,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub description: Option<String>,
|
||||
pub homepage_url: Option<String>,
|
||||
pub repository_url: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn get_plugin_meta(plugin_dir: &Path) -> Result<PluginMetadata> {
|
||||
let package_json = fs::File::open(plugin_dir.join("package.json"))?;
|
||||
let package_json: PackageJson = serde_json::from_reader(package_json)?;
|
||||
|
||||
let display_name = match package_json.display_name {
|
||||
None => {
|
||||
let display_name = package_json.name.to_string();
|
||||
let display_name = display_name.split('/').last().unwrap_or(&package_json.name);
|
||||
let display_name = display_name.strip_prefix("yaak-plugin-").unwrap_or(&display_name);
|
||||
let display_name = display_name.strip_prefix("yaak-").unwrap_or(&display_name);
|
||||
display_name.to_string()
|
||||
}
|
||||
Some(n) => n,
|
||||
};
|
||||
|
||||
Ok(PluginMetadata {
|
||||
version: package_json.version,
|
||||
description: package_json.description,
|
||||
name: package_json.name,
|
||||
display_name,
|
||||
homepage_url: package_json.homepage,
|
||||
repository_url: match package_json.repository {
|
||||
None => None,
|
||||
Some(RepositoryField::Object { url }) => Some(url),
|
||||
Some(RepositoryField::String(url)) => Some(url),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PackageJson {
|
||||
pub name: String,
|
||||
pub display_name: Option<String>,
|
||||
pub version: String,
|
||||
pub repository: Option<RepositoryField>,
|
||||
pub homepage: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum RepositoryField {
|
||||
String(String),
|
||||
Object { url: String },
|
||||
}
|
||||
Reference in New Issue
Block a user