use std::{ io::{Read, Write}, path::PathBuf, sync::Mutex, }; use mlua::{Lua, Table}; use serde_json::json; use crate::{ tools::{app_path, clone_repo}, CrateConfig, }; use self::{ interface::{ command::PluginCommander, dirs::PluginDirs, fs::PluginFileSystem, log::PluginLogger, network::PluginNetwork, os::PluginOS, path::PluginPath, PluginInfo, }, types::PluginConfig, }; pub mod interface; mod types; lazy_static::lazy_static! { static ref LUA: Mutex = Mutex::new(Lua::new()); } pub struct PluginManager; impl PluginManager { pub fn init(config: toml::Value) -> anyhow::Result<()> { let config = PluginConfig::from_toml_value(config); if !config.available { return Ok(()); } let lua = LUA.lock().unwrap(); let manager = lua.create_table().unwrap(); let name_index = lua.create_table().unwrap(); let plugin_dir = Self::init_plugin_dir(); let api = lua.create_table().unwrap(); api.set("log", PluginLogger).unwrap(); api.set("command", PluginCommander).unwrap(); api.set("network", PluginNetwork).unwrap(); api.set("dirs", PluginDirs).unwrap(); api.set("fs", PluginFileSystem).unwrap(); api.set("path", PluginPath).unwrap(); api.set("os", PluginOS).unwrap(); lua.globals().set("plugin_lib", api).unwrap(); lua.globals() .set("library_dir", plugin_dir.to_str().unwrap()) .unwrap(); lua.globals().set("config_info", config.clone())?; let mut index: u32 = 1; let dirs = std::fs::read_dir(&plugin_dir)?; let mut path_list = dirs .filter(|v| v.is_ok()) .map(|v| (v.unwrap().path(), false)) .collect::>(); for i in &config.loader { let path = PathBuf::from(i); if !path.is_dir() { // for loader dir, we need check first, because we need give a error log. log::error!("Plugin loader: {:?} path is not a exists directory.", path); } path_list.push((path, true)); } for entry in path_list { let plugin_dir = entry.0.to_path_buf(); if plugin_dir.is_dir() { let init_file = plugin_dir.join("init.lua"); if init_file.is_file() { let mut file = std::fs::File::open(init_file).unwrap(); let mut buffer = String::new(); file.read_to_string(&mut buffer).unwrap(); let current_plugin_dir = plugin_dir.to_str().unwrap().to_string(); let from_loader = entry.1; lua.globals() .set("_temp_plugin_dir", current_plugin_dir.clone())?; lua.globals().set("_temp_from_loader", from_loader)?; let info = lua.load(&buffer).eval::(); match info { Ok(mut info) => { if name_index.contains_key(info.name.clone()).unwrap_or(false) && !from_loader { // found same name plugin, intercept load log::warn!( "Plugin {} has been intercepted. [mulit-load]", info.name ); continue; } info.inner.plugin_dir = current_plugin_dir; info.inner.from_loader = from_loader; // call `on_init` if file "dcp.json" not exists let dcp_file = plugin_dir.join("dcp.json"); if !dcp_file.is_file() { if let Some(func) = info.clone().on_init { let result = func.call::<_, bool>(()); match result { Ok(true) => { // plugin init success, create `dcp.json` file. let mut file = std::fs::File::create(dcp_file).unwrap(); let value = json!({ "name": info.name, "author": info.author, "repository": info.repository, "version": info.version, "generate_time": chrono::Local::now().timestamp(), }); let buffer = serde_json::to_string_pretty(&value).unwrap(); let buffer = buffer.as_bytes(); file.write_all(buffer).unwrap(); // insert plugin-info into plugin-manager if let Ok(index) = name_index.get::<_, u32>(info.name.clone()) { let _ = manager.set(index, info.clone()); } else { let _ = manager.set(index, info.clone()); index += 1; let _ = name_index.set(info.name, index); } } Ok(false) => { log::warn!( "Plugin init function result is `false`, init failed." ); } Err(e) => { log::warn!("Plugin init failed: {e}"); } } } } else if let Ok(index) = name_index.get::<_, u32>(info.name.clone()) { let _ = manager.set(index, info.clone()); } else { let _ = manager.set(index, info.clone()); index += 1; let _ = name_index.set(info.name, index); } } Err(_e) => { let dir_name = plugin_dir.file_name().unwrap().to_str().unwrap(); log::error!("Plugin '{dir_name}' load failed."); } } } } } lua.globals().set("manager", manager).unwrap(); Ok(()) } pub fn on_build_start(crate_config: &CrateConfig, platform: &str) -> anyhow::Result<()> { let lua = LUA.lock().unwrap(); if !lua.globals().contains_key("manager")? { return Ok(()); } let manager = lua.globals().get::<_, Table>("manager")?; let args = lua.create_table()?; args.set("name", crate_config.dioxus_config.application.name.clone())?; args.set("platform", platform)?; args.set("out_dir", crate_config.out_dir.to_str().unwrap())?; args.set("asset_dir", crate_config.asset_dir.to_str().unwrap())?; for i in 1..(manager.len()? as i32 + 1) { let info = manager.get::(i)?; if let Some(func) = info.build.on_start { func.call::(args.clone())?; } } Ok(()) } pub fn on_build_finish(crate_config: &CrateConfig, platform: &str) -> anyhow::Result<()> { let lua = LUA.lock().unwrap(); if !lua.globals().contains_key("manager")? { return Ok(()); } let manager = lua.globals().get::<_, Table>("manager")?; let args = lua.create_table()?; args.set("name", crate_config.dioxus_config.application.name.clone())?; args.set("platform", platform)?; args.set("out_dir", crate_config.out_dir.to_str().unwrap())?; args.set("asset_dir", crate_config.asset_dir.to_str().unwrap())?; for i in 1..(manager.len()? as i32 + 1) { let info = manager.get::(i)?; if let Some(func) = info.build.on_finish { func.call::(args.clone())?; } } Ok(()) } pub fn on_serve_start(crate_config: &CrateConfig) -> anyhow::Result<()> { let lua = LUA.lock().unwrap(); if !lua.globals().contains_key("manager")? { return Ok(()); } let manager = lua.globals().get::<_, Table>("manager")?; let args = lua.create_table()?; args.set("name", crate_config.dioxus_config.application.name.clone())?; for i in 1..(manager.len()? as i32 + 1) { let info = manager.get::(i)?; if let Some(func) = info.serve.on_start { func.call::(args.clone())?; } } Ok(()) } pub fn on_serve_rebuild(timestamp: i64, files: Vec) -> anyhow::Result<()> { let lua = LUA.lock().unwrap(); let manager = lua.globals().get::<_, Table>("manager")?; let args = lua.create_table()?; args.set("timestamp", timestamp)?; let files: Vec = files .iter() .map(|v| v.to_str().unwrap().to_string()) .collect(); args.set("changed_files", files)?; for i in 1..(manager.len()? as i32 + 1) { let info = manager.get::(i)?; if let Some(func) = info.serve.on_rebuild { func.call::(args.clone())?; } } Ok(()) } pub fn on_serve_shutdown(crate_config: &CrateConfig) -> anyhow::Result<()> { let lua = LUA.lock().unwrap(); if !lua.globals().contains_key("manager")? { return Ok(()); } let manager = lua.globals().get::<_, Table>("manager")?; let args = lua.create_table()?; args.set("name", crate_config.dioxus_config.application.name.clone())?; for i in 1..(manager.len()? as i32 + 1) { let info = manager.get::(i)?; if let Some(func) = info.serve.on_shutdown { func.call::(args.clone())?; } } Ok(()) } pub fn init_plugin_dir() -> PathBuf { let app_path = app_path(); let plugin_path = app_path.join("plugins"); if !plugin_path.is_dir() { log::info!("📖 Start to init plugin library ..."); let url = "https://github.com/DioxusLabs/cli-plugin-library"; if let Err(err) = clone_repo(&plugin_path, url) { log::error!("Failed to init plugin dir, error caused by {}. ", err); } } plugin_path } pub fn plugin_list() -> Vec { let mut res = vec![]; if let Ok(lua) = LUA.lock() { let list = lua .load(mlua::chunk!( local list = {} for key, value in ipairs(manager) do table.insert(list, {name = value.name, loader = value.inner.from_loader}) end return list )) .eval::>() .unwrap_or_default(); for i in list { let name = i.get::<_, String>("name").unwrap(); let loader = i.get::<_, bool>("loader").unwrap(); let text = if loader { format!("{name} [:loader]") } else { name }; res.push(text); } } res } }