mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
plugin_framework, manager, install plugin
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
@@ -19,7 +19,7 @@ use hbb_common::compress::decompress;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
compress::compress as compress_func,
|
||||
config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT},
|
||||
config::{self, Config, RENDEZVOUS_TIMEOUT},
|
||||
get_version_number, log,
|
||||
message_proto::*,
|
||||
protobuf::Enum,
|
||||
@@ -68,6 +68,19 @@ lazy_static::lazy_static! {
|
||||
static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
|
||||
}
|
||||
|
||||
pub struct SimpleCallOnReturn {
|
||||
pub b: bool,
|
||||
pub f: Box<dyn Fn() + 'static>,
|
||||
}
|
||||
|
||||
impl Drop for SimpleCallOnReturn {
|
||||
fn drop(&mut self) {
|
||||
if self.b {
|
||||
(self.f)();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_init() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@@ -98,7 +111,7 @@ pub fn valid_for_numlock(evt: &KeyEvent) -> bool {
|
||||
|
||||
pub fn create_clipboard_msg(content: String) -> Message {
|
||||
let bytes = content.into_bytes();
|
||||
let compressed = compress_func(&bytes, COMPRESS_LEVEL);
|
||||
let compressed = compress_func(&bytes);
|
||||
let compress = compressed.len() < bytes.len();
|
||||
let content = if compress { compressed } else { bytes };
|
||||
let mut msg = Message::new();
|
||||
|
||||
@@ -112,7 +112,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
let load_plugins = crate::platform::is_installed();
|
||||
if load_plugins {
|
||||
hbb_common::allow_err!(crate::plugin::load_plugins());
|
||||
crate::plugin::init();
|
||||
}
|
||||
}
|
||||
if args.is_empty() {
|
||||
@@ -240,6 +240,18 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::flutter::connection_manager::start_cm_no_ui();
|
||||
return None;
|
||||
} else {
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
#[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]);
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--plugin-uninstall" {
|
||||
// Do nothing
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
//_async_logger_holder.map(|x| x.flush());
|
||||
|
||||
@@ -1497,17 +1497,8 @@ pub fn plugin_reload(_id: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_id_uninstall(_id: String) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::plugin::unload_plugin(&_id);
|
||||
allow_err!(crate::plugin::ipc::uninstall_plugin(&_id));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn plugin_id_enable(_id: String, _v: bool) {
|
||||
pub fn plugin_enable(_id: String, _v: bool) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
@@ -1517,14 +1508,14 @@ pub fn plugin_id_enable(_id: String, _v: bool) {
|
||||
_v.to_string()
|
||||
));
|
||||
if _v {
|
||||
allow_err!(crate::plugin::load_plugin(None, Some(&_id)));
|
||||
allow_err!(crate::plugin::load_plugin(&_id));
|
||||
} else {
|
||||
crate::plugin::unload_plugin(&_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_id_is_enabled(_id: String) -> SyncReturn<bool> {
|
||||
pub fn plugin_is_enabled(_id: String) -> SyncReturn<bool> {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
@@ -1575,6 +1566,29 @@ pub fn plugin_sync_ui(_sync_to: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_list_reload() {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::plugin::load_plugin_list(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_install(id: String, b: bool) {
|
||||
#[cfg(feature = "plugin_framework")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
if b {
|
||||
allow_err!(crate::plugin::user_install_plugin(&id));
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config, log};
|
||||
|
||||
@@ -280,7 +280,7 @@ impl ManagerConfig {
|
||||
let enabled = bool::from_str(value).unwrap_or(false);
|
||||
allow_err!(Self::set_plugin_option_enabled(id, enabled));
|
||||
if enabled {
|
||||
allow_err!(super::load_plugin(None, Some(id)));
|
||||
allow_err!(super::load_plugin(id));
|
||||
} else {
|
||||
super::unload_plugin(id);
|
||||
}
|
||||
|
||||
@@ -46,18 +46,29 @@ pub struct Config {
|
||||
pub peer: Vec<ConfigItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PublishInfo {
|
||||
pub published: String,
|
||||
pub last_released: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Meta {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub description: String,
|
||||
pub author: String,
|
||||
pub home: String,
|
||||
pub license: String,
|
||||
pub source: String,
|
||||
pub publish_info: PublishInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Desc {
|
||||
id: String,
|
||||
name: String,
|
||||
version: String,
|
||||
description: String,
|
||||
author: String,
|
||||
home: String,
|
||||
license: String,
|
||||
published: String,
|
||||
released: String,
|
||||
github: String,
|
||||
meta: Meta,
|
||||
need_reboot: bool,
|
||||
location: Location,
|
||||
config: Config,
|
||||
listen_events: Vec<String>,
|
||||
@@ -69,44 +80,8 @@ impl Desc {
|
||||
Ok(serde_json::from_str(s.to_str()?)?)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
|
||||
pub fn author(&self) -> &str {
|
||||
&self.author
|
||||
}
|
||||
|
||||
pub fn home(&self) -> &str {
|
||||
&self.home
|
||||
}
|
||||
|
||||
pub fn license(&self) -> &str {
|
||||
&self.license
|
||||
}
|
||||
|
||||
pub fn published(&self) -> &str {
|
||||
&self.published
|
||||
}
|
||||
|
||||
pub fn released(&self) -> &str {
|
||||
&self.released
|
||||
}
|
||||
|
||||
pub fn github(&self) -> &str {
|
||||
&self.github
|
||||
pub fn meta(&self) -> &Meta {
|
||||
&self.meta
|
||||
}
|
||||
|
||||
pub fn location(&self) -> &Location {
|
||||
|
||||
@@ -5,14 +5,25 @@ use serde_derive::{Deserialize, Serialize};
|
||||
#[cfg(not(windows))]
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum InstallStatus {
|
||||
Downloading(u8),
|
||||
Installing,
|
||||
Finished,
|
||||
FailedCreating,
|
||||
FailedDownloading,
|
||||
FailedInstalling,
|
||||
}
|
||||
|
||||
#[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>),
|
||||
Load(String),
|
||||
Reload(String),
|
||||
Uninstall(String),
|
||||
InstallStatus((String, InstallStatus)),
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@@ -46,13 +57,13 @@ pub async fn set_manager_plugin_config(id: &str, name: &str, value: String) -> R
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
reload_plugin_async(id).await
|
||||
pub async fn load_plugin(id: &str) -> ResultType<()> {
|
||||
load_plugin_async(id).await
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn uninstall_plugin(id: &str) -> ResultType<()> {
|
||||
uninstall_plugin_async(id).await
|
||||
pub async fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
reload_plugin_async(id).await
|
||||
}
|
||||
|
||||
async fn get_config_async(id: &str, name: &str, ms_timeout: u64) -> ResultType<Option<String>> {
|
||||
@@ -141,16 +152,15 @@ async fn set_manager_plugin_config_async(id: &str, name: &str, value: String) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reload_plugin_async(id: &str) -> ResultType<()> {
|
||||
async fn load_plugin_async(id: &str) -> ResultType<()> {
|
||||
let mut c = connect(1000, "").await?;
|
||||
c.send(&Data::Plugin(Plugin::Reload(id.to_owned()))).await?;
|
||||
c.send(&Data::Plugin(Plugin::Load(id.to_owned()))).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn uninstall_plugin_async(id: &str) -> ResultType<()> {
|
||||
async fn reload_plugin_async(id: &str) -> ResultType<()> {
|
||||
let mut c = connect(1000, "").await?;
|
||||
c.send(&Data::Plugin(Plugin::Uninstall(id.to_owned())))
|
||||
.await?;
|
||||
c.send(&Data::Plugin(Plugin::Reload(id.to_owned()))).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -158,7 +168,7 @@ 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);
|
||||
let value = super::SharedConfig::get(&id, &name);
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::Plugin(Plugin::Config(id, name, value)))
|
||||
@@ -166,12 +176,12 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
allow_err!(crate::plugin::SharedConfig::set(&id, &name, &value));
|
||||
allow_err!(super::SharedConfig::set(&id, &name, &value));
|
||||
}
|
||||
},
|
||||
Plugin::ManagerConfig(name, value) => match value {
|
||||
None => {
|
||||
let value = crate::plugin::ManagerConfig::get_option(&name);
|
||||
let value = super::ManagerConfig::get_option(&name);
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::Plugin(Plugin::ManagerConfig(name, value)))
|
||||
@@ -179,12 +189,12 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
crate::plugin::ManagerConfig::set_option(&name, &value);
|
||||
super::ManagerConfig::set_option(&name, &value);
|
||||
}
|
||||
},
|
||||
Plugin::ManagerPluginConfig(id, name, value) => match value {
|
||||
None => {
|
||||
let value = crate::plugin::ManagerConfig::get_plugin_option(&id, &name);
|
||||
let value = super::ManagerConfig::get_plugin_option(&id, &name);
|
||||
allow_err!(
|
||||
stream
|
||||
.send(&Data::Plugin(Plugin::ManagerPluginConfig(id, name, value)))
|
||||
@@ -192,16 +202,15 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
);
|
||||
}
|
||||
Some(value) => {
|
||||
crate::plugin::ManagerConfig::set_plugin_option(&id, &name, &value);
|
||||
super::ManagerConfig::set_plugin_option(&id, &name, &value);
|
||||
}
|
||||
},
|
||||
Plugin::Load(id) => {
|
||||
allow_err!(super::load_plugin(&id));
|
||||
}
|
||||
Plugin::Reload(id) => {
|
||||
allow_err!(crate::plugin::reload_plugin(&id));
|
||||
}
|
||||
Plugin::Uninstall(_id) => {
|
||||
// to-do: uninstall plugin
|
||||
// 1. unload 2. remove configs 3. remove config files
|
||||
// allow_err!(crate::plugin::unload_plugin(&id));
|
||||
allow_err!(super::reload_plugin(&id));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
370
src/plugin/manager.rs
Normal file
370
src/plugin/manager.rs
Normal file
@@ -0,0 +1,370 @@
|
||||
// 1. Check update.
|
||||
// 2. Install or uninstall.
|
||||
|
||||
use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *};
|
||||
use crate::{common::is_server, flutter};
|
||||
use hbb_common::{allow_err, bail, log, tokio};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
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 IPC_PLUGIN_POSTFIX: &str = "_plugin";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PLUGIN_INFO: Arc<Mutex<HashMap<String, PluginInfo>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ManagerMeta {
|
||||
pub version: String,
|
||||
pub description: String,
|
||||
pub plugins: Vec<PluginMeta>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PluginSource {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PluginInfo {
|
||||
pub source: PluginSource,
|
||||
pub plugin: PluginMeta,
|
||||
pub installed_version: String,
|
||||
pub install_time: String,
|
||||
pub invalid_reason: String,
|
||||
}
|
||||
|
||||
static PLUGIN_SOURCE_LOCAL: &str = "local";
|
||||
pub(super) static PLUGIN_SOURCE_LOCAL_URL: &str = "plugins";
|
||||
|
||||
fn get_plugin_source_list() -> Vec<PluginSource> {
|
||||
// Only one source for now.
|
||||
vec![PluginSource {
|
||||
name: "rustdesk".to_string(),
|
||||
#[cfg(debug_assertions)]
|
||||
url: PLUGIN_SOURCE_LOCAL_URL.to_string(),
|
||||
#[cfg(not(debug_assertions))]
|
||||
url: "https://github.com/fufesou/rustdesk-plugins".to_string(),
|
||||
description: "".to_string(),
|
||||
}]
|
||||
}
|
||||
|
||||
fn get_source_plugins() -> HashMap<String, PluginInfo> {
|
||||
let mut plugins = HashMap::new();
|
||||
for source in get_plugin_source_list().into_iter() {
|
||||
let url = format!("{}/meta.json", source.url);
|
||||
match reqwest::blocking::get(&url) {
|
||||
Ok(resp) => {
|
||||
if !resp.status().is_success() {
|
||||
log::error!(
|
||||
"Failed to get plugin list from '{}', status code: {}",
|
||||
url,
|
||||
resp.status()
|
||||
);
|
||||
}
|
||||
match resp.json::<ManagerMeta>() {
|
||||
Ok(meta) => {
|
||||
for plugin in meta.plugins.iter() {
|
||||
plugins.insert(
|
||||
plugin.id.clone(),
|
||||
PluginInfo {
|
||||
source: source.clone(),
|
||||
plugin: plugin.clone(),
|
||||
installed_version: "".to_string(),
|
||||
install_time: "".to_string(),
|
||||
invalid_reason: "".to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to parse plugin list from '{}', {}", url, e),
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to get plugin list from '{}', {}", url, e),
|
||||
}
|
||||
}
|
||||
plugins
|
||||
}
|
||||
|
||||
fn send_plugin_list_event(plugins: &HashMap<String, PluginInfo>) {
|
||||
let mut plugin_list = plugins.values().collect::<Vec<_>>();
|
||||
plugin_list.sort_by(|a, b| a.plugin.name.cmp(&b.plugin.name));
|
||||
if let Ok(plugin_list) = serde_json::to_string(&plugin_list) {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_MANAGER);
|
||||
m.insert(MSG_TO_UI_PLUGIN_MANAGER_LIST, &plugin_list);
|
||||
if let Ok(event) = serde_json::to_string(&m) {
|
||||
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_plugin_list(load_local: bool) {
|
||||
let mut plugin_info_lock = PLUGIN_INFO.lock().unwrap();
|
||||
|
||||
if load_local {
|
||||
if is_server() {
|
||||
allow_err!(super::plugins::load_plugins());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut plugins = get_source_plugins();
|
||||
for (id, info) in super::plugins::get_plugin_infos().read().unwrap().iter() {
|
||||
if let Some(p) = plugins.get_mut(id) {
|
||||
p.install_time = info.install_time.clone();
|
||||
p.invalid_reason = info.desc.meta().version.clone();
|
||||
} else {
|
||||
plugins.insert(
|
||||
id.to_string(),
|
||||
PluginInfo {
|
||||
source: PluginSource {
|
||||
name: PLUGIN_SOURCE_LOCAL.to_string(),
|
||||
url: PLUGIN_SOURCE_LOCAL_URL.to_string(),
|
||||
description: "".to_string(),
|
||||
},
|
||||
plugin: info.desc.meta().clone(),
|
||||
installed_version: info.desc.meta().version.clone(),
|
||||
install_time: info.install_time.clone(),
|
||||
invalid_reason: "".to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
send_plugin_list_event(&plugins);
|
||||
*plugin_info_lock = plugins;
|
||||
}
|
||||
|
||||
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.plugin.id, plugin.plugin.id, plugin.plugin.version
|
||||
);
|
||||
#[cfg(windows)]
|
||||
let _res =
|
||||
crate::platform::elevate(&format!("--plugin-install '{}' '{}'", id, _plugin_url))?;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
bail!("Plugin not found: {}", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 push_install_event(id: &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);
|
||||
if let Ok(event) = serde_json::to_string(&m) {
|
||||
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_conn(mut stream: crate::ipc::Connection) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Err(err) => {
|
||||
log::trace!("plugin ipc connection closed: {}", err);
|
||||
break;
|
||||
}
|
||||
Ok(Some(data)) => {
|
||||
match &data {
|
||||
crate::ipc::Data::Plugin(super::ipc::Plugin::InstallStatus((id, status))) => {
|
||||
match status {
|
||||
InstallStatus::Downloading(n) => {
|
||||
push_install_event(&id, &format!("downloading-{}", n));
|
||||
},
|
||||
InstallStatus::Installing => {
|
||||
push_install_event(&id, "installing");
|
||||
}
|
||||
InstallStatus::Finished => {
|
||||
allow_err!(super::plugins::load_plugin(&id));
|
||||
allow_err!(super::ipc::load_plugin(id));
|
||||
push_install_event(&id, "finished");
|
||||
}
|
||||
InstallStatus::FailedCreating => {
|
||||
push_install_event(&id, "failed-creating");
|
||||
}
|
||||
InstallStatus::FailedDownloading => {
|
||||
push_install_event(&id, "failed-downloading");
|
||||
}
|
||||
InstallStatus::FailedInstalling => {
|
||||
push_install_event(&id, "failed-installing");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[tokio::main]
|
||||
pub async fn start_ipc() {
|
||||
match crate::ipc::new_listener(IPC_PLUGIN_POSTFIX).await {
|
||||
Ok(mut incoming) => {
|
||||
while let Some(result) = incoming.next().await {
|
||||
match result {
|
||||
Ok(stream) => {
|
||||
log::debug!("Got new connection");
|
||||
tokio::spawn(handle_conn(crate::ipc::Connection::new(stream)));
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Couldn't get plugin client: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to start plugin ipc server: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// install process
|
||||
pub(super) mod install {
|
||||
use super::IPC_PLUGIN_POSTFIX;
|
||||
use crate::{
|
||||
ipc::{connect, Data},
|
||||
plugin::ipc::{InstallStatus, Plugin},
|
||||
};
|
||||
use hbb_common::{allow_err, bail, log, tokio, ResultType};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use zip::ZipArchive;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn send_install_status(id: &str, status: InstallStatus) {
|
||||
allow_err!(_send_install_status(id, status).await);
|
||||
}
|
||||
|
||||
async fn _send_install_status(id: &str, status: InstallStatus) -> ResultType<()> {
|
||||
let mut c = connect(1_000, IPC_PLUGIN_POSTFIX).await?;
|
||||
c.send(&Data::Plugin(Plugin::InstallStatus((
|
||||
id.to_string(),
|
||||
status,
|
||||
))))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn download_to_file(url: &str, file: File) -> ResultType<()> {
|
||||
let resp = match reqwest::blocking::get(url) {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
bail!("get plugin from '{}', {}", url, e);
|
||||
}
|
||||
};
|
||||
|
||||
if !resp.status().is_success() {
|
||||
bail!("get plugin from '{}', status code: {}", url, resp.status());
|
||||
}
|
||||
|
||||
let mut writer = BufWriter::new(file);
|
||||
writer.write_all(resp.bytes()?.as_ref())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn download_file(id: &str, url: &str, filename: &PathBuf) -> bool {
|
||||
let file = match File::create(filename) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create plugin file: {}", e);
|
||||
send_install_status(id, InstallStatus::FailedCreating);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if let Err(e) = download_to_file(url, file) {
|
||||
log::error!("Failed to download plugin '{}', {}", id, e);
|
||||
send_install_status(id, InstallStatus::FailedDownloading);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn do_install_file(filename: &PathBuf, target_dir: &PathBuf) -> ResultType<()> {
|
||||
let mut zip = ZipArchive::new(BufReader::new(File::open(filename)?))?;
|
||||
for i in 0..zip.len() {
|
||||
let mut file = zip.by_index(i)?;
|
||||
let file_path = target_dir.join(file.name());
|
||||
if file.name().ends_with("/") {
|
||||
std::fs::create_dir_all(&file_path)?;
|
||||
} else {
|
||||
if let Some(p) = file_path.parent() {
|
||||
if !p.exists() {
|
||||
std::fs::create_dir_all(&p)?;
|
||||
}
|
||||
}
|
||||
let mut outfile = File::create(&file_path)?;
|
||||
std::io::copy(&mut file, &mut outfile)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_plugin(id: &str, url: &str) {
|
||||
let plugin_dir = match super::super::get_plugin_dir(id) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
send_install_status(id, InstallStatus::FailedCreating);
|
||||
log::error!("Failed to get plugin dir: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if !plugin_dir.exists() {
|
||||
if let Err(e) = std::fs::create_dir_all(&plugin_dir) {
|
||||
send_install_status(id, InstallStatus::FailedCreating);
|
||||
log::error!("Failed to create plugin dir: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let filename = plugin_dir.join(format!("{}.zip", id));
|
||||
if !download_file(id, url, &filename) {
|
||||
return;
|
||||
}
|
||||
send_install_status(id, InstallStatus::Installing);
|
||||
if let Err(e) = do_install_file(&filename, &plugin_dir) {
|
||||
log::error!("Failed to install plugin: {}", e);
|
||||
send_install_status(id, InstallStatus::FailedInstalling);
|
||||
return;
|
||||
}
|
||||
send_install_status(id, InstallStatus::Finished);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use hbb_common::{libc, ResultType};
|
||||
use hbb_common::{libc, tokio, ResultType};
|
||||
use std::{
|
||||
env,
|
||||
ffi::{c_char, c_int, c_void, CStr},
|
||||
path::PathBuf,
|
||||
ptr::null,
|
||||
};
|
||||
|
||||
@@ -10,20 +12,26 @@ mod config;
|
||||
pub mod desc;
|
||||
mod errno;
|
||||
pub mod ipc;
|
||||
mod manager;
|
||||
pub mod native;
|
||||
pub mod native_handlers;
|
||||
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,
|
||||
};
|
||||
pub use plugins::{
|
||||
handle_client_event, handle_listen_event, handle_server_event, handle_ui_event, load_plugin,
|
||||
load_plugins, reload_plugin, sync_ui, 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_EVENT: &str = "plugin_event";
|
||||
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_MANAGER: &str = "plugin_manager";
|
||||
|
||||
pub const EVENT_ON_CONN_CLIENT: &str = "on_conn_client";
|
||||
pub const EVENT_ON_CONN_SERVER: &str = "on_conn_server";
|
||||
@@ -32,6 +40,8 @@ pub const EVENT_ON_CONN_CLOSE_SERVER: &str = "on_conn_close_server";
|
||||
|
||||
pub use config::{ManagerConfig, PeerConfig, SharedConfig};
|
||||
|
||||
use crate::common::is_server;
|
||||
|
||||
/// Common plugin return.
|
||||
///
|
||||
/// [Note]
|
||||
@@ -77,6 +87,25 @@ impl PluginReturn {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
std::thread::spawn(move || manager::start_ipc());
|
||||
if is_server() {
|
||||
manager::remove_plugins();
|
||||
}
|
||||
load_plugin_list(true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_plugins_dir() -> ResultType<PathBuf> {
|
||||
// to-do: linux and macos
|
||||
Ok(PathBuf::from(env::var("ProgramData")?).join(manager::PLUGIN_SOURCE_LOCAL_URL))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_plugin_dir(id: &str) -> ResultType<PathBuf> {
|
||||
Ok(get_plugins_dir()?.join(id))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cstr_to_string(cstr: *const c_char) -> ResultType<String> {
|
||||
Ok(String::from_utf8(unsafe {
|
||||
|
||||
@@ -15,6 +15,7 @@ use std::{
|
||||
ffi::{c_char, c_void},
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0";
|
||||
@@ -26,9 +27,10 @@ lazy_static::lazy_static! {
|
||||
static ref PLUGINS: Arc<RwLock<HashMap<String, Plugin>>> = Default::default();
|
||||
}
|
||||
|
||||
struct PluginInfo {
|
||||
path: String,
|
||||
desc: Desc,
|
||||
pub(super) struct PluginInfo {
|
||||
pub path: String,
|
||||
pub install_time: String,
|
||||
pub desc: Desc,
|
||||
}
|
||||
|
||||
/// Initialize the plugins.
|
||||
@@ -136,6 +138,11 @@ struct Callbacks {
|
||||
native: CallbackNative,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct InitInfo {
|
||||
is_server: bool,
|
||||
}
|
||||
|
||||
/// The plugin initialize data.
|
||||
/// version: The version of the plugin, can't be nullptr.
|
||||
/// local_peer_id: The local peer id, can't be nullptr.
|
||||
@@ -143,6 +150,7 @@ struct Callbacks {
|
||||
#[repr(C)]
|
||||
struct InitData {
|
||||
version: *const c_char,
|
||||
info: *const c_char,
|
||||
cbs: Callbacks,
|
||||
}
|
||||
|
||||
@@ -255,34 +263,54 @@ 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() {
|
||||
Some(dir) => {
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
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)
|
||||
{
|
||||
pub(super) fn load_plugins() -> ResultType<()> {
|
||||
let plugins_dir = super::get_plugins_dir()?;
|
||||
if !plugins_dir.exists() {
|
||||
std::fs::create_dir_all(&plugins_dir)?;
|
||||
} else {
|
||||
for entry in std::fs::read_dir(plugins_dir)? {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let plugin_dir = entry.path();
|
||||
if plugin_dir.is_dir() {
|
||||
load_plugin_dir(&plugin_dir);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read plugins dir entry, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_plugin_dir(dir: &PathBuf) {
|
||||
if let Ok(rd) = std::fs::read_dir(dir) {
|
||||
for entry in rd {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let filename = entry.file_name();
|
||||
let filename = filename.to_str().unwrap_or("");
|
||||
if filename.starts_with("plugin_") && filename.ends_with(DYLIB_SUFFIX) {
|
||||
if let Some(path) = path.to_str() {
|
||||
if let Err(e) = load_plugin_path(path) {
|
||||
log::error!("Failed to load plugin {}, {}", filename, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read dir entry, {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to read '{}' dir entry, {}",
|
||||
dir.file_stem().and_then(|f| f.to_str()).unwrap_or(""),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
bail!("Failed to get parent dir of {}", exe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,7 +337,7 @@ pub fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
None => bail!("Plugin {} not found", id),
|
||||
};
|
||||
unload_plugin(id);
|
||||
load_plugin(Some(&path), Some(id))
|
||||
load_plugin_path(&path)
|
||||
}
|
||||
|
||||
fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
@@ -319,8 +347,21 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
// to-do validate plugin
|
||||
// to-do check the plugin id (make sure it does not use another plugin's id)
|
||||
|
||||
let init_info = serde_json::to_string(&InitInfo {
|
||||
is_server: crate::common::is_server(),
|
||||
})?;
|
||||
let ptr_info = str_to_cstr_ret(&init_info);
|
||||
let ptr_version = str_to_cstr_ret(crate::VERSION);
|
||||
let _call_on_ret = crate::common::SimpleCallOnReturn {
|
||||
b: true,
|
||||
f: Box::new(move || {
|
||||
free_c_ptr(ptr_info as _);
|
||||
free_c_ptr(ptr_version as _);
|
||||
}),
|
||||
};
|
||||
let init_data = InitData {
|
||||
version: str_to_cstr_ret(crate::VERSION),
|
||||
version: ptr_version as _,
|
||||
info: ptr_info as _,
|
||||
cbs: Callbacks {
|
||||
msg: callback_msg::cb_msg,
|
||||
get_conf: config::cb_get_conf,
|
||||
@@ -332,7 +373,7 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
plugin.init(&init_data, path)?;
|
||||
|
||||
if change_manager() {
|
||||
super::config::ManagerConfig::add_plugin(desc.id())?;
|
||||
super::config::ManagerConfig::add_plugin(&desc.meta().id)?;
|
||||
}
|
||||
|
||||
// update ui
|
||||
@@ -340,10 +381,18 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
update_ui_plugin_desc(&desc, None);
|
||||
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.id().to_string();
|
||||
let id = desc.meta().id.clone();
|
||||
let plugin_info = PluginInfo {
|
||||
path: path.to_string(),
|
||||
install_time,
|
||||
desc,
|
||||
};
|
||||
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info);
|
||||
@@ -360,20 +409,10 @@ pub fn sync_ui(sync_to: String) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_plugin(path: Option<&str>, id: Option<&str>) -> ResultType<()> {
|
||||
match (path, id) {
|
||||
(Some(path), _) => load_plugin_path(path),
|
||||
(None, Some(id)) => {
|
||||
let path = match PLUGIN_INFO.read().unwrap().get(id) {
|
||||
Some(plugin) => plugin.path.clone(),
|
||||
None => bail!("Plugin {} not found", id),
|
||||
};
|
||||
load_plugin_path(&path)
|
||||
}
|
||||
(None, None) => {
|
||||
bail!("path and id are both None");
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn load_plugin(id: &str) -> ResultType<()> {
|
||||
load_plugin_dir(&super::get_plugin_dir(id)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(method: &[u8], id: &str, peer: &str, event: &[u8]) -> ResultType<()> {
|
||||
@@ -418,7 +457,7 @@ fn _handle_listen_event(event: String, peer: String) {
|
||||
let mut plugins = Vec::new();
|
||||
for info in PLUGIN_INFO.read().unwrap().values() {
|
||||
if info.desc.listen_events().contains(&event.to_string()) {
|
||||
plugins.push(info.desc.id().to_string());
|
||||
plugins.push(info.desc.meta().id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +535,7 @@ pub fn handle_client_event(id: &str, peer: &str, event: &[u8]) -> Message {
|
||||
msg
|
||||
);
|
||||
let name = match PLUGIN_INFO.read().unwrap().get(id) {
|
||||
Some(plugin) => plugin.desc.name(),
|
||||
Some(plugin) => &plugin.desc.meta().name,
|
||||
None => "???",
|
||||
}
|
||||
.to_owned();
|
||||
@@ -568,7 +607,7 @@ fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
|
||||
let make_event = |ui: &str| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_RELOAD);
|
||||
m.insert("id", desc.id());
|
||||
m.insert("id", &desc.meta().id);
|
||||
m.insert("location", &location);
|
||||
// Do not depend on the "location" and plugin desc on the ui side.
|
||||
// Send the ui field to ensure the ui is valid.
|
||||
@@ -622,6 +661,10 @@ fn update_ui_plugin_desc(desc: &Desc, sync_to: Option<&str>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_plugin_infos() -> Arc<RwLock<HashMap<String, PluginInfo>>> {
|
||||
PLUGIN_INFO.clone()
|
||||
}
|
||||
|
||||
pub(super) fn get_desc_conf(id: &str) -> Option<super::desc::Config> {
|
||||
PLUGIN_INFO
|
||||
.read()
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::common::IS_X11;
|
||||
#[cfg(target_os = "macos")]
|
||||
use dispatch::Queue;
|
||||
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
|
||||
use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown};
|
||||
use hbb_common::{get_time, protobuf::EnumOrUnknown};
|
||||
use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
|
||||
#[cfg(target_os = "macos")]
|
||||
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
|
||||
@@ -299,8 +299,7 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
|
||||
msg = cached.clone();
|
||||
} else {
|
||||
let mut data = crate::get_cursor_data(hcursor)?;
|
||||
data.colors =
|
||||
hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
|
||||
data.colors = hbb_common::compress::compress(&data.colors[..]).into();
|
||||
let mut tmp = Message::new();
|
||||
tmp.set_cursor_data(data);
|
||||
msg = Arc::new(tmp);
|
||||
|
||||
Reference in New Issue
Block a user