use std::path::PathBuf;
use actix_web::{web, HttpResponse, Responder};
use bamboo_plugin::{InstallDisposition, PluginError, PluginInstaller, PluginSource};
use crate::app_state::AppState;
use crate::plugin_installer::ServerPluginInstaller;
use crate::plugin_source::{install_plugin_from_source, stage_plugin_source, PluginSourceInput};
use super::api_types::{to_view, InstallPluginRequest, PluginListResponse};
use super::errors::plugin_error_response;
fn plugins_root(state: &AppState) -> PathBuf {
state.app_data_dir.join("plugins")
}
fn to_source_input(source: PluginSource) -> PluginSourceInput {
match source {
PluginSource::LocalDir { path } => PluginSourceInput::LocalDir(path),
PluginSource::LocalArchive { path } => PluginSourceInput::LocalArchive(path),
PluginSource::Url {
url,
sha256,
allow_unverified,
allow_untrusted_host,
allow_unsigned,
signed_by: _,
} => PluginSourceInput::Url {
url,
sha256,
allow_unverified,
allow_untrusted_host,
allow_unsigned,
},
}
}
pub async fn list_plugins(state: web::Data<AppState>) -> impl Responder {
let installer = ServerPluginInstaller::new(state.clone());
match installer.list().await {
Ok(entries) => {
let mut plugins = Vec::with_capacity(entries.len());
for entry in entries {
plugins.push(to_view(entry).await);
}
HttpResponse::Ok().json(PluginListResponse { plugins })
}
Err(error) => plugin_error_response(&error),
}
}
pub async fn install_plugin(
state: web::Data<AppState>,
body: web::Json<InstallPluginRequest>,
) -> impl Responder {
let installer = ServerPluginInstaller::new(state.clone());
let root = plugins_root(&state);
let input = to_source_input(body.into_inner().source);
let trust = state.config.read().await.plugin_trust.clone();
match install_plugin_from_source(
&installer,
input,
&root,
&trust,
InstallDisposition::FailIfInstalled,
)
.await
{
Ok(entry) => HttpResponse::Created().json(to_view(entry).await),
Err(error) => plugin_error_response(&error),
}
}
pub async fn update_plugin(
state: web::Data<AppState>,
path: web::Path<String>,
body: web::Json<InstallPluginRequest>,
) -> impl Responder {
let path_id = path.into_inner();
let installer = ServerPluginInstaller::new(state.clone());
let root = plugins_root(&state);
let input = to_source_input(body.into_inner().source);
let trust = state.config.read().await.plugin_trust.clone();
let staged = match stage_plugin_source(input, &root, &trust).await {
Ok(staged) => staged,
Err(error) => return plugin_error_response(&error),
};
if staged.manifest.id != path_id {
let manifest_id = staged.manifest.id.clone();
staged.rollback().await;
return plugin_error_response(&PluginError::InvalidManifest(format!(
"path id '{path_id}' does not match the source's manifest id '{manifest_id}'"
)));
}
let manifest = staged.manifest.clone();
let plugin_dir = staged.plugin_dir.clone();
let source = staged.source.clone();
match installer
.install(
&manifest,
&plugin_dir,
source,
InstallDisposition::Upgrade,
chrono::Utc::now(),
)
.await
{
Ok(entry) => {
staged.commit().await;
HttpResponse::Ok().json(to_view(entry).await)
}
Err(error) => {
staged.rollback().await;
plugin_error_response(&error)
}
}
}
pub async fn remove_plugin(state: web::Data<AppState>, path: web::Path<String>) -> impl Responder {
let id = path.into_inner();
let installer = ServerPluginInstaller::new(state.clone());
match installer.uninstall(&id).await {
Ok(()) => HttpResponse::Ok().json(serde_json::json!({ "id": id, "removed": true })),
Err(error) => plugin_error_response(&error),
}
}