plugin_framework, load plugin

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2023-04-23 15:40:55 +08:00
parent ae789ff5f1
commit 260c671d6c
13 changed files with 411 additions and 103 deletions

View File

@ -1434,27 +1434,38 @@ class _PluginState extends State<_Plugin> {
final scrollController = ScrollController(); final scrollController = ScrollController();
buildCards(DescModel model) { buildCards(DescModel model) {
final cards = <Widget>[ final cards = <Widget>[
_Card(title: 'Plugin', children: [ _Card(
_checkbox('Enable', bind.pluginIsEnabled, (bool v) async { title: 'Plugin',
if (!v) { children: [
clearLocations(); _checkbox(
} 'Enable',
await bind.pluginEnable(v: v); () => bind.pluginIsEnabled() ?? false,
}), (bool v) async {
]), if (!v) {
clearLocations();
}
await bind.pluginEnable(v: v);
},
),
],
),
]; ];
model.all.forEach((key, value) { model.all.forEach((key, value) {
cards.add(_Card(title: key, children: [ cards.add(_Card(title: key, children: [
_Button('Reload', () { _Button(
bind.pluginReload(id: key); 'Reload',
}), () => bind.pluginReload(id: key),
_checkbox('Enable', () => bind.pluginIdIsEnabled(id: key), ),
(bool v) async { _checkbox(
if (!v) { 'Enable',
clearPlugin(key); () => bind.pluginIdIsEnabled(id: key),
} (bool v) async {
await bind.pluginIdEnable(id: key, v: v); if (!v) {
}), clearPlugin(key);
}
await bind.pluginIdEnable(id: key, v: v);
},
),
])); ]));
}); });
return cards; return cards;

View File

@ -341,6 +341,7 @@ class ServerModel with ChangeNotifier {
_isStart = true; _isStart = true;
notifyListeners(); notifyListeners();
parent.target?.ffiModel.updateEventListener(""); parent.target?.ffiModel.updateEventListener("");
bind.pluginSyncUi();
await parent.target?.invokeMethod("init_service"); await parent.target?.invokeMethod("init_service");
// ugly is here, because for desktop, below is useless // ugly is here, because for desktop, below is useless
await bind.mainStartService(); await bind.mainStartService();

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:collection'; import 'dart:collection';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -64,7 +65,7 @@ class Location {
Location(this.ui); Location(this.ui);
Location.fromJson(Map<String, dynamic> json) : ui = HashMap() { Location.fromJson(Map<String, dynamic> json) : ui = HashMap() {
json.forEach((key, value) { (json['ui'] as Map<String, dynamic>).forEach((key, value) {
var ui = UiType.create(value); var ui = UiType.create(value);
if (ui != null) { if (ui != null) {
this.ui[ui.key] = ui; this.ui[ui.key] = ui;
@ -93,12 +94,12 @@ class ConfigItem {
} }
class Config { class Config {
List<ConfigItem> local; List<ConfigItem> shared;
List<ConfigItem> peer; List<ConfigItem> peer;
Config(this.local, this.peer); Config(this.shared, this.peer);
Config.fromJson(Map<String, dynamic> json) Config.fromJson(Map<String, dynamic> json)
: local = (json['local'] as List<dynamic>) : shared = (json['shared'] as List<dynamic>)
.map((e) => ConfigItem.fromJson(e)) .map((e) => ConfigItem.fromJson(e))
.toList(), .toList(),
peer = (json['peer'] as List<dynamic>) peer = (json['peer'] as List<dynamic>)
@ -145,14 +146,8 @@ class Desc {
published = json['published'] ?? '', published = json['published'] ?? '',
released = json['released'] ?? '', released = json['released'] ?? '',
github = json['github'] ?? '', github = json['github'] ?? '',
location = Location(HashMap<String, UiType>.from(json['location'])), location = Location.fromJson(json['location']),
config = Config( config = Config.fromJson(json['config']);
(json['config'] as List<dynamic>)
.map((e) => ConfigItem.fromJson(e))
.toList(),
(json['config'] as List<dynamic>)
.map((e) => ConfigItem.fromJson(e))
.toList());
} }
class DescModel with ChangeNotifier { class DescModel with ChangeNotifier {
@ -161,9 +156,13 @@ class DescModel with ChangeNotifier {
DescModel._(); DescModel._();
void _updateDesc(Map<String, dynamic> desc) { void _updateDesc(Map<String, dynamic> desc) {
Desc d = Desc.fromJson(desc); try {
data[d.id] = d; Desc d = Desc.fromJson(json.decode(desc['desc']));
notifyListeners(); data[d.id] = d;
notifyListeners();
} catch (e) {
debugPrint('DescModel json.decode fail(): $e');
}
} }
Desc? _getDesc(String id) { Desc? _getDesc(String id) {

View File

@ -143,7 +143,7 @@ class PluginItem extends StatelessWidget {
var v = model.value; var v = model.value;
if (v == null) { if (v == null) {
if (peerId.isEmpty) { if (peerId.isEmpty) {
v = bind.pluginGetLocalOption(id: pluginId, key: key); v = bind.pluginGetSharedOption(id: pluginId, key: key);
} else { } else {
v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key); v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
} }

View File

@ -104,6 +104,17 @@ pub fn core_main() -> Option<Vec<String>> {
crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system); crate::platform::elevate_or_run_as_system(click_setup, _is_elevate, _is_run_as_system);
return None; 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() { if args.is_empty() {
std::thread::spawn(move || crate::start_server(false)); std::thread::spawn(move || crate::start_server(false));
} else { } else {

View File

@ -1436,11 +1436,11 @@ pub fn plugin_set_session_option(_id: String, _peer: String, _key: String, _valu
} }
#[inline] #[inline]
pub fn plugin_get_local_option(_id: String, _key: String) -> SyncReturn<Option<String>> { pub fn plugin_get_shared_option(_id: String, _key: String) -> SyncReturn<Option<String>> {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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( #[cfg(any(
not(feature = "plugin_framework"), not(feature = "plugin_framework"),
@ -1453,11 +1453,11 @@ pub fn plugin_get_local_option(_id: String, _key: String) -> SyncReturn<Option<S
} }
#[inline] #[inline]
pub fn plugin_set_local_option(_id: String, _key: String, _value: String) { pub fn plugin_set_shared_option(_id: String, _key: String, _value: String) {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
let _res = crate::plugin::LocalConfig::set(&_id, &_key, &_value); allow_err!(crate::plugin::ipc::set_config(&_id, &_key, _value));
} }
} }
@ -1466,21 +1466,31 @@ pub fn plugin_reload(_id: String) {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
// to-do: allow_err!(crate::plugin::reload_plugin(&_id));
// reload plugin }
}
pub fn plugin_id_uninstall(_id: String) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
// to-do: uninstall plugin
} }
} }
#[inline] #[inline]
pub fn plugin_id_enable(_id: String, v: bool) { pub fn plugin_id_enable(_id: String, _v: bool) {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
if v { allow_err!(crate::plugin::ipc::set_manager_plugin_config(
allow_err!(crate::plugin::ManagerConfig::set_plugin_enabled(&_id, true)); &_id,
"enable",
_v.to_string()
));
if _v {
allow_err!(crate::plugin::reload_plugin(&_id)); allow_err!(crate::plugin::reload_plugin(&_id));
} else { } else {
allow_err!(crate::plugin::ManagerConfig::set_plugin_enabled(&_id, false));
crate::plugin::unload_plugin(&_id); crate::plugin::unload_plugin(&_id);
} }
} }
@ -1491,7 +1501,10 @@ pub fn plugin_id_is_enabled(_id: String) -> SyncReturn<bool> {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
SyncReturn( 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( #[cfg(any(
@ -1504,12 +1517,15 @@ pub fn plugin_id_is_enabled(_id: String) -> SyncReturn<bool> {
} }
} }
pub fn plugin_enable(v: bool) { pub fn plugin_enable(_v: bool) {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
allow_err!(crate::plugin::ManagerConfig::set_enabled(v)); allow_err!(crate::plugin::ipc::set_manager_config(
if v { "enable",
_v.to_string()
));
if _v {
allow_err!(crate::plugin::load_plugins()); allow_err!(crate::plugin::load_plugins());
} else { } else {
crate::plugin::unload_plugins(); crate::plugin::unload_plugins();
@ -1517,11 +1533,35 @@ pub fn plugin_enable(v: bool) {
} }
} }
pub fn plugin_is_enabled() -> SyncReturn<bool> { pub fn plugin_is_enabled() -> SyncReturn<Option<bool>> {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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<bool> {
#[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( #[cfg(any(
not(feature = "plugin_framework"), not(feature = "plugin_framework"),
@ -1533,19 +1573,13 @@ pub fn plugin_is_enabled() -> SyncReturn<bool> {
} }
} }
pub fn plugin_feature_is_enabled() -> SyncReturn<bool> { pub fn plugin_sync_ui() {
#[cfg(feature = "plugin_framework")] #[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
SyncReturn(true) if plugin_feature_is_enabled().0 {
} crate::plugin::sync_ui();
#[cfg(any( }
not(feature = "plugin_framework"),
target_os = "android",
target_os = "ios"
))]
{
SyncReturn(false)
} }
} }

View File

@ -8,6 +8,9 @@ use parity_tokio_ipc::{
}; };
use serde_derive::{Deserialize, Serialize}; 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")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub use clipboard::ClipboardFile; pub use clipboard::ClipboardFile;
use hbb_common::{ use hbb_common::{
@ -215,6 +218,9 @@ pub enum Data {
StartVoiceCall, StartVoiceCall,
VoiceCallResponse(bool), VoiceCallResponse(bool),
CloseVoiceCall(String), 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")] #[tokio::main(flavor = "current_thread")]
@ -453,6 +459,9 @@ async fn handle(data: Data, stream: &mut Connection) {
.await .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,
_ => {} _ => {}
} }
} }

View File

@ -110,8 +110,8 @@ pub fn callback_msg(
// No need to merge the msgs. Handling the msg one by one is ok. // No need to merge the msgs. Handling the msg one by one is ok.
if let Ok(msg) = serde_json::from_str::<MsgToConfig>(s) { if let Ok(msg) = serde_json::from_str::<MsgToConfig>(s) {
match &msg.r#type as _ { match &msg.r#type as _ {
config::CONFIG_TYPE_LOCAL => { config::CONFIG_TYPE_SHARED => {
match config::LocalConfig::set(&msg.id, &msg.key, &msg.value) { match config::SharedConfig::set(&msg.id, &msg.key, &msg.value) {
Ok(_) => { Ok(_) => {
if let Some(ui) = &msg.ui { if let Some(ui) = &msg.ui {
// No need to set the peer id for location config. // No need to set the peer id for location config.

View File

@ -1,17 +1,16 @@
use crate::plugins::Plugin;
use super::desc::ConfigItem; 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 serde_derive::{Deserialize, Serialize};
use std::{ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
str::FromStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
{collections::HashMap, path::PathBuf}, {collections::HashMap, path::PathBuf},
}; };
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref CONFIG_LOCAL: Arc<Mutex<HashMap<String, LocalConfig>>> = Default::default(); static ref CONFIG_SHARED: Arc<Mutex<HashMap<String, SharedConfig>>> = Default::default();
static ref CONFIG_LOCAL_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default(); static ref CONFIG_SHARED_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default();
static ref CONFIG_PEERS: Arc<Mutex<HashMap<String, PeersConfig>>> = Default::default(); static ref CONFIG_PEERS: Arc<Mutex<HashMap<String, PeersConfig>>> = Default::default();
static ref CONFIG_PEER_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default(); static ref CONFIG_PEER_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default();
static ref CONFIG_MANAGER: Arc<Mutex<ManagerConfig>> = { static ref CONFIG_MANAGER: Arc<Mutex<ManagerConfig>> = {
@ -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"; pub(super) const CONFIG_TYPE_PEER: &str = "peer";
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct LocalConfig(HashMap<String, String>); pub struct SharedConfig(HashMap<String, String>);
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct PeerConfig(HashMap<String, String>); pub struct PeerConfig(HashMap<String, String>);
type PeersConfig = HashMap<String, PeerConfig>; type PeersConfig = HashMap<String, PeerConfig>;
@ -34,7 +33,7 @@ fn path_plugins(id: &str) -> PathBuf {
HbbConfig::path("plugins").join(id) HbbConfig::path("plugins").join(id)
} }
impl Deref for LocalConfig { impl Deref for SharedConfig {
type Target = HashMap<String, String>; type Target = HashMap<String, String>;
fn deref(&self) -> &Self::Target { 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 { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
} }
@ -62,32 +61,32 @@ impl DerefMut for PeerConfig {
} }
} }
impl LocalConfig { impl SharedConfig {
#[inline] #[inline]
fn path(id: &str) -> PathBuf { fn path(id: &str) -> PathBuf {
path_plugins(id).join("local.toml") path_plugins(id).join("shared.toml")
} }
#[inline] #[inline]
pub fn load(id: &str) { pub fn load(id: &str) {
let mut conf = hbb_common::config::load_path::<LocalConfig>(Self::path(id)); let mut conf = hbb_common::config::load_path::<SharedConfig>(Self::path(id));
if let Some(items) = CONFIG_LOCAL_ITEMS.lock().unwrap().get(id) { if let Some(items) = CONFIG_SHARED_ITEMS.lock().unwrap().get(id) {
for item in items { for item in items {
if !conf.contains_key(&item.key) { if !conf.contains_key(&item.key) {
conf.insert(item.key.to_owned(), item.default.to_owned()); 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] #[inline]
pub fn get(id: &str, key: &str) -> Option<String> { pub fn get(id: &str, key: &str) -> Option<String> {
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()); return conf.get(key).map(|s| s.to_owned());
} }
Self::load(id); Self::load(id);
CONFIG_LOCAL CONFIG_SHARED
.lock() .lock()
.unwrap() .unwrap()
.get(id)? .get(id)?
@ -97,7 +96,7 @@ impl LocalConfig {
#[inline] #[inline]
pub fn set(id: &str, key: &str, value: &str) -> ResultType<()> { 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) => { Some(config) => {
config.insert(key.to_owned(), value.to_owned()); config.insert(key.to_owned(), value.to_owned());
hbb_common::config::store_path(Self::path(id), config) hbb_common::config::store_path(Self::path(id), config)
@ -170,8 +169,8 @@ impl PeerConfig {
} }
#[inline] #[inline]
pub(super) fn set_local_items(id: &str, items: &Vec<ConfigItem>) { pub(super) fn set_shared_items(id: &str, items: &Vec<ConfigItem>) {
CONFIG_LOCAL_ITEMS CONFIG_SHARED_ITEMS
.lock() .lock()
.unwrap() .unwrap()
.insert(id.to_owned(), items.clone()); .insert(id.to_owned(), items.clone());
@ -196,14 +195,18 @@ const MANAGER_VERSION: &str = "0.1.0";
pub struct ManagerConfig { pub struct ManagerConfig {
pub version: String, pub version: String,
pub enabled: bool, pub enabled: bool,
#[serde(default)]
pub options: HashMap<String, String>,
#[serde(default)]
pub plugins: HashMap<String, PluginStatus>, pub plugins: HashMap<String, PluginStatus>,
} }
impl Default for ManagerConfig { impl Default for ManagerConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
version: "0.1.0".to_owned(), version: MANAGER_VERSION.to_owned(),
enabled: true, enabled: true,
options: HashMap::new(),
plugins: HashMap::new(), plugins: HashMap::new(),
} }
} }
@ -217,31 +220,65 @@ impl ManagerConfig {
} }
#[inline] #[inline]
pub fn is_enabled() -> bool { pub fn get_option(key: &str) -> Option<String> {
CONFIG_MANAGER.lock().unwrap().enabled 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] #[inline]
pub fn set_enabled(enabled: bool) -> ResultType<()> { pub fn set_option(key: &str, value: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap(); 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) hbb_common::config::store_path(Self::path(), &*lock)
} }
#[inline] #[inline]
pub fn get_plugin_status<T>(id: &str, f: fn(&PluginStatus) -> T) -> Option<T> { pub fn get_plugin_option(id: &str, key: &str) -> Option<String> {
let lock = CONFIG_MANAGER.lock().unwrap(); 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(); let mut lock = CONFIG_MANAGER.lock().unwrap();
if let Some(status) = lock.plugins.get_mut(id) { if let Some(status) = lock.plugins.get_mut(id) {
status.enabled = enabled; match key {
hbb_common::config::store_path(Self::path(), &*lock) "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 { } else {
bail!("No such plugin {}", id) bail!("No such plugin {}", id)
} }
hbb_common::config::store_path(Self::path(), &*lock)
} }
#[inline] #[inline]
@ -256,6 +293,8 @@ impl ManagerConfig {
pub fn remove_plugin(id: &str) -> ResultType<()> { pub fn remove_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap(); let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins.remove(id); 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(())
} }
} }

View File

@ -8,7 +8,7 @@ use std::ffi::{c_char, CStr};
pub struct UiButton { pub struct UiButton {
key: String, key: String,
text: 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, tooltip: String,
action: String, // The action to be triggered when the button is clicked. action: String, // The action to be triggered when the button is clicked.
} }
@ -43,7 +43,7 @@ pub struct ConfigItem {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Config { pub struct Config {
pub local: Vec<ConfigItem>, pub shared: Vec<ConfigItem>,
pub peer: Vec<ConfigItem>, pub peer: Vec<ConfigItem>,
} }

184
src/plugin/ipc.rs Normal file
View File

@ -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<String>),
ManagerConfig(String, Option<String>),
ManagerPluginConfig(String, String, Option<String>),
Reload(String),
Uninstall,
}
#[tokio::main(flavor = "current_thread")]
pub async fn get_config(id: &str, name: &str) -> ResultType<Option<String>> {
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<Option<String>> {
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<Option<String>> {
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<Option<String>> {
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<Option<String>> {
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<Option<String>> {
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
}
}
}

View File

@ -5,11 +5,12 @@ mod callback_msg;
mod config; mod config;
pub mod desc; pub mod desc;
mod errno; mod errno;
pub mod ipc;
mod plugins; mod plugins;
pub use plugins::{ pub use plugins::{
handle_client_event, handle_server_event, handle_ui_event, load_plugin, load_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"; 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_RELOAD: &str = "plugin_reload";
const MSG_TO_UI_TYPE_PLUGIN_OPTION: &str = "plugin_option"; const MSG_TO_UI_TYPE_PLUGIN_OPTION: &str = "plugin_option";
pub use config::{LocalConfig, ManagerConfig, PeerConfig}; pub use config::{ManagerConfig, PeerConfig, SharedConfig};
#[inline] #[inline]
fn cstr_to_string(cstr: *const c_char) -> ResultType<String> { fn cstr_to_string(cstr: *const c_char) -> ResultType<String> {

View File

@ -104,7 +104,7 @@ macro_rules! make_plugin {
$(let $field = match unsafe { lib.symbol::<$tp>(stringify!($field)) } { $(let $field = match unsafe { lib.symbol::<$tp>(stringify!($field)) } {
Ok(m) => { Ok(m) => {
log::info!("method found {}", stringify!($field)); log::debug!("{} method found {}", path, stringify!($field));
*m *m
}, },
Err(e) => { Err(e) => {
@ -132,6 +132,13 @@ make_plugin!(
set_cb_get_id: PluginFuncGetIdCallback 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<()> { pub fn load_plugins() -> ResultType<()> {
let exe = std::env::current_exe()?.to_string_lossy().to_string(); let exe = std::env::current_exe()?.to_string_lossy().to_string();
match PathBuf::from(&exe).parent() { match PathBuf::from(&exe).parent() {
@ -141,10 +148,12 @@ pub fn load_plugins() -> ResultType<()> {
Ok(entry) => { Ok(entry) => {
let path = entry.path(); let path = entry.path();
if path.is_file() { if path.is_file() {
let path = path.to_str().unwrap_or(""); let filename = entry.file_name();
if path.ends_with(".so") { let filename = filename.to_str().unwrap_or("");
if let Err(e) = load_plugin(Some(path), None) { if filename.starts_with("plugin_") && filename.ends_with(DYLIB_SUFFIX) {
log::error!("{e}"); 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) // 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_msg)(callback_msg::callback_msg);
(plugin.set_cb_get_id)(get_local_peer_id as _); (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_ui_plugin_desc(&desc);
update_config(&desc); update_config(&desc);
// Ui may be not ready now, so we need to reload again once ui is ready.
reload_ui(&desc); reload_ui(&desc);
let plugin_info = PluginInfo { let plugin_info = PluginInfo {
path: path.to_string(), path: path.to_string(),
desc, desc,
}; };
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info); 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(()) 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<()> { pub fn load_plugin(path: Option<&str>, id: Option<&str>) -> ResultType<()> {
match (path, id) { match (path, id) {
(Some(path), _) => load_plugin_path(path), (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) { 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); super::config::set_peer_items(desc.id(), &desc.config().peer);
} }