Model and DB refactor (#61)

- [x] Move from `sqlx` to `rusqlite`
- [x] Generate TS types from Rust models
This commit is contained in:
Gregory Schier
2024-08-05 07:58:20 -07:00
committed by GitHub
parent 71013fd701
commit 989b5a8058
193 changed files with 7083 additions and 8337 deletions

View File

@@ -0,0 +1,22 @@
[package]
name = "yaak_plugin_runtime"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.86"
command-group = "5.0.1"
dunce = "1.0.4"
log = "0.4.21"
prost = "0.13.1"
rand = "0.8.5"
reqwest = { version = "0.12.5", features = ["stream"] }
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.113"
tauri = { workspace = true }
tauri-plugin-shell = { workspace = true }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "process"] }
tonic = "0.12.1"
[build-dependencies]
tonic-build = "0.12.1"

View File

@@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("../../proto/plugins/runtime.proto")?;
Ok(())
}

View File

@@ -0,0 +1,41 @@
extern crate core;
use crate::manager::PluginManager;
use log::info;
use std::process::exit;
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Manager, RunEvent, Runtime, State};
use tokio::sync::Mutex;
pub mod manager;
mod nodejs;
pub mod plugin_runtime {
tonic::include_proto!("yaak.plugins.runtime");
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("yaak_plugin_runtime")
.setup(|app, _| {
tauri::async_runtime::block_on(async move {
let manager = PluginManager::new(&app).await;
let manager_state = Mutex::new(manager);
app.manage(manager_state);
Ok(())
})
})
.on_event(|app, e| match e {
// TODO: Also exit when app is force-quit (eg. cmd+r in IntelliJ runner)
RunEvent::ExitRequested { api, .. } => {
api.prevent_exit();
tauri::async_runtime::block_on(async move {
info!("Exiting plugin runtime due to app exit");
let manager: State<Mutex<PluginManager>> = app.state();
manager.lock().await.cleanup().await;
exit(0);
});
}
_ => {}
})
.build()
}

View File

@@ -0,0 +1,86 @@
use log::{debug, info};
use std::time::Duration;
use tauri::{AppHandle, Manager, Runtime};
use tokio::sync::watch::Sender;
use tonic::transport::Channel;
use crate::nodejs::node_start;
use crate::plugin_runtime::plugin_runtime_client::PluginRuntimeClient;
use crate::plugin_runtime::{
HookExportRequest, HookImportRequest, HookResponse, HookResponseFilterRequest,
};
pub struct PluginManager {
client: PluginRuntimeClient<Channel>,
kill_tx: Sender<bool>,
}
impl PluginManager {
pub async fn new<R: Runtime>(app_handle: &AppHandle<R>) -> PluginManager {
let temp_dir = app_handle.path().temp_dir().unwrap();
let (kill_tx, kill_rx) = tokio::sync::watch::channel(false);
let start_resp = node_start(app_handle, &temp_dir, &kill_rx).await;
info!("Connecting to gRPC client at {}", start_resp.addr);
let client = match PluginRuntimeClient::connect(start_resp.addr.clone()).await {
Ok(v) => v,
Err(err) => panic!("{}", err.to_string()),
};
PluginManager { client, kill_tx }
}
pub async fn cleanup(&mut self) {
self.kill_tx.send_replace(true);
// Give it a bit of time to kill
tokio::time::sleep(Duration::from_millis(500)).await;
}
pub async fn run_import(&mut self, data: &str) -> Result<HookResponse, String> {
let response = self
.client
.hook_import(tonic::Request::new(HookImportRequest {
data: data.to_string(),
}))
.await
.map_err(|e| e.message().to_string())?;
Ok(response.into_inner())
}
pub async fn run_export_curl(&mut self, request: &str) -> Result<HookResponse, String> {
let response = self
.client
.hook_export(tonic::Request::new(HookExportRequest {
request: request.to_string(),
}))
.await
.map_err(|e| e.message().to_string())?;
Ok(response.into_inner())
}
pub async fn run_response_filter(
&mut self,
filter: &str,
body: &str,
content_type: &str,
) -> Result<HookResponse, String> {
debug!("Running plugin filter");
let response = self
.client
.hook_response_filter(tonic::Request::new(HookResponseFilterRequest {
filter: filter.to_string(),
body: body.to_string(),
content_type: content_type.to_string(),
}))
.await
.map_err(|e| e.message().to_string())?;
let result = response.into_inner();
debug!("Ran plugin response filter {}", result.data);
Ok(result)
}
}

View File

@@ -0,0 +1,102 @@
use std::path::PathBuf;
use std::time::Duration;
use log::info;
use rand::distributions::{Alphanumeric, DistString};
use serde;
use serde::Deserialize;
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager, Runtime};
use tauri_plugin_shell::ShellExt;
use tokio::fs;
use tokio::sync::watch::Receiver;
#[derive(Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
struct PortFile {
port: i32,
}
pub struct StartResp {
pub addr: String,
}
pub async fn node_start<R: Runtime>(
app: &AppHandle<R>,
temp_dir: &PathBuf,
kill_rx: &Receiver<bool>,
) -> StartResp {
let port_file_path = temp_dir.join(Alphanumeric.sample_string(&mut rand::thread_rng(), 10));
let plugins_dir = app
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource");
let plugin_runtime_main = app
.path()
.resolve("plugin-runtime", BaseDirectory::Resource)
.expect("failed to resolve plugin runtime resource")
.join("index.cjs");
// HACK: Remove UNC prefix for Windows paths to pass to sidecar
let plugins_dir = dunce::simplified(plugins_dir.as_path())
.to_string_lossy()
.to_string();
let plugin_runtime_main = dunce::simplified(plugin_runtime_main.as_path())
.to_string_lossy()
.to_string();
info!(
"Starting plugin runtime\n → port_file={}\n → plugins_dir={}\n → runtime_dir={}",
port_file_path.to_string_lossy(),
plugins_dir,
plugin_runtime_main,
);
let cmd = app
.shell()
.sidecar("yaaknode")
.expect("yaaknode not found")
.env("YAAK_GRPC_PORT_FILE_PATH", port_file_path.clone())
.env("YAAK_PLUGINS_DIR", plugins_dir)
.args(&[plugin_runtime_main]);
println!("Waiting on plugin runtime");
let (_, child) = cmd.spawn().expect("yaaknode failed to start");
let mut kill_rx = kill_rx.clone();
// Check on child
tokio::spawn(async move {
kill_rx
.wait_for(|b| *b == true)
.await
.expect("Kill channel errored");
info!("Killing plugin runtime");
child.kill().expect("Failed to kill plugin runtime");
info!("Killed plugin runtime");
return;
});
let start = std::time::Instant::now();
let port_file_contents = loop {
if start.elapsed().as_millis() > 30000 {
panic!("Failed to read port file in time");
}
match fs::read_to_string(port_file_path.clone()).await {
Ok(s) => break s,
Err(_) => {
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
};
let port_file: PortFile = serde_json::from_str(port_file_contents.as_str()).unwrap();
info!("Started plugin runtime on :{}", port_file.port);
let addr = format!("http://localhost:{}", port_file.port);
StartResp { addr }
}