diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 74617a140..3202d0d35 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1466,15 +1466,22 @@ class _PluginState extends State<_Plugin> { return ListView( physics: DraggableNeverScrollableScrollPhysics(), controller: scrollController, - children: model.plugins - .map((entry) => DesktopSettingsCard(plugin: entry)) - .toList(), + children: model.plugins.map((entry) => pluginCard(entry)).toList(), ).marginOnly(bottom: _kListViewBottomMargin); }), ), ); } + Widget pluginCard(PluginInfo plugin) { + return ChangeNotifierProvider.value( + value: plugin, + child: Consumer( + builder: (context, model, child) => DesktopSettingsCard(plugin: model), + ), + ); + } + Widget accountAction() { return Obx(() => _Button( gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout', diff --git a/flutter/lib/plugin/manager.dart b/flutter/lib/plugin/manager.dart index c7a9c7b3f..d605aab38 100644 --- a/flutter/lib/plugin/manager.dart +++ b/flutter/lib/plugin/manager.dart @@ -144,17 +144,20 @@ class PluginInfo with ChangeNotifier { SourceInfo sourceInfo; Meta meta; String installedVersion; // It is empty if not installed. - DateTime installTime; + String failedMsg; String invalidReason; // It is empty if valid. PluginInfo({ required this.sourceInfo, required this.meta, required this.installedVersion, - required this.installTime, required this.invalidReason, + this.failedMsg = '', }); + bool get installed => installedVersion.isNotEmpty; + bool get needUpdate => installed && installedVersion != meta.version; + void update(PluginInfo plugin) { assert(plugin.meta.id == meta.id, 'Plugin id not match'); if (plugin.meta.id != meta.id) { @@ -164,10 +167,28 @@ class PluginInfo with ChangeNotifier { sourceInfo = plugin.sourceInfo; meta = plugin.meta; installedVersion = plugin.installedVersion; - installTime = plugin.installTime; invalidReason = plugin.invalidReason; notifyListeners(); } + + void setInstall(String msg) { + if (msg == "finished") { + msg = ''; + } + failedMsg = msg; + if (msg.isEmpty) { + installedVersion = meta.version; + } + notifyListeners(); + } + + void setUninstall(String msg) { + failedMsg = msg; + if (msg.isEmpty) { + installedVersion = ''; + } + notifyListeners(); + } } class PluginManager with ChangeNotifier { @@ -194,11 +215,27 @@ class PluginManager with ChangeNotifier { _handlePluginList(evt['plugin_list']); } else if (evt['plugin_update'] != null) { _handlePluginUpdate(evt['plugin_update']); + } else if (evt['plugin_install'] != null && evt['id'] != null) { + _handlePluginInstall(evt['id'], evt['plugin_install']); + } else if (evt['plugin_uninstall'] != null && evt['id'] != null) { + _handlePluginUninstall(evt['id'], evt['plugin_uninstall']); } else { debugPrint('Failed to handle manager event: $evt'); } } + void _sortPlugins() { + plugins.sort((a, b) { + if (a.installed) { + return -1; + } else if (b.installed) { + return 1; + } else { + return 0; + } + }); + } + void _handlePluginUpdate(Map evt) { final plugin = _getPluginFromEvent(evt); if (plugin == null) { @@ -207,6 +244,8 @@ class PluginManager with ChangeNotifier { for (var i = 0; i < _plugins.length; i++) { if (_plugins[i].meta.id == plugin.meta.id) { _plugins[i].update(plugin); + _sortPlugins(); + notifyListeners(); return; } } @@ -225,9 +264,32 @@ class PluginManager with ChangeNotifier { } catch (e) { debugPrint('Failed to decode $e, plugin list \'$pluginList\''); } + _sortPlugins(); notifyListeners(); } + void _handlePluginInstall(String id, String msg) { + for (var i = 0; i < _plugins.length; i++) { + if (_plugins[i].meta.id == id) { + _plugins[i].setInstall(msg); + _sortPlugins(); + notifyListeners(); + return; + } + } + } + + void _handlePluginUninstall(String id, String msg) { + for (var i = 0; i < _plugins.length; i++) { + if (_plugins[i].meta.id == id) { + _plugins[i].setUninstall(msg); + _sortPlugins(); + notifyListeners(); + return; + } + } + } + PluginInfo? _getPluginFromEvent(Map evt) { final s = evt['source']; assert(s != null, 'Source is null'); @@ -273,19 +335,10 @@ class PluginManager with ChangeNotifier { publishInfo: PublishInfo(lastReleased: lastReleased, published: published), ); - - late DateTime installTime; - try { - installTime = - DateTime.parse(evt['install_time'] ?? '1970-01-01T00+00:00'); - } catch (e) { - installTime = DateTime.utc(1970); - } return PluginInfo( sourceInfo: source, meta: meta, installedVersion: evt['installed_version'], - installTime: installTime, invalidReason: evt['invalid_reason'] ?? '', ); } diff --git a/flutter/lib/plugin/widgets/desktop_settings.dart b/flutter/lib/plugin/widgets/desktop_settings.dart index f1368fbef..232df001f 100644 --- a/flutter/lib/plugin/widgets/desktop_settings.dart +++ b/flutter/lib/plugin/widgets/desktop_settings.dart @@ -28,10 +28,13 @@ class DesktopSettingsCard extends StatefulWidget { class _DesktopSettingsCardState extends State { PluginInfo get plugin => widget.plugin; - bool get installed => plugin.installedVersion.isNotEmpty; + bool get installed => plugin.installed; + + bool isEnabled = false; @override Widget build(BuildContext context) { + isEnabled = bind.pluginIsEnabled(id: plugin.meta.id); return Row( children: [ Flexible( @@ -70,7 +73,7 @@ class _DesktopSettingsCardState extends State { child: Row( children: [ Text( - translate(widget.plugin.meta.name), + widget.plugin.meta.name, textAlign: TextAlign.start, style: const TextStyle( fontSize: _kTitleFontSize, @@ -95,24 +98,25 @@ class _DesktopSettingsCardState extends State { return Container( child: ElevatedButton( onPressed: onPressed, - child: Text(label), + child: Text(translate(label)), ), ); } Widget headerInstallEnable() { - final installButton = headerButton(installed ? 'uninstall' : 'install', () { - bind.pluginInstall( - id: plugin.meta.id, - b: !installed, - ); - }); + final installButton = headerButton( + installed ? 'Uninstall' : 'Install', + () { + bind.pluginInstall( + id: plugin.meta.id, + b: !installed, + ); + }, + ); if (installed) { - final needUpdate = - plugin.installedVersion.compareTo(plugin.meta.version) < 0; - final updateButton = needUpdate - ? headerButton('update', () { + final updateButton = plugin.needUpdate + ? headerButton('Update', () { bind.pluginInstall( id: plugin.meta.id, b: !installed, @@ -120,10 +124,9 @@ class _DesktopSettingsCardState extends State { }) : Container(); - final isEnabled = bind.pluginIsEnabled(id: plugin.meta.id); final enableButton = !installed ? Container() - : headerButton(isEnabled ? 'disable' : 'enable', () { + : headerButton(isEnabled ? 'Disable' : 'Enable', () { if (isEnabled) { clearPlugin(plugin.meta.id); } @@ -175,7 +178,7 @@ class _DesktopSettingsCardState extends State { } Widget more() { - if (!installed) { + if (!(installed && isEnabled)) { return Container(); } diff --git a/src/common.rs b/src/common.rs index 7187b02cc..959ce2896 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,6 +1,6 @@ use std::{ future::Future, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, RwLock}, }; #[derive(Debug, Eq, PartialEq)] @@ -61,6 +61,7 @@ lazy_static::lazy_static! { lazy_static::lazy_static! { static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned()); + static ref SERVER_RUNNING: Arc> = Default::default(); } #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -93,9 +94,14 @@ pub fn global_init() -> bool { pub fn global_clean() {} +#[inline] +pub fn set_server_running(b: bool) { + *SERVER_RUNNING.write().unwrap() = b; +} + #[inline] pub fn is_server() -> bool { - *IS_SERVER + *IS_SERVER || *SERVER_RUNNING.read().unwrap() } #[inline] diff --git a/src/core_main.rs b/src/core_main.rs index 714e361c3..bda6580ee 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -245,7 +245,7 @@ pub fn core_main() -> Option> { #[cfg(not(any(target_os = "android", target_os = "ios")))] if args[0] == "--plugin-install" { if args.len() == 3 { - crate::plugin::privileged_install_plugin(&args[1], &args[2]); + crate::plugin::install_plugin_with_url(&args[1], &args[2]); } return None; } else if args[0] == "--plugin-uninstall" { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 59fe91ced..dad4d2782 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1580,12 +1580,11 @@ pub fn plugin_install(id: String, b: bool) { #[cfg(not(any(target_os = "android", target_os = "ios")))] { if b { - allow_err!(crate::plugin::user_install_plugin(&id)); + if let Err(e) = crate::plugin::install_plugin(&id) { + log::error!("Failed to install plugin '{}': {}", id, e); + } } else { - // to-do: uninstall plugin - // 1. unload 2. remove configs 3. remove config files - // allow_err!(super::unload_plugin(&id)); - crate::plugin::uninstall_plugin(&id); + crate::plugin::uninstall_plugin(&id, true); } } } diff --git a/src/plugin/config.rs b/src/plugin/config.rs index 4f44ced42..a23690a1b 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -36,6 +36,16 @@ fn path_plugins(id: &str) -> PathBuf { HbbConfig::path("plugins").join(id) } +pub fn remove(id: &str) { + CONFIG_SHARED.lock().unwrap().remove(id); + CONFIG_PEERS.lock().unwrap().remove(id); + // allow_err is Ok here. + allow_err!(ManagerConfig::remove_plugin(id)); + if let Err(e) = fs::remove_dir_all(path_plugins(id)) { + log::error!("Failed to remove plugin '{}' directory: {}", id, e); + } +} + impl Deref for SharedConfig { type Target = HashMap; @@ -207,6 +217,7 @@ impl PeerConfig { #[derive(Debug, Serialize, Deserialize)] pub struct PluginStatus { pub enabled: bool, + pub uninstalled: bool, } const MANAGER_VERSION: &str = "0.1.0"; @@ -269,7 +280,13 @@ impl ManagerConfig { if let Some(status) = lock.plugins.get_mut(id) { status.enabled = enabled; } else { - lock.plugins.insert(id.to_owned(), PluginStatus { enabled }); + lock.plugins.insert( + id.to_owned(), + PluginStatus { + enabled, + uninstalled: false, + }, + ); } hbb_common::config::store_path(Self::path(), &*lock) } @@ -292,29 +309,50 @@ impl ManagerConfig { #[inline] pub fn add_plugin(id: &str) -> ResultType<()> { let mut lock = CONFIG_MANAGER.lock().unwrap(); - lock.plugins - .insert(id.to_owned(), PluginStatus { enabled: true }); + lock.plugins.insert( + id.to_owned(), + PluginStatus { + enabled: true, + uninstalled: false, + }, + ); hbb_common::config::store_path(Self::path(), &*lock) } #[inline] - pub fn remove_plugin(id: &str, uninstall: bool) -> ResultType<()> { + pub fn remove_plugin(id: &str) -> ResultType<()> { let mut lock = CONFIG_MANAGER.lock().unwrap(); lock.plugins.remove(id); - hbb_common::config::store_path(Self::path(), &*lock)?; - if uninstall { - allow_err!(fs::remove_dir_all(path_plugins(id))); - } - Ok(()) + hbb_common::config::store_path(Self::path(), &*lock) } - pub fn remove_plugins(uninstall: bool) { + #[inline] + pub fn is_uninstalled(id: &str) -> bool { + CONFIG_MANAGER + .lock() + .unwrap() + .plugins + .get(id) + .map(|p| p.uninstalled) + .unwrap_or(false) + } + + #[inline] + pub fn set_uninstall(id: &str, uninstall: bool) -> ResultType<()> { let mut lock = CONFIG_MANAGER.lock().unwrap(); - lock.plugins.clear(); - allow_err!(hbb_common::config::store_path(Self::path(), &*lock)); - if uninstall { - allow_err!(fs::remove_dir_all(HbbConfig::path("plugins"))); + if let Some(status) = lock.plugins.get_mut(id) { + status.uninstalled = uninstall; + } else { + lock.plugins.insert( + id.to_owned(), + PluginStatus { + enabled: true, + uninstalled: uninstall, + }, + ); } + hbb_common::config::store_path(Self::path(), &*lock)?; + Ok(()) } } diff --git a/src/plugin/ipc.rs b/src/plugin/ipc.rs index 73742cbda..1ca5521b0 100644 --- a/src/plugin/ipc.rs +++ b/src/plugin/ipc.rs @@ -24,6 +24,7 @@ pub enum Plugin { Load(String), Reload(String), InstallStatus((String, InstallStatus)), + Uninstall(String), } #[tokio::main(flavor = "current_thread")] @@ -66,6 +67,11 @@ pub async fn reload_plugin(id: &str) -> ResultType<()> { reload_plugin_async(id).await } +#[tokio::main(flavor = "current_thread")] +pub async fn uninstall_plugin(id: &str) -> ResultType<()> { + uninstall_plugin_async(id).await +} + async fn get_config_async(id: &str, name: &str, ms_timeout: u64) -> ResultType> { let mut c = connect(ms_timeout, "").await?; c.send(&Data::Plugin(Plugin::Config( @@ -164,6 +170,13 @@ async fn reload_plugin_async(id: &str) -> ResultType<()> { Ok(()) } +async fn uninstall_plugin_async(id: &str) -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send(&Data::Plugin(Plugin::Uninstall(id.to_owned()))) + .await?; + Ok(()) +} + pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) { match plugin { Plugin::Config(id, name, value) => match value { @@ -206,11 +219,15 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) { } }, Plugin::Load(id) => { + allow_err!(super::config::ManagerConfig::set_uninstall(&id, false)); allow_err!(super::load_plugin(&id)); } Plugin::Reload(id) => { allow_err!(super::reload_plugin(&id)); } + Plugin::Uninstall(id) => { + super::manager::uninstall_plugin(&id, false); + } _ => {} } } diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index cfbc6216c..9480445bf 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -8,12 +8,14 @@ use serde_derive::{Deserialize, Serialize}; use serde_json; use std::{ collections::HashMap, + fs, sync::{Arc, Mutex}, }; const MSG_TO_UI_PLUGIN_MANAGER_LIST: &str = "plugin_list"; const MSG_TO_UI_PLUGIN_MANAGER_UPDATE: &str = "plugin_update"; const MSG_TO_UI_PLUGIN_MANAGER_INSTALL: &str = "plugin_install"; +const MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL: &str = "plugin_uninstall"; const IPC_PLUGIN_POSTFIX: &str = "_plugin"; @@ -40,7 +42,6 @@ pub struct PluginInfo { pub source: PluginSource, pub meta: PluginMeta, pub installed_version: String, - pub install_time: String, pub invalid_reason: String, } @@ -78,7 +79,6 @@ fn get_source_plugins() -> HashMap { source: source.clone(), meta: meta.clone(), installed_version: "".to_string(), - install_time: "".to_string(), invalid_reason: "".to_string(), }, ); @@ -110,9 +110,18 @@ fn send_plugin_list_event(plugins: &HashMap) { pub fn load_plugin_list() { let mut plugin_info_lock = PLUGIN_INFO.lock().unwrap(); let mut plugins = get_source_plugins(); - for (id, info) in super::plugins::get_plugin_infos().read().unwrap().iter() { + + // A big read lock is needed to prevent race conditions. + // Loading plugin list may be slow. + // Users may call uninstall plugin in the middle. + let plugin_infos = super::plugins::get_plugin_infos(); + let plugin_infos_read_lock = plugin_infos.read().unwrap(); + for (id, info) in plugin_infos_read_lock.iter() { + if info.uninstalled { + continue; + } + if let Some(p) = plugins.get_mut(id) { - p.install_time = info.install_time.clone(); p.installed_version = info.desc.meta().version.clone(); p.invalid_reason = "".to_string(); } else { @@ -126,7 +135,6 @@ pub fn load_plugin_list() { }, meta: info.desc.meta().clone(), installed_version: info.desc.meta().version.clone(), - install_time: info.install_time.clone(), invalid_reason: "".to_string(), }, ); @@ -139,14 +147,34 @@ pub fn load_plugin_list() { pub fn install_plugin(id: &str) -> ResultType<()> { match PLUGIN_INFO.lock().unwrap().get(id) { Some(plugin) => { - let _plugin_url = format!( - "{}/plugins/{}/{}_{}.zip", - plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version - ); - // to-do: Support args with space in quotes. 'arg 1' and "arg 2" #[cfg(windows)] - let _res = - crate::platform::elevate(&format!("--plugin-install {} {}", id, _plugin_url))?; + { + let mut same_plugin_exists = false; + if let Some(version) = super::plugins::get_version(id) { + if version == plugin.meta.version { + same_plugin_exists = true; + } + } + + // to-do: Support args with space in quotes. 'arg 1' and "arg 2" + let plugin_url = format!( + "{}/plugins/{}/{}_{}.zip", + plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version + ); + let args = if same_plugin_exists { + format!("--plugin-install {}", id) + } else { + format!("--plugin-install {} {}", id, plugin_url) + }; + let allowed = crate::platform::elevate(&args)?; + + if allowed && same_plugin_exists { + super::ipc::load_plugin(id)?; + super::plugins::load_plugin(id)?; + super::plugins::mark_uninstalled(id, false); + push_install_event(id, "finished"); + } + } Ok(()) } None => { @@ -155,26 +183,93 @@ pub fn install_plugin(id: &str) -> ResultType<()> { } } -pub(super) fn remove_plugins() {} - -// 1. Add to uninstall list. -// 2. Try remove. -// 2. Remove on the next start. -pub fn uninstall_plugin(id: &str) { - // to-do: add to uninstall list. - super::plugins::unload_plugin(id); +fn get_uninstalled_plugins() -> ResultType> { + let plugins_dir = super::get_plugins_dir()?; + let mut plugins = Vec::new(); + if plugins_dir.exists() { + for entry in std::fs::read_dir(plugins_dir)? { + match entry { + Ok(entry) => { + let plugin_dir = entry.path(); + if plugin_dir.is_dir() { + if let Some(id) = plugin_dir.file_name().and_then(|n| n.to_str()) { + if super::config::ManagerConfig::is_uninstalled(id) { + plugins.push(id.to_string()); + } + } + } + } + Err(e) => { + log::error!("Failed to read plugins dir entry, {}", e); + } + } + } + } + Ok(plugins) } -fn push_install_event(id: &str, msg: &str) { +pub(super) fn remove_plugins() -> ResultType<()> { + for id in get_uninstalled_plugins()?.iter() { + super::config::remove(id as _); + if let Ok(dir) = super::get_plugin_dir(id as _) { + allow_err!(fs::remove_dir_all(dir)); + } + } + Ok(()) +} + +pub fn uninstall_plugin(id: &str, called_by_ui: bool) { + if called_by_ui { + match crate::platform::elevate(&format!("--plugin-uninstall {}", id)) { + Ok(true) => { + if let Err(e) = super::ipc::uninstall_plugin(id) { + log::error!("Failed to uninstall plugin '{}': {}", id, e); + push_uninstall_event(id, "failed"); + return; + } + super::plugins::unload_plugin(id); + super::plugins::mark_uninstalled(id, true); + super::config::remove(id); + push_uninstall_event(id, ""); + } + Ok(false) => { + return; + } + Err(e) => { + log::error!("Failed to uninstall plugin '{}': {}", id, e); + push_uninstall_event(id, "failed"); + return; + } + } + } + + if is_server() { + super::plugins::unload_plugin(&id); + // allow_err is Ok here. + allow_err!(super::config::ManagerConfig::set_uninstall(&id, true)); + } +} + +fn push_event(id: &str, r#type: &str, msg: &str) { let mut m = HashMap::new(); m.insert("name", MSG_TO_UI_TYPE_PLUGIN_MANAGER); m.insert("id", id); - m.insert(MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg); + m.insert(r#type, msg); if let Ok(event) = serde_json::to_string(&m) { let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone()); } } +#[inline] +fn push_uninstall_event(id: &str, msg: &str) { + push_event(id, MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL, msg); +} + +#[inline] +fn push_install_event(id: &str, msg: &str) { + push_event(id, MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg); +} + async fn handle_conn(mut stream: crate::ipc::Connection) { loop { tokio::select! { @@ -197,7 +292,7 @@ async fn handle_conn(mut stream: crate::ipc::Connection) { InstallStatus::Finished => { allow_err!(super::plugins::load_plugin(&id)); allow_err!(super::ipc::load_plugin_async(id).await); - load_plugin_list(); + std::thread::spawn(load_plugin_list); push_install_event(&id, "finished"); } InstallStatus::FailedCreating => { @@ -329,7 +424,7 @@ pub(super) mod install { Ok(()) } - pub fn install_plugin(id: &str, url: &str) { + pub fn install_plugin_with_url(id: &str, url: &str) { let plugin_dir = match super::super::get_plugin_dir(id) { Ok(d) => d, Err(e) => { diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 9542171f3..7ec690c59 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,4 +1,4 @@ -use hbb_common::{allow_err, libc, log, ResultType}; +use hbb_common::{libc, log, ResultType}; use std::{ env, ffi::{c_char, c_int, c_void, CStr}, @@ -19,12 +19,11 @@ mod plog; mod plugins; pub use manager::{ - install::install_plugin as privileged_install_plugin, install_plugin as user_install_plugin, - load_plugin_list, uninstall_plugin, + install::install_plugin_with_url, install_plugin, load_plugin_list, uninstall_plugin, }; pub use plugins::{ handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin, - reload_plugin, sync_ui, unload_plugin, unload_plugins, + reload_plugin, sync_ui, unload_plugin, }; const MSG_TO_UI_TYPE_PLUGIN_EVENT: &str = "plugin_event"; @@ -92,9 +91,13 @@ pub fn init() { if !is_server() { std::thread::spawn(move || manager::start_ipc()); } else { - manager::remove_plugins(); + if let Err(e) = manager::remove_plugins() { + log::error!("Failed to remove plugins: {}", e); + } + } + if let Err(e) = plugins::load_plugins() { + log::error!("Failed to load plugins: {}", e); } - allow_err!(plugins::load_plugins()); } #[inline] diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index ee4f1691f..2207e4757 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -3,7 +3,7 @@ use super::{desc::Desc, errno::*, *}; use crate::common::is_server; use crate::flutter; use hbb_common::{ - allow_err, bail, + bail, dlopen::symbor::Library, lazy_static, log, message_proto::{Message, Misc, PluginFailure, PluginRequest}, @@ -15,7 +15,6 @@ use std::{ ffi::{c_char, c_void}, path::PathBuf, sync::{Arc, RwLock}, - time::SystemTime, }; const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0"; @@ -29,7 +28,7 @@ lazy_static::lazy_static! { pub(super) struct PluginInfo { pub path: String, - pub install_time: String, + pub uninstalled: bool, pub desc: Desc, } @@ -307,7 +306,7 @@ fn load_plugin_dir(dir: &PathBuf) { Err(e) => { log::error!( "Failed to read '{}' dir entry, {}", - dir.file_stem().and_then(|f| f.to_str()).unwrap_or(""), + dir.file_name().and_then(|f| f.to_str()).unwrap_or(""), e ); } @@ -316,20 +315,18 @@ fn load_plugin_dir(dir: &PathBuf) { } } -pub fn unload_plugins() { - log::info!("Plugins unloaded"); - PLUGINS.write().unwrap().clear(); - if change_manager() { - super::config::ManagerConfig::remove_plugins(false); - } -} - pub fn unload_plugin(id: &str) { log::info!("Plugin {} unloaded", id); PLUGINS.write().unwrap().remove(id); - if change_manager() { - allow_err!(super::config::ManagerConfig::remove_plugin(id, false)); - } +} + +pub(super) fn mark_uninstalled(id: &str, uninstalled: bool) { + log::info!("Plugin {} uninstall", id); + PLUGIN_INFO + .write() + .unwrap() + .get_mut(id) + .map(|info| info.uninstalled = uninstalled); } pub fn reload_plugin(id: &str) -> ResultType<()> { @@ -364,7 +361,7 @@ fn load_plugin_path(path: &str) -> ResultType<()> { }; plugin.init(&init_data, path)?; - if change_manager() { + if is_server() { super::config::ManagerConfig::add_plugin(&desc.meta().id)?; } @@ -372,18 +369,11 @@ fn load_plugin_path(path: &str) -> ResultType<()> { // Ui may be not ready now, so we need to update again once ui is ready. reload_ui(&desc, None); - let install_time = PathBuf::from(path) - .metadata() - .and_then(|d| d.created()) - .unwrap_or(SystemTime::UNIX_EPOCH); - let install_time = chrono::DateTime::::from(install_time) - .format("%Y-%m-%d %H:%M:%S") - .to_string(); // add plugins let id = desc.meta().id.clone(); let plugin_info = PluginInfo { path: path.to_string(), - install_time, + uninstalled: false, desc, }; PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info); @@ -582,15 +572,6 @@ fn make_plugin_failure(id: &str, name: &str, msg: &str) -> Message { msg_out } -#[inline] -fn change_manager() -> bool { - #[cfg(debug_assertions)] - let change_manager = true; - #[cfg(not(debug_assertions))] - let change_manager = is_server(); - change_manager -} - fn reload_ui(desc: &Desc, sync_to: Option<&str>) { for (location, ui) in desc.location().ui.iter() { if let Ok(ui) = serde_json::to_string(&ui) { @@ -641,3 +622,11 @@ pub(super) fn get_desc_conf(id: &str) -> Option { .get(id) .map(|info| info.desc.config().clone()) } + +pub(super) fn get_version(id: &str) -> Option { + PLUGIN_INFO + .read() + .unwrap() + .get(id) + .map(|info| info.desc.meta().version.clone()) +} diff --git a/src/server.rs b/src/server.rs index b6cbdac44..89e57c79e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -394,6 +394,7 @@ pub async fn start_server(is_server: bool) { } if is_server { + crate::common::set_server_running(true); std::thread::spawn(move || { if let Err(err) = crate::ipc::start("") { log::error!("Failed to start ipc: {}", err);