plugin_framework, load plugin
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
ae789ff5f1
commit
260c671d6c
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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
184
src/plugin/ipc.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user