use crate::api::runtime_plugin_server::RuntimePlugin as RawRuntimePlugin;
use crate::api::{
InstallAppRequest, InstallAppResponse, IsSupportedAppRequest, IsSupportedAppResponse,
ParseAppRequest, ParseAppResponse, PauseAppRequest, PauseAppResponse, ResumeAppRequest,
ResumeAppResponse, UninstallAppRequest, UninstallAppResponse, UserState,
};
use flate2::read::GzDecoder;
use n5i_apps::internal::InternalAppRepresentation;
use std::path::Path;
use tar::Archive;
use tempfile::TempDir;
use tonic::{Request, Response, Status};
#[tonic::async_trait]
pub trait RuntimePlugin {
async fn is_supported_app(
&self,
app_id: &str,
app_path: &Path,
user_state: UserState,
) -> Result<bool, Status>;
async fn parse_app(
&self,
app_id: &str,
app_path: &Path,
user_state: UserState,
init_domain: Option<String>,
seed_base: String,
) -> Result<InternalAppRepresentation, Status>;
async fn install_app(
&self,
app_id: &str,
app_path: &Path,
user_state: UserState,
init_domain: Option<String>,
seed_base: String,
) -> Result<(), Status>;
async fn uninstall_app(
&self,
app_id: &str,
app_path: &Path,
user_state: UserState,
seed_base: String,
) -> Result<(), Status>;
async fn pause_app(
&self,
app_id: &str,
app_path: &Path,
user_state: UserState,
init_domain: Option<String>,
seed_base: String,
) -> Result<(), Status>;
async fn resume_app(
&self,
app_id: &str,
app_path: &Path,
user_state: UserState,
init_domain: Option<String>,
seed_base: String,
) -> Result<(), Status>;
}
macro_rules! app_unpack_prelude {
($req:expr) => {{
let Some(user_state) = $req.user_state else {
return Err(Status::invalid_argument("User state not provided"));
};
let tmp_dir = TempDir::new()?;
let tmp_dir_path = tmp_dir.path();
let archive = GzDecoder::new($req.app_directory.as_slice());
let mut archive = Archive::new(archive);
archive.unpack(tmp_dir_path)?;
(tmp_dir, user_state)
}};
}
pub struct RuntimePluginWrapper<T: RuntimePlugin + Send + Sync + 'static>(T);
impl<T: RuntimePlugin + Send + Sync + 'static> RuntimePluginWrapper<T> {
pub fn new(plugin: T) -> Self {
Self(plugin)
}
}
#[tonic::async_trait]
impl<T: RuntimePlugin + Send + Sync + 'static> RawRuntimePlugin for RuntimePluginWrapper<T> {
async fn is_supported_app(
&self,
request: Request<IsSupportedAppRequest>,
) -> Result<Response<IsSupportedAppResponse>, Status> {
let req = request.into_inner();
let (tmp_dir, user_state) = app_unpack_prelude!(req);
Ok(Response::new(IsSupportedAppResponse {
supported: T::is_supported_app(&self.0, &req.app_id, tmp_dir.path(), user_state)
.await?,
}))
}
async fn parse_app(
&self,
request: Request<ParseAppRequest>,
) -> Result<Response<ParseAppResponse>, Status> {
let req = request.into_inner();
let (tmp_dir, user_state) = app_unpack_prelude!(req);
let converted = T::parse_app(
&self.0,
&req.app_id,
tmp_dir.path(),
user_state,
req.initial_domain,
req.app_seed_base,
)
.await?;
let converted = serde_json::to_string(&converted).unwrap();
Ok(Response::new(ParseAppResponse {
parsed_app: converted,
}))
}
async fn install_app(
&self,
request: Request<InstallAppRequest>,
) -> Result<Response<InstallAppResponse>, Status> {
let req = request.into_inner();
let (tmp_dir, user_state) = app_unpack_prelude!(req);
T::install_app(
&self.0,
&req.app_id,
tmp_dir.path(),
user_state,
req.initial_domain,
req.app_seed_base,
)
.await?;
Ok(Response::new(InstallAppResponse {}))
}
async fn uninstall_app(
&self,
request: Request<UninstallAppRequest>,
) -> Result<Response<UninstallAppResponse>, Status> {
let req = request.into_inner();
let (tmp_dir, user_state) = app_unpack_prelude!(req);
T::uninstall_app(
&self.0,
&req.app_id,
tmp_dir.path(),
user_state,
req.app_seed_base,
)
.await?;
Ok(Response::new(UninstallAppResponse {}))
}
async fn pause_app(
&self,
request: Request<PauseAppRequest>,
) -> Result<Response<PauseAppResponse>, Status> {
let req = request.into_inner();
let (tmp_dir, user_state) = app_unpack_prelude!(req);
T::pause_app(
&self.0,
&req.app_id,
tmp_dir.path(),
user_state,
req.initial_domain,
req.app_seed_base,
)
.await?;
Ok(Response::new(PauseAppResponse {}))
}
async fn resume_app(
&self,
request: Request<ResumeAppRequest>,
) -> Result<Response<ResumeAppResponse>, Status> {
let req = request.into_inner();
let (tmp_dir, user_state) = app_unpack_prelude!(req);
T::resume_app(
&self.0,
&req.app_id,
tmp_dir.path(),
user_state,
req.initial_domain,
req.app_seed_base,
)
.await?;
Ok(Response::new(ResumeAppResponse {}))
}
}