plugin_framework, test plugin manager, uninstall is not fully tested

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou
2023-05-10 23:57:46 +08:00
parent 4ee0fd9676
commit 095ac46255
12 changed files with 327 additions and 116 deletions

View File

@@ -1,6 +1,6 @@
use std::{
future::Future,
sync::{Arc, Mutex},
sync::{Arc, Mutex, RwLock},
};
#[derive(Debug, Eq, PartialEq)]
@@ -61,6 +61,7 @@ lazy_static::lazy_static! {
lazy_static::lazy_static! {
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
static ref SERVER_RUNNING: Arc<RwLock<bool>> = Default::default();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -93,9 +94,14 @@ pub fn global_init() -> bool {
pub fn global_clean() {}
#[inline]
pub fn set_server_running(b: bool) {
*SERVER_RUNNING.write().unwrap() = b;
}
#[inline]
pub fn is_server() -> bool {
*IS_SERVER
*IS_SERVER || *SERVER_RUNNING.read().unwrap()
}
#[inline]

View File

@@ -245,7 +245,7 @@ pub fn core_main() -> Option<Vec<String>> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if args[0] == "--plugin-install" {
if args.len() == 3 {
crate::plugin::privileged_install_plugin(&args[1], &args[2]);
crate::plugin::install_plugin_with_url(&args[1], &args[2]);
}
return None;
} else if args[0] == "--plugin-uninstall" {

View File

@@ -1580,12 +1580,11 @@ pub fn plugin_install(id: String, b: bool) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
if b {
allow_err!(crate::plugin::user_install_plugin(&id));
if let Err(e) = crate::plugin::install_plugin(&id) {
log::error!("Failed to install plugin '{}': {}", id, e);
}
} else {
// to-do: uninstall plugin
// 1. unload 2. remove configs 3. remove config files
// allow_err!(super::unload_plugin(&id));
crate::plugin::uninstall_plugin(&id);
crate::plugin::uninstall_plugin(&id, true);
}
}
}

View File

@@ -36,6 +36,16 @@ fn path_plugins(id: &str) -> PathBuf {
HbbConfig::path("plugins").join(id)
}
pub fn remove(id: &str) {
CONFIG_SHARED.lock().unwrap().remove(id);
CONFIG_PEERS.lock().unwrap().remove(id);
// allow_err is Ok here.
allow_err!(ManagerConfig::remove_plugin(id));
if let Err(e) = fs::remove_dir_all(path_plugins(id)) {
log::error!("Failed to remove plugin '{}' directory: {}", id, e);
}
}
impl Deref for SharedConfig {
type Target = HashMap<String, String>;
@@ -207,6 +217,7 @@ impl PeerConfig {
#[derive(Debug, Serialize, Deserialize)]
pub struct PluginStatus {
pub enabled: bool,
pub uninstalled: bool,
}
const MANAGER_VERSION: &str = "0.1.0";
@@ -269,7 +280,13 @@ impl ManagerConfig {
if let Some(status) = lock.plugins.get_mut(id) {
status.enabled = enabled;
} else {
lock.plugins.insert(id.to_owned(), PluginStatus { enabled });
lock.plugins.insert(
id.to_owned(),
PluginStatus {
enabled,
uninstalled: false,
},
);
}
hbb_common::config::store_path(Self::path(), &*lock)
}
@@ -292,29 +309,50 @@ impl ManagerConfig {
#[inline]
pub fn add_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins
.insert(id.to_owned(), PluginStatus { enabled: true });
lock.plugins.insert(
id.to_owned(),
PluginStatus {
enabled: true,
uninstalled: false,
},
);
hbb_common::config::store_path(Self::path(), &*lock)
}
#[inline]
pub fn remove_plugin(id: &str, uninstall: bool) -> ResultType<()> {
pub fn remove_plugin(id: &str) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins.remove(id);
hbb_common::config::store_path(Self::path(), &*lock)?;
if uninstall {
allow_err!(fs::remove_dir_all(path_plugins(id)));
}
Ok(())
hbb_common::config::store_path(Self::path(), &*lock)
}
pub fn remove_plugins(uninstall: bool) {
#[inline]
pub fn is_uninstalled(id: &str) -> bool {
CONFIG_MANAGER
.lock()
.unwrap()
.plugins
.get(id)
.map(|p| p.uninstalled)
.unwrap_or(false)
}
#[inline]
pub fn set_uninstall(id: &str, uninstall: bool) -> ResultType<()> {
let mut lock = CONFIG_MANAGER.lock().unwrap();
lock.plugins.clear();
allow_err!(hbb_common::config::store_path(Self::path(), &*lock));
if uninstall {
allow_err!(fs::remove_dir_all(HbbConfig::path("plugins")));
if let Some(status) = lock.plugins.get_mut(id) {
status.uninstalled = uninstall;
} else {
lock.plugins.insert(
id.to_owned(),
PluginStatus {
enabled: true,
uninstalled: uninstall,
},
);
}
hbb_common::config::store_path(Self::path(), &*lock)?;
Ok(())
}
}

View File

@@ -24,6 +24,7 @@ pub enum Plugin {
Load(String),
Reload(String),
InstallStatus((String, InstallStatus)),
Uninstall(String),
}
#[tokio::main(flavor = "current_thread")]
@@ -66,6 +67,11 @@ pub async fn reload_plugin(id: &str) -> ResultType<()> {
reload_plugin_async(id).await
}
#[tokio::main(flavor = "current_thread")]
pub async fn uninstall_plugin(id: &str) -> ResultType<()> {
uninstall_plugin_async(id).await
}
async fn get_config_async(id: &str, name: &str, ms_timeout: u64) -> ResultType<Option<String>> {
let mut c = connect(ms_timeout, "").await?;
c.send(&Data::Plugin(Plugin::Config(
@@ -164,6 +170,13 @@ async fn reload_plugin_async(id: &str) -> ResultType<()> {
Ok(())
}
async fn uninstall_plugin_async(id: &str) -> ResultType<()> {
let mut c = connect(1000, "").await?;
c.send(&Data::Plugin(Plugin::Uninstall(id.to_owned())))
.await?;
Ok(())
}
pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
match plugin {
Plugin::Config(id, name, value) => match value {
@@ -206,11 +219,15 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
}
},
Plugin::Load(id) => {
allow_err!(super::config::ManagerConfig::set_uninstall(&id, false));
allow_err!(super::load_plugin(&id));
}
Plugin::Reload(id) => {
allow_err!(super::reload_plugin(&id));
}
Plugin::Uninstall(id) => {
super::manager::uninstall_plugin(&id, false);
}
_ => {}
}
}

View File

@@ -8,12 +8,14 @@ use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::{
collections::HashMap,
fs,
sync::{Arc, Mutex},
};
const MSG_TO_UI_PLUGIN_MANAGER_LIST: &str = "plugin_list";
const MSG_TO_UI_PLUGIN_MANAGER_UPDATE: &str = "plugin_update";
const MSG_TO_UI_PLUGIN_MANAGER_INSTALL: &str = "plugin_install";
const MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL: &str = "plugin_uninstall";
const IPC_PLUGIN_POSTFIX: &str = "_plugin";
@@ -40,7 +42,6 @@ pub struct PluginInfo {
pub source: PluginSource,
pub meta: PluginMeta,
pub installed_version: String,
pub install_time: String,
pub invalid_reason: String,
}
@@ -78,7 +79,6 @@ fn get_source_plugins() -> HashMap<String, PluginInfo> {
source: source.clone(),
meta: meta.clone(),
installed_version: "".to_string(),
install_time: "".to_string(),
invalid_reason: "".to_string(),
},
);
@@ -110,9 +110,18 @@ fn send_plugin_list_event(plugins: &HashMap<String, PluginInfo>) {
pub fn load_plugin_list() {
let mut plugin_info_lock = PLUGIN_INFO.lock().unwrap();
let mut plugins = get_source_plugins();
for (id, info) in super::plugins::get_plugin_infos().read().unwrap().iter() {
// A big read lock is needed to prevent race conditions.
// Loading plugin list may be slow.
// Users may call uninstall plugin in the middle.
let plugin_infos = super::plugins::get_plugin_infos();
let plugin_infos_read_lock = plugin_infos.read().unwrap();
for (id, info) in plugin_infos_read_lock.iter() {
if info.uninstalled {
continue;
}
if let Some(p) = plugins.get_mut(id) {
p.install_time = info.install_time.clone();
p.installed_version = info.desc.meta().version.clone();
p.invalid_reason = "".to_string();
} else {
@@ -126,7 +135,6 @@ pub fn load_plugin_list() {
},
meta: info.desc.meta().clone(),
installed_version: info.desc.meta().version.clone(),
install_time: info.install_time.clone(),
invalid_reason: "".to_string(),
},
);
@@ -139,14 +147,34 @@ pub fn load_plugin_list() {
pub fn install_plugin(id: &str) -> ResultType<()> {
match PLUGIN_INFO.lock().unwrap().get(id) {
Some(plugin) => {
let _plugin_url = format!(
"{}/plugins/{}/{}_{}.zip",
plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version
);
// to-do: Support args with space in quotes. 'arg 1' and "arg 2"
#[cfg(windows)]
let _res =
crate::platform::elevate(&format!("--plugin-install {} {}", id, _plugin_url))?;
{
let mut same_plugin_exists = false;
if let Some(version) = super::plugins::get_version(id) {
if version == plugin.meta.version {
same_plugin_exists = true;
}
}
// to-do: Support args with space in quotes. 'arg 1' and "arg 2"
let plugin_url = format!(
"{}/plugins/{}/{}_{}.zip",
plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version
);
let args = if same_plugin_exists {
format!("--plugin-install {}", id)
} else {
format!("--plugin-install {} {}", id, plugin_url)
};
let allowed = crate::platform::elevate(&args)?;
if allowed && same_plugin_exists {
super::ipc::load_plugin(id)?;
super::plugins::load_plugin(id)?;
super::plugins::mark_uninstalled(id, false);
push_install_event(id, "finished");
}
}
Ok(())
}
None => {
@@ -155,26 +183,93 @@ pub fn install_plugin(id: &str) -> ResultType<()> {
}
}
pub(super) fn remove_plugins() {}
// 1. Add to uninstall list.
// 2. Try remove.
// 2. Remove on the next start.
pub fn uninstall_plugin(id: &str) {
// to-do: add to uninstall list.
super::plugins::unload_plugin(id);
fn get_uninstalled_plugins() -> ResultType<Vec<String>> {
let plugins_dir = super::get_plugins_dir()?;
let mut plugins = Vec::new();
if plugins_dir.exists() {
for entry in std::fs::read_dir(plugins_dir)? {
match entry {
Ok(entry) => {
let plugin_dir = entry.path();
if plugin_dir.is_dir() {
if let Some(id) = plugin_dir.file_name().and_then(|n| n.to_str()) {
if super::config::ManagerConfig::is_uninstalled(id) {
plugins.push(id.to_string());
}
}
}
}
Err(e) => {
log::error!("Failed to read plugins dir entry, {}", e);
}
}
}
}
Ok(plugins)
}
fn push_install_event(id: &str, msg: &str) {
pub(super) fn remove_plugins() -> ResultType<()> {
for id in get_uninstalled_plugins()?.iter() {
super::config::remove(id as _);
if let Ok(dir) = super::get_plugin_dir(id as _) {
allow_err!(fs::remove_dir_all(dir));
}
}
Ok(())
}
pub fn uninstall_plugin(id: &str, called_by_ui: bool) {
if called_by_ui {
match crate::platform::elevate(&format!("--plugin-uninstall {}", id)) {
Ok(true) => {
if let Err(e) = super::ipc::uninstall_plugin(id) {
log::error!("Failed to uninstall plugin '{}': {}", id, e);
push_uninstall_event(id, "failed");
return;
}
super::plugins::unload_plugin(id);
super::plugins::mark_uninstalled(id, true);
super::config::remove(id);
push_uninstall_event(id, "");
}
Ok(false) => {
return;
}
Err(e) => {
log::error!("Failed to uninstall plugin '{}': {}", id, e);
push_uninstall_event(id, "failed");
return;
}
}
}
if is_server() {
super::plugins::unload_plugin(&id);
// allow_err is Ok here.
allow_err!(super::config::ManagerConfig::set_uninstall(&id, true));
}
}
fn push_event(id: &str, r#type: &str, msg: &str) {
let mut m = HashMap::new();
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_MANAGER);
m.insert("id", id);
m.insert(MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg);
m.insert(r#type, msg);
if let Ok(event) = serde_json::to_string(&m) {
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
}
}
#[inline]
fn push_uninstall_event(id: &str, msg: &str) {
push_event(id, MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL, msg);
}
#[inline]
fn push_install_event(id: &str, msg: &str) {
push_event(id, MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg);
}
async fn handle_conn(mut stream: crate::ipc::Connection) {
loop {
tokio::select! {
@@ -197,7 +292,7 @@ async fn handle_conn(mut stream: crate::ipc::Connection) {
InstallStatus::Finished => {
allow_err!(super::plugins::load_plugin(&id));
allow_err!(super::ipc::load_plugin_async(id).await);
load_plugin_list();
std::thread::spawn(load_plugin_list);
push_install_event(&id, "finished");
}
InstallStatus::FailedCreating => {
@@ -329,7 +424,7 @@ pub(super) mod install {
Ok(())
}
pub fn install_plugin(id: &str, url: &str) {
pub fn install_plugin_with_url(id: &str, url: &str) {
let plugin_dir = match super::super::get_plugin_dir(id) {
Ok(d) => d,
Err(e) => {

View File

@@ -1,4 +1,4 @@
use hbb_common::{allow_err, libc, log, ResultType};
use hbb_common::{libc, log, ResultType};
use std::{
env,
ffi::{c_char, c_int, c_void, CStr},
@@ -19,12 +19,11 @@ mod plog;
mod plugins;
pub use manager::{
install::install_plugin as privileged_install_plugin, install_plugin as user_install_plugin,
load_plugin_list, uninstall_plugin,
install::install_plugin_with_url, install_plugin, load_plugin_list, uninstall_plugin,
};
pub use plugins::{
handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin,
reload_plugin, sync_ui, unload_plugin, unload_plugins,
reload_plugin, sync_ui, unload_plugin,
};
const MSG_TO_UI_TYPE_PLUGIN_EVENT: &str = "plugin_event";
@@ -92,9 +91,13 @@ pub fn init() {
if !is_server() {
std::thread::spawn(move || manager::start_ipc());
} else {
manager::remove_plugins();
if let Err(e) = manager::remove_plugins() {
log::error!("Failed to remove plugins: {}", e);
}
}
if let Err(e) = plugins::load_plugins() {
log::error!("Failed to load plugins: {}", e);
}
allow_err!(plugins::load_plugins());
}
#[inline]

View File

@@ -3,7 +3,7 @@ use super::{desc::Desc, errno::*, *};
use crate::common::is_server;
use crate::flutter;
use hbb_common::{
allow_err, bail,
bail,
dlopen::symbor::Library,
lazy_static, log,
message_proto::{Message, Misc, PluginFailure, PluginRequest},
@@ -15,7 +15,6 @@ use std::{
ffi::{c_char, c_void},
path::PathBuf,
sync::{Arc, RwLock},
time::SystemTime,
};
const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0";
@@ -29,7 +28,7 @@ lazy_static::lazy_static! {
pub(super) struct PluginInfo {
pub path: String,
pub install_time: String,
pub uninstalled: bool,
pub desc: Desc,
}
@@ -307,7 +306,7 @@ fn load_plugin_dir(dir: &PathBuf) {
Err(e) => {
log::error!(
"Failed to read '{}' dir entry, {}",
dir.file_stem().and_then(|f| f.to_str()).unwrap_or(""),
dir.file_name().and_then(|f| f.to_str()).unwrap_or(""),
e
);
}
@@ -316,20 +315,18 @@ fn load_plugin_dir(dir: &PathBuf) {
}
}
pub fn unload_plugins() {
log::info!("Plugins unloaded");
PLUGINS.write().unwrap().clear();
if change_manager() {
super::config::ManagerConfig::remove_plugins(false);
}
}
pub fn unload_plugin(id: &str) {
log::info!("Plugin {} unloaded", id);
PLUGINS.write().unwrap().remove(id);
if change_manager() {
allow_err!(super::config::ManagerConfig::remove_plugin(id, false));
}
}
pub(super) fn mark_uninstalled(id: &str, uninstalled: bool) {
log::info!("Plugin {} uninstall", id);
PLUGIN_INFO
.write()
.unwrap()
.get_mut(id)
.map(|info| info.uninstalled = uninstalled);
}
pub fn reload_plugin(id: &str) -> ResultType<()> {
@@ -364,7 +361,7 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
};
plugin.init(&init_data, path)?;
if change_manager() {
if is_server() {
super::config::ManagerConfig::add_plugin(&desc.meta().id)?;
}
@@ -372,18 +369,11 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
// Ui may be not ready now, so we need to update again once ui is ready.
reload_ui(&desc, None);
let install_time = PathBuf::from(path)
.metadata()
.and_then(|d| d.created())
.unwrap_or(SystemTime::UNIX_EPOCH);
let install_time = chrono::DateTime::<chrono::Local>::from(install_time)
.format("%Y-%m-%d %H:%M:%S")
.to_string();
// add plugins
let id = desc.meta().id.clone();
let plugin_info = PluginInfo {
path: path.to_string(),
install_time,
uninstalled: false,
desc,
};
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info);
@@ -582,15 +572,6 @@ fn make_plugin_failure(id: &str, name: &str, msg: &str) -> Message {
msg_out
}
#[inline]
fn change_manager() -> bool {
#[cfg(debug_assertions)]
let change_manager = true;
#[cfg(not(debug_assertions))]
let change_manager = is_server();
change_manager
}
fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
for (location, ui) in desc.location().ui.iter() {
if let Ok(ui) = serde_json::to_string(&ui) {
@@ -641,3 +622,11 @@ pub(super) fn get_desc_conf(id: &str) -> Option<super::desc::Config> {
.get(id)
.map(|info| info.desc.config().clone())
}
pub(super) fn get_version(id: &str) -> Option<String> {
PLUGIN_INFO
.read()
.unwrap()
.get(id)
.map(|info| info.desc.meta().version.clone())
}

View File

@@ -394,6 +394,7 @@ pub async fn start_server(is_server: bool) {
}
if is_server {
crate::common::set_server_running(true);
std::thread::spawn(move || {
if let Err(err) = crate::ipc::start("") {
log::error!("Failed to start ipc: {}", err);