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();
buildCards(DescModel model) {
final cards = <Widget>[
_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;

View File

@ -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();

View File

@ -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<String, dynamic> json) : ui = HashMap() {
json.forEach((key, value) {
(json['ui'] as Map<String, dynamic>).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<ConfigItem> local;
List<ConfigItem> shared;
List<ConfigItem> peer;
Config(this.local, this.peer);
Config(this.shared, this.peer);
Config.fromJson(Map<String, dynamic> json)
: local = (json['local'] as List<dynamic>)
: shared = (json['shared'] as List<dynamic>)
.map((e) => ConfigItem.fromJson(e))
.toList(),
peer = (json['peer'] as List<dynamic>)
@ -145,14 +146,8 @@ class Desc {
published = json['published'] ?? '',
released = json['released'] ?? '',
github = json['github'] ?? '',
location = Location(HashMap<String, UiType>.from(json['location'])),
config = Config(
(json['config'] as List<dynamic>)
.map((e) => ConfigItem.fromJson(e))
.toList(),
(json['config'] as List<dynamic>)
.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<String, dynamic> 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) {

View File

@ -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);
}

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);
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 {

View File

@ -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<Option<String>> {
pub fn plugin_get_shared_option(_id: String, _key: String) -> SyncReturn<Option<String>> {
#[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<Option<S
}
#[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(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(not(any(target_os = "android", target_os = "ios")))]
{
// to-do:
// reload plugin
allow_err!(crate::plugin::reload_plugin(&_id));
}
}
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]
pub fn plugin_id_enable(_id: String, v: bool) {
pub fn plugin_id_enable(_id: String, _v: bool) {
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
if v {
allow_err!(crate::plugin::ManagerConfig::set_plugin_enabled(&_id, true));
allow_err!(crate::plugin::ipc::set_manager_plugin_config(
&_id,
"enable",
_v.to_string()
));
if _v {
allow_err!(crate::plugin::reload_plugin(&_id));
} else {
allow_err!(crate::plugin::ManagerConfig::set_plugin_enabled(&_id, false));
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")))]
{
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<bool> {
}
}
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<bool> {
pub fn plugin_is_enabled() -> SyncReturn<Option<bool>> {
#[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<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(
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(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();
}
}
}

View File

@ -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,
_ => {}
}
}

View File

@ -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::<MsgToConfig>(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.

View File

@ -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<Mutex<HashMap<String, LocalConfig>>> = Default::default();
static ref CONFIG_LOCAL_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default();
static ref CONFIG_SHARED: Arc<Mutex<HashMap<String, SharedConfig>>> = 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_PEER_ITEMS: Arc<Mutex<HashMap<String, Vec<ConfigItem>>>> = Default::default();
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";
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct LocalConfig(HashMap<String, String>);
pub struct SharedConfig(HashMap<String, String>);
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PeerConfig(HashMap<String, String>);
type PeersConfig = HashMap<String, PeerConfig>;
@ -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<String, String>;
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::<LocalConfig>(Self::path(id));
if let Some(items) = CONFIG_LOCAL_ITEMS.lock().unwrap().get(id) {
let mut conf = hbb_common::config::load_path::<SharedConfig>(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<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());
}
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<ConfigItem>) {
CONFIG_LOCAL_ITEMS
pub(super) fn set_shared_items(id: &str, items: &Vec<ConfigItem>) {
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<String, String>,
#[serde(default)]
pub plugins: HashMap<String, PluginStatus>,
}
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<String> {
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<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();
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(())
}
}

View File

@ -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<ConfigItem>,
pub shared: 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;
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<String> {

View File

@ -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);
}