From 260c671d6c831e8b9e59caa9acf12615c5e3b81e Mon Sep 17 00:00:00 2001 From: fufesou Date: Sun, 23 Apr 2023 15:40:55 +0800 Subject: [PATCH] plugin_framework, load plugin Signed-off-by: fufesou --- .../desktop/pages/desktop_setting_page.dart | 47 +++-- flutter/lib/models/server_model.dart | 1 + flutter/lib/plugin/desc.dart | 29 ++- flutter/lib/plugin/widget.dart | 2 +- src/core_main.rs | 11 ++ src/flutter_ffi.rs | 86 +++++--- src/ipc.rs | 9 + src/plugin/callback_msg.rs | 4 +- src/plugin/config.rs | 99 +++++++--- src/plugin/desc.rs | 4 +- src/plugin/ipc.rs | 184 ++++++++++++++++++ src/plugin/mod.rs | 5 +- src/plugin/plugins.rs | 33 +++- 13 files changed, 411 insertions(+), 103 deletions(-) create mode 100644 src/plugin/ipc.rs diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 821edd27d..9fb3d0a75 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1434,27 +1434,38 @@ class _PluginState extends State<_Plugin> { final scrollController = ScrollController(); buildCards(DescModel model) { final cards = [ - _Card(title: 'Plugin', children: [ - _checkbox('Enable', bind.pluginIsEnabled, (bool v) async { - if (!v) { - clearLocations(); - } - await bind.pluginEnable(v: v); - }), - ]), + _Card( + title: 'Plugin', + children: [ + _checkbox( + 'Enable', + () => bind.pluginIsEnabled() ?? false, + (bool v) async { + if (!v) { + clearLocations(); + } + await bind.pluginEnable(v: v); + }, + ), + ], + ), ]; model.all.forEach((key, value) { cards.add(_Card(title: key, children: [ - _Button('Reload', () { - bind.pluginReload(id: key); - }), - _checkbox('Enable', () => bind.pluginIdIsEnabled(id: key), - (bool v) async { - if (!v) { - clearPlugin(key); - } - await bind.pluginIdEnable(id: key, v: v); - }), + _Button( + 'Reload', + () => bind.pluginReload(id: key), + ), + _checkbox( + 'Enable', + () => bind.pluginIdIsEnabled(id: key), + (bool v) async { + if (!v) { + clearPlugin(key); + } + await bind.pluginIdEnable(id: key, v: v); + }, + ), ])); }); return cards; diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 216814cf6..31a95b8fb 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -341,6 +341,7 @@ class ServerModel with ChangeNotifier { _isStart = true; notifyListeners(); parent.target?.ffiModel.updateEventListener(""); + bind.pluginSyncUi(); await parent.target?.invokeMethod("init_service"); // ugly is here, because for desktop, below is useless await bind.mainStartService(); diff --git a/flutter/lib/plugin/desc.dart b/flutter/lib/plugin/desc.dart index 409fc5265..c27fc656e 100644 --- a/flutter/lib/plugin/desc.dart +++ b/flutter/lib/plugin/desc.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:collection'; import 'package:flutter/foundation.dart'; @@ -64,7 +65,7 @@ class Location { Location(this.ui); Location.fromJson(Map json) : ui = HashMap() { - json.forEach((key, value) { + (json['ui'] as Map).forEach((key, value) { var ui = UiType.create(value); if (ui != null) { this.ui[ui.key] = ui; @@ -93,12 +94,12 @@ class ConfigItem { } class Config { - List local; + List shared; List peer; - Config(this.local, this.peer); + Config(this.shared, this.peer); Config.fromJson(Map json) - : local = (json['local'] as List) + : shared = (json['shared'] as List) .map((e) => ConfigItem.fromJson(e)) .toList(), peer = (json['peer'] as List) @@ -145,14 +146,8 @@ class Desc { published = json['published'] ?? '', released = json['released'] ?? '', github = json['github'] ?? '', - location = Location(HashMap.from(json['location'])), - config = Config( - (json['config'] as List) - .map((e) => ConfigItem.fromJson(e)) - .toList(), - (json['config'] as List) - .map((e) => ConfigItem.fromJson(e)) - .toList()); + location = Location.fromJson(json['location']), + config = Config.fromJson(json['config']); } class DescModel with ChangeNotifier { @@ -161,9 +156,13 @@ class DescModel with ChangeNotifier { DescModel._(); void _updateDesc(Map desc) { - Desc d = Desc.fromJson(desc); - data[d.id] = d; - notifyListeners(); + try { + Desc d = Desc.fromJson(json.decode(desc['desc'])); + data[d.id] = d; + notifyListeners(); + } catch (e) { + debugPrint('DescModel json.decode fail(): $e'); + } } Desc? _getDesc(String id) { diff --git a/flutter/lib/plugin/widget.dart b/flutter/lib/plugin/widget.dart index feda1801f..15c632448 100644 --- a/flutter/lib/plugin/widget.dart +++ b/flutter/lib/plugin/widget.dart @@ -143,7 +143,7 @@ class PluginItem extends StatelessWidget { var v = model.value; if (v == null) { if (peerId.isEmpty) { - v = bind.pluginGetLocalOption(id: pluginId, key: key); + v = bind.pluginGetSharedOption(id: pluginId, key: key); } else { v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key); } diff --git a/src/core_main.rs b/src/core_main.rs index c5193b566..00e7b9829 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -104,6 +104,17 @@ pub fn core_main() -> Option> { crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system); return None; } + #[cfg(all(feature = "flutter", feature = "plugin_framework"))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if args.is_empty() || "--server" == (&args[0] as &str) { + #[cfg(debug_assertions)] + let load_plugins = true; + #[cfg(not(debug_assertions))] + let load_plugins = crate::platform::is_installed(); + if load_plugins { + hbb_common::allow_err!(crate::plugin::load_plugins()); + } + } if args.is_empty() { std::thread::spawn(move || crate::start_server(false)); } else { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index db8c65a89..e52d446cb 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1436,11 +1436,11 @@ pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _valu } #[inline] -pub fn plugin_get_local_option(_id: String, _key: String) -> SyncReturn> { +pub fn plugin_get_shared_option(_id: String, _key: String) -> SyncReturn> { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - SyncReturn(crate::plugin::LocalConfig::get(&_id, &_key)) + SyncReturn(crate::plugin::ipc::get_config(&_id, &_key).unwrap_or(None)) } #[cfg(any( not(feature = "plugin_framework"), @@ -1453,11 +1453,11 @@ pub fn plugin_get_local_option(_id: String, _key: String) -> SyncReturn SyncReturn { #[cfg(not(any(target_os = "android", target_os = "ios")))] { SyncReturn( - crate::plugin::ManagerConfig::get_plugin_status(&_id, |s| s.enabled).unwrap_or(false), + match crate::plugin::ipc::get_manager_plugin_config(&_id, "enabled") { + Ok(Some(enabled)) => bool::from_str(&enabled).unwrap_or(false), + _ => false, + }, ) } #[cfg(any( @@ -1504,12 +1517,15 @@ pub fn plugin_id_is_enabled(_id: String) -> SyncReturn { } } -pub fn plugin_enable(v: bool) { +pub fn plugin_enable(_v: bool) { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - allow_err!(crate::plugin::ManagerConfig::set_enabled(v)); - if v { + allow_err!(crate::plugin::ipc::set_manager_config( + "enable", + _v.to_string() + )); + if _v { allow_err!(crate::plugin::load_plugins()); } else { crate::plugin::unload_plugins(); @@ -1517,11 +1533,35 @@ pub fn plugin_enable(v: bool) { } } -pub fn plugin_is_enabled() -> SyncReturn { +pub fn plugin_is_enabled() -> SyncReturn> { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - SyncReturn(crate::plugin::ManagerConfig::is_enabled()) + let r = match crate::plugin::ipc::get_manager_config("enabled") { + Ok(Some(enabled)) => Some(bool::from_str(&enabled).unwrap_or(false)), + _ => None, + }; + SyncReturn(r) + } + #[cfg(any( + not(feature = "plugin_framework"), + target_os = "android", + target_os = "ios" + ))] + { + SyncReturn(Some(false)) + } +} + +pub fn plugin_feature_is_enabled() -> SyncReturn { + #[cfg(feature = "plugin_framework")] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + #[cfg(debug_assertions)] + let enabled = true; + #[cfg(not(debug_assertions))] + let enabled = is_installed(); + SyncReturn(enabled) } #[cfg(any( not(feature = "plugin_framework"), @@ -1533,19 +1573,13 @@ pub fn plugin_is_enabled() -> SyncReturn { } } -pub fn plugin_feature_is_enabled() -> SyncReturn { +pub fn plugin_sync_ui() { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - SyncReturn(true) - } - #[cfg(any( - not(feature = "plugin_framework"), - target_os = "android", - target_os = "ios" - ))] - { - SyncReturn(false) + if plugin_feature_is_enabled().0 { + crate::plugin::sync_ui(); + } } } diff --git a/src/ipc.rs b/src/ipc.rs index a3222dd02..a2194c337 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -8,6 +8,9 @@ use parity_tokio_ipc::{ }; use serde_derive::{Deserialize, Serialize}; +#[cfg(all(feature = "flutter", feature = "plugin_framework"))] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use crate::plugin::ipc::Plugin; #[cfg(not(any(target_os = "android", target_os = "ios")))] pub use clipboard::ClipboardFile; use hbb_common::{ @@ -215,6 +218,9 @@ pub enum Data { StartVoiceCall, VoiceCallResponse(bool), CloseVoiceCall(String), + #[cfg(all(feature = "flutter", feature = "plugin_framework"))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + Plugin(Plugin), } #[tokio::main(flavor = "current_thread")] @@ -453,6 +459,9 @@ async fn handle(data: Data, stream: &mut Connection) { .await ); } + #[cfg(all(feature = "flutter", feature = "plugin_framework"))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] + Data::Plugin(plugin) => crate::plugin::ipc::handle_plugin(plugin, stream).await, _ => {} } } diff --git a/src/plugin/callback_msg.rs b/src/plugin/callback_msg.rs index 95372ce96..1e8775883 100644 --- a/src/plugin/callback_msg.rs +++ b/src/plugin/callback_msg.rs @@ -110,8 +110,8 @@ pub fn callback_msg( // No need to merge the msgs. Handling the msg one by one is ok. if let Ok(msg) = serde_json::from_str::(s) { match &msg.r#type as _ { - config::CONFIG_TYPE_LOCAL => { - match config::LocalConfig::set(&msg.id, &msg.key, &msg.value) { + config::CONFIG_TYPE_SHARED => { + match config::SharedConfig::set(&msg.id, &msg.key, &msg.value) { Ok(_) => { if let Some(ui) = &msg.ui { // No need to set the peer id for location config. diff --git a/src/plugin/config.rs b/src/plugin/config.rs index dc754dabd..1f9267f0c 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -1,17 +1,16 @@ -use crate::plugins::Plugin; - use super::desc::ConfigItem; -use hbb_common::{bail, config::Config as HbbConfig, lazy_static, ResultType}; +use hbb_common::{allow_err, bail, config::Config as HbbConfig, lazy_static, log, ResultType}; use serde_derive::{Deserialize, Serialize}; use std::{ ops::{Deref, DerefMut}, + str::FromStr, sync::{Arc, Mutex}, {collections::HashMap, path::PathBuf}, }; lazy_static::lazy_static! { - static ref CONFIG_LOCAL: Arc>> = Default::default(); - static ref CONFIG_LOCAL_ITEMS: Arc>>> = Default::default(); + static ref CONFIG_SHARED: Arc>> = Default::default(); + static ref CONFIG_SHARED_ITEMS: Arc>>> = Default::default(); static ref CONFIG_PEERS: Arc>> = Default::default(); static ref CONFIG_PEER_ITEMS: Arc>>> = Default::default(); static ref CONFIG_MANAGER: Arc> = { @@ -20,11 +19,11 @@ lazy_static::lazy_static! { }; } -pub(super) const CONFIG_TYPE_LOCAL: &str = "local"; +pub(super) const CONFIG_TYPE_SHARED: &str = "shared"; pub(super) const CONFIG_TYPE_PEER: &str = "peer"; #[derive(Debug, Default, Serialize, Deserialize)] -pub struct LocalConfig(HashMap); +pub struct SharedConfig(HashMap); #[derive(Debug, Default, Serialize, Deserialize)] pub struct PeerConfig(HashMap); type PeersConfig = HashMap; @@ -34,7 +33,7 @@ fn path_plugins(id: &str) -> PathBuf { HbbConfig::path("plugins").join(id) } -impl Deref for LocalConfig { +impl Deref for SharedConfig { type Target = HashMap; fn deref(&self) -> &Self::Target { @@ -42,7 +41,7 @@ impl Deref for LocalConfig { } } -impl DerefMut for LocalConfig { +impl DerefMut for SharedConfig { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -62,32 +61,32 @@ impl DerefMut for PeerConfig { } } -impl LocalConfig { +impl SharedConfig { #[inline] fn path(id: &str) -> PathBuf { - path_plugins(id).join("local.toml") + path_plugins(id).join("shared.toml") } #[inline] pub fn load(id: &str) { - let mut conf = hbb_common::config::load_path::(Self::path(id)); - if let Some(items) = CONFIG_LOCAL_ITEMS.lock().unwrap().get(id) { + let mut conf = hbb_common::config::load_path::(Self::path(id)); + if let Some(items) = CONFIG_SHARED_ITEMS.lock().unwrap().get(id) { for item in items { if !conf.contains_key(&item.key) { conf.insert(item.key.to_owned(), item.default.to_owned()); } } } - CONFIG_LOCAL.lock().unwrap().insert(id.to_owned(), conf); + CONFIG_SHARED.lock().unwrap().insert(id.to_owned(), conf); } #[inline] pub fn get(id: &str, key: &str) -> Option { - if let Some(conf) = CONFIG_LOCAL.lock().unwrap().get(id) { + if let Some(conf) = CONFIG_SHARED.lock().unwrap().get(id) { return conf.get(key).map(|s| s.to_owned()); } Self::load(id); - CONFIG_LOCAL + CONFIG_SHARED .lock() .unwrap() .get(id)? @@ -97,7 +96,7 @@ impl LocalConfig { #[inline] pub fn set(id: &str, key: &str, value: &str) -> ResultType<()> { - match CONFIG_LOCAL.lock().unwrap().get_mut(id) { + match CONFIG_SHARED.lock().unwrap().get_mut(id) { Some(config) => { config.insert(key.to_owned(), value.to_owned()); hbb_common::config::store_path(Self::path(id), config) @@ -170,8 +169,8 @@ impl PeerConfig { } #[inline] -pub(super) fn set_local_items(id: &str, items: &Vec) { - CONFIG_LOCAL_ITEMS +pub(super) fn set_shared_items(id: &str, items: &Vec) { + CONFIG_SHARED_ITEMS .lock() .unwrap() .insert(id.to_owned(), items.clone()); @@ -196,14 +195,18 @@ const MANAGER_VERSION: &str = "0.1.0"; pub struct ManagerConfig { pub version: String, pub enabled: bool, + #[serde(default)] + pub options: HashMap, + #[serde(default)] pub plugins: HashMap, } impl Default for ManagerConfig { fn default() -> Self { Self { - version: "0.1.0".to_owned(), + version: MANAGER_VERSION.to_owned(), enabled: true, + options: HashMap::new(), plugins: HashMap::new(), } } @@ -217,31 +220,65 @@ impl ManagerConfig { } #[inline] - pub fn is_enabled() -> bool { - CONFIG_MANAGER.lock().unwrap().enabled + pub fn get_option(key: &str) -> Option { + if key == "enabled" { + Some(CONFIG_MANAGER.lock().unwrap().enabled.to_string()) + } else { + CONFIG_MANAGER + .lock() + .unwrap() + .options + .get(key) + .map(|s| s.to_owned()) + } } #[inline] - pub fn set_enabled(enabled: bool) -> ResultType<()> { + pub fn set_option(key: &str, value: &str) -> ResultType<()> { let mut lock = CONFIG_MANAGER.lock().unwrap(); - lock.enabled = enabled; + if key == "enabled" { + let enabled = bool::from_str(value).unwrap_or(false); + lock.enabled = enabled; + if enabled { + allow_err!(super::load_plugins()); + } else { + super::unload_plugins(); + } + } else { + lock.options.insert(key.to_owned(), value.to_owned()); + } hbb_common::config::store_path(Self::path(), &*lock) } #[inline] - pub fn get_plugin_status(id: &str, f: fn(&PluginStatus) -> T) -> Option { + pub fn get_plugin_option(id: &str, key: &str) -> Option { let lock = CONFIG_MANAGER.lock().unwrap(); - lock.plugins.get(id).map(f) + let status = lock.plugins.get(id)?; + match key { + "enabled" => Some(status.enabled.to_string()), + _ => None, + } } - pub fn set_plugin_enabled(id: &str, enabled: bool) -> ResultType<()> { + pub fn set_plugin_option(id: &str, key: &str, value: &str) -> ResultType<()> { let mut lock = CONFIG_MANAGER.lock().unwrap(); if let Some(status) = lock.plugins.get_mut(id) { - status.enabled = enabled; - hbb_common::config::store_path(Self::path(), &*lock) + match key { + "enabled" => { + let enabled = bool::from_str(value).unwrap_or(false); + status.enabled = enabled; + if enabled { + allow_err!(super::load_plugin(None, Some(id))); + } else { + super::unload_plugin(id); + } + } + _ => bail!("No such option {}", key), + } } else { bail!("No such plugin {}", id) } + hbb_common::config::store_path(Self::path(), &*lock) } #[inline] @@ -256,6 +293,8 @@ impl ManagerConfig { 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) + hbb_common::config::store_path(Self::path(), &*lock)?; + // to-do: remove plugin config dir + Ok(()) } } diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs index 94a137570..a0bb5949f 100644 --- a/src/plugin/desc.rs +++ b/src/plugin/desc.rs @@ -8,7 +8,7 @@ use std::ffi::{c_char, CStr}; pub struct UiButton { key: String, text: String, - icon: String, // icon can be int in flutter, but string in other ui framework. And it is flexible to use string. + icon: String, // icon can be int in flutter, but string in other ui framework. And it is flexible to use string. tooltip: String, action: String, // The action to be triggered when the button is clicked. } @@ -43,7 +43,7 @@ pub struct ConfigItem { #[derive(Debug, Serialize, Deserialize)] pub struct Config { - pub local: Vec, + pub shared: Vec, pub peer: Vec, } diff --git a/src/plugin/ipc.rs b/src/plugin/ipc.rs new file mode 100644 index 000000000..71807bb6b --- /dev/null +++ b/src/plugin/ipc.rs @@ -0,0 +1,184 @@ +// to-do: Interdependence(This mod and crate::ipc) is not good practice here. +use crate::ipc::{connect, Connection, Data}; +use hbb_common::{allow_err, bail, bytes, log, tokio, ResultType}; +use serde_derive::{Deserialize, Serialize}; +#[cfg(not(windows))] +use std::{fs::File, io::prelude::*}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "t", content = "c")] +pub enum Plugin { + Config(String, String, Option), + ManagerConfig(String, Option), + ManagerPluginConfig(String, String, Option), + Reload(String), + Uninstall, +} + +#[tokio::main(flavor = "current_thread")] +pub async fn get_config(id: &str, name: &str) -> ResultType> { + get_config_async(id, name, 1_000).await +} + +#[tokio::main(flavor = "current_thread")] +pub async fn set_config(id: &str, name: &str, value: String) -> ResultType<()> { + set_config_async(id, name, value).await +} + +#[tokio::main(flavor = "current_thread")] +pub async fn get_manager_config(name: &str) -> ResultType> { + get_manager_config_async(name, 1_000).await +} + +#[tokio::main(flavor = "current_thread")] +pub async fn set_manager_config(name: &str, value: String) -> ResultType<()> { + set_manager_config_async(name, value).await +} + +#[tokio::main(flavor = "current_thread")] +pub async fn get_manager_plugin_config(id: &str, name: &str) -> ResultType> { + get_manager_plugin_config_async(id, name, 1_000).await +} + +#[tokio::main(flavor = "current_thread")] +pub async fn set_manager_plugin_config(id: &str, name: &str, value: String) -> ResultType<()> { + set_manager_plugin_config_async(id, name, value).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( + id.to_owned(), + name.to_owned(), + None, + ))) + .await?; + if let Some(Data::Plugin(Plugin::Config(id2, name2, value))) = + c.next_timeout(ms_timeout).await? + { + if id == id2 && name == name2 { + return Ok(value); + } + } + return Ok(None); +} + +async fn set_config_async(id: &str, name: &str, value: String) -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send(&Data::Plugin(Plugin::Config( + id.to_owned(), + name.to_owned(), + Some(value), + ))) + .await?; + Ok(()) +} + +async fn get_manager_config_async(name: &str, ms_timeout: u64) -> ResultType> { + let mut c = connect(ms_timeout, "").await?; + c.send(&Data::Plugin(Plugin::ManagerConfig(name.to_owned(), None))) + .await?; + if let Some(Data::Plugin(Plugin::ManagerConfig(name2, value))) = + c.next_timeout(ms_timeout).await? + { + if name == name2 { + return Ok(value); + } + } + return Ok(None); +} + +async fn set_manager_config_async(name: &str, value: String) -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send(&Data::Plugin(Plugin::ManagerConfig( + name.to_owned(), + Some(value), + ))) + .await?; + Ok(()) +} + +async fn get_manager_plugin_config_async( + id: &str, + name: &str, + ms_timeout: u64, +) -> ResultType> { + let mut c = connect(ms_timeout, "").await?; + c.send(&Data::Plugin(Plugin::ManagerPluginConfig( + id.to_owned(), + name.to_owned(), + None, + ))) + .await?; + if let Some(Data::Plugin(Plugin::ManagerPluginConfig(id2, name2, value))) = + c.next_timeout(ms_timeout).await? + { + if id == id2 && name == name2 { + return Ok(value); + } + } + return Ok(None); +} + +async fn set_manager_plugin_config_async(id: &str, name: &str, value: String) -> ResultType<()> { + let mut c = connect(1000, "").await?; + c.send(&Data::Plugin(Plugin::ManagerPluginConfig( + id.to_owned(), + name.to_owned(), + Some(value), + ))) + .await?; + Ok(()) +} + +pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) { + match plugin { + Plugin::Config(id, name, value) => match value { + None => { + let value = crate::plugin::SharedConfig::get(&id, &name); + allow_err!( + stream + .send(&Data::Plugin(Plugin::Config(id, name, value))) + .await + ); + } + Some(value) => { + allow_err!(crate::plugin::SharedConfig::set(&id, &name, &value)); + } + }, + Plugin::ManagerConfig(name, value) => match value { + None => { + let value = crate::plugin::ManagerConfig::get_option(&name); + allow_err!( + stream + .send(&Data::Plugin(Plugin::ManagerConfig(name, value))) + .await + ); + } + Some(value) => { + allow_err!(crate::plugin::ManagerConfig::set_option(&name, &value)); + } + }, + Plugin::ManagerPluginConfig(id, name, value) => match value { + None => { + let value = crate::plugin::ManagerConfig::get_plugin_option(&id, &name); + allow_err!( + stream + .send(&Data::Plugin(Plugin::ManagerPluginConfig(id, name, value))) + .await + ); + } + Some(value) => { + allow_err!(crate::plugin::ManagerConfig::set_plugin_option( + &id, &name, &value + )); + } + }, + Plugin::Reload(id) => { + allow_err!(crate::plugin::reload_plugin(&id)); + } + Plugin::Uninstall => { + // to-do: uninstall plugin + } + } +} diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index fd854666e..a530cfff3 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -5,11 +5,12 @@ mod callback_msg; mod config; pub mod desc; mod errno; +pub mod ipc; mod plugins; pub use plugins::{ handle_client_event, handle_server_event, handle_ui_event, load_plugin, load_plugins, - reload_plugin, unload_plugin, unload_plugins, + reload_plugin, sync_ui, unload_plugin, unload_plugins, }; const MSG_TO_UI_TYPE_PLUGIN_DESC: &str = "plugin_desc"; @@ -17,7 +18,7 @@ const MSG_TO_UI_TYPE_PLUGIN_EVENT: &str = "plugin_event"; const MSG_TO_UI_TYPE_PLUGIN_RELOAD: &str = "plugin_reload"; const MSG_TO_UI_TYPE_PLUGIN_OPTION: &str = "plugin_option"; -pub use config::{LocalConfig, ManagerConfig, PeerConfig}; +pub use config::{ManagerConfig, PeerConfig, SharedConfig}; #[inline] fn cstr_to_string(cstr: *const c_char) -> ResultType { diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index 27a401974..ab15287cf 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -104,7 +104,7 @@ macro_rules! make_plugin { $(let $field = match unsafe { lib.symbol::<$tp>(stringify!($field)) } { Ok(m) => { - log::info!("method found {}", stringify!($field)); + log::debug!("{} method found {}", path, stringify!($field)); *m }, Err(e) => { @@ -132,6 +132,13 @@ make_plugin!( set_cb_get_id: PluginFuncGetIdCallback ); +#[cfg(target_os = "windows")] +const DYLIB_SUFFIX: &str = ".dll"; +#[cfg(target_os = "linux")] +const DYLIB_SUFFIX: &str = ".so"; +#[cfg(target_os = "macos")] +const DYLIB_SUFFIX: &str = ".dylib"; + pub fn load_plugins() -> ResultType<()> { let exe = std::env::current_exe()?.to_string_lossy().to_string(); match PathBuf::from(&exe).parent() { @@ -141,10 +148,12 @@ pub fn load_plugins() -> ResultType<()> { Ok(entry) => { let path = entry.path(); if path.is_file() { - let path = path.to_str().unwrap_or(""); - if path.ends_with(".so") { - if let Err(e) = load_plugin(Some(path), None) { - log::error!("{e}"); + let filename = entry.file_name(); + let filename = filename.to_str().unwrap_or(""); + if filename.starts_with("plugin_") && filename.ends_with(DYLIB_SUFFIX) { + if let Err(e) = load_plugin(Some(path.to_str().unwrap_or("")), None) + { + log::error!("Failed to load plugin {}, {}", filename, e); } } } @@ -214,18 +223,28 @@ fn load_plugin_path(path: &str) -> ResultType<()> { // to-do check the plugin id (make sure it does not use another plugin's id) (plugin.set_cb_msg)(callback_msg::callback_msg); (plugin.set_cb_get_id)(get_local_peer_id as _); + // Ui may be not ready now, so we need to update again once ui is ready. update_ui_plugin_desc(&desc); update_config(&desc); + // Ui may be not ready now, so we need to reload again once ui is ready. reload_ui(&desc); let plugin_info = PluginInfo { path: path.to_string(), desc, }; PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info); - PLUGINS.write().unwrap().insert(id, plugin); + PLUGINS.write().unwrap().insert(id.clone(), plugin); + log::info!("Plugin {} loaded", id); Ok(()) } +pub fn sync_ui() { + for plugin in PLUGIN_INFO.read().unwrap().values() { + update_ui_plugin_desc(&plugin.desc); + reload_ui(&plugin.desc); + } +} + pub fn load_plugin(path: Option<&str>, id: Option<&str>) -> ResultType<()> { match (path, id) { (Some(path), _) => load_plugin_path(path), @@ -346,7 +365,7 @@ fn make_plugin_response(id: &str, name: &str, msg: &str) -> Message { } fn update_config(desc: &Desc) { - super::config::set_local_items(desc.id(), &desc.config().local); + super::config::set_shared_items(desc.id(), &desc.config().shared); super::config::set_peer_items(desc.id(), &desc.config().peer); }