From e6f72e76ddf6f76eadb10abce35a758287b57936 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 5 May 2023 15:57:18 +0800 Subject: [PATCH 01/13] refact Idd err msg Signed-off-by: fufesou --- .../dylib/src/win10/IddController.c | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/libs/virtual_display/dylib/src/win10/IddController.c b/libs/virtual_display/dylib/src/win10/IddController.c index 6c240657a..1b177d47b 100644 --- a/libs/virtual_display/dylib/src/win10/IddController.c +++ b/libs/virtual_display/dylib/src/win10/IddController.c @@ -82,7 +82,7 @@ BOOL InstallUpdate(LPCWSTR fullInfPath, PBOOL rebootRequired) DWORD error = GetLastError(); if (error != 0) { - SetLastMsg("UpdateDriverForPlugAndPlayDevicesW failed, last error 0x%x\n", error); + SetLastMsg("Failed InstallUpdate UpdateDriverForPlugAndPlayDevicesW, last error 0x%x\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -108,7 +108,7 @@ BOOL Uninstall(LPCWSTR fullInfPath, PBOOL rebootRequired) DWORD error = GetLastError(); if (error != 0) { - SetLastMsg("DiUninstallDriverW failed, last error 0x%x\n", error); + SetLastMsg("Failed Uninstall DiUninstallDriverW, last error 0x%x\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -132,7 +132,7 @@ BOOL IsDeviceCreated(PBOOL created) DIGCF_DEVICEINTERFACE)); // Function class devices. if (INVALID_HANDLE_VALUE == hardwareDeviceInfo) { - SetLastMsg("Idd device: SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError()); + SetLastMsg("Idd device: Failed IsDeviceCreated SetupDiGetClassDevs, last error 0x%x\n", GetLastError()); if (g_printMsg) { printf(g_lastMsg); @@ -165,7 +165,7 @@ BOOL IsDeviceCreated(PBOOL created) break; } - SetLastMsg("Idd device: SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", error); + SetLastMsg("Idd device: Failed IsDeviceCreated SetupDiEnumDeviceInterfaces, last error 0x%x\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -209,7 +209,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice) if (hEvent == INVALID_HANDLE_VALUE || hEvent == NULL) { DWORD error = GetLastError(); - SetLastMsg("CreateEvent failed 0x%lx\n", error); + SetLastMsg("Failed DeviceCreate CreateEvent 0x%lx\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -247,7 +247,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice) hSwDevice); if (FAILED(hr)) { - SetLastMsg("SwDeviceCreate failed with 0x%lx\n", hr); + SetLastMsg("Failed DeviceCreate SwDeviceCreate 0x%lx\n", hr); if (g_printMsg) { printf(g_lastMsg); @@ -261,7 +261,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice) DWORD waitResult = WaitForSingleObject(hEvent, 10 * 1000); if (waitResult != WAIT_OBJECT_0) { - SetLastMsg("Wait for device creation failed 0x%d\n", waitResult); + SetLastMsg("Failed DeviceCreate wait for device creation 0x%d\n", waitResult); if (g_printMsg) { printf(g_lastMsg); @@ -288,7 +288,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries) if (retries < 0) { - SetLastMsg("Invalid tries %d\n", retries); + SetLastMsg("Failed MonitorPlugIn invalid tries %d\n", retries); if (g_printMsg) { printf(g_lastMsg); @@ -319,7 +319,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries) HRESULT hr = CoCreateGuid(&plugIn.ContainerId); if (!SUCCEEDED(hr)) { - SetLastMsg("CoCreateGuid failed %d\n", hr); + SetLastMsg("Failed MonitorPlugIn CoCreateGuid %d\n", hr); if (g_printMsg) { printf(g_lastMsg); @@ -348,7 +348,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries) if (ret == FALSE) { DWORD error = GetLastError(); - SetLastMsg("DeviceIoControl failed 0x%lx\n", error); + SetLastMsg("Failed MonitorPlugIn DeviceIoControl 0x%lx\n", error); printf(g_lastMsg); } } @@ -382,7 +382,7 @@ BOOL MonitorPlugOut(UINT index) 0)) // Ptr to Overlapped structure { DWORD error = GetLastError(); - SetLastMsg("DeviceIoControl failed 0x%lx\n", error); + SetLastMsg("Failed MonitorPlugOut DeviceIoControl 0x%lx\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -414,7 +414,7 @@ BOOL MonitorModesUpdate(UINT index, UINT modeCount, PMonitorMode modes) PCtlMonitorModes pMonitorModes = (PCtlMonitorModes)malloc(buflen); if (pMonitorModes == NULL) { - SetLastMsg("CtlMonitorModes malloc failed 0x%lx\n"); + SetLastMsg("Failed MonitorModesUpdate CtlMonitorModes malloc 0x%lx\n"); if (g_printMsg) { printf(g_lastMsg); @@ -441,7 +441,7 @@ BOOL MonitorModesUpdate(UINT index, UINT modeCount, PMonitorMode modes) 0)) // Ptr to Overlapped structure { DWORD error = GetLastError(); - SetLastMsg("DeviceIoControl failed 0x%lx\n", error); + SetLastMsg("Failed MonitorModesUpdate DeviceIoControl 0x%lx\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -495,7 +495,7 @@ GetDevicePath( CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES); if (cr != CR_SUCCESS) { - SetLastMsg("Error GetDevicePath 0x%x retrieving device interface list size.\n", cr); + SetLastMsg("Failed GetDevicePath 0x%x retrieving device interface list size.\n", cr); if (g_printMsg) { printf(g_lastMsg); @@ -611,7 +611,7 @@ BOOLEAN GetDevicePath2( DIGCF_DEVICEINTERFACE)); // Function class devices. if (INVALID_HANDLE_VALUE == hardwareDeviceInfo) { - SetLastMsg("Idd device: SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError()); + SetLastMsg("Idd device: GetDevicePath2 SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError()); if (g_printMsg) { printf(g_lastMsg); @@ -627,7 +627,7 @@ BOOLEAN GetDevicePath2( 0, // &deviceInterfaceData)) { - SetLastMsg("Idd device: SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", GetLastError()); + SetLastMsg("Idd device: GetDevicePath2 SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", GetLastError()); if (g_printMsg) { printf(g_lastMsg); @@ -649,7 +649,7 @@ BOOLEAN GetDevicePath2( if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { - SetLastMsg("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError()); + SetLastMsg("Idd device: GetDevicePath2 SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError()); if (g_printMsg) { printf(g_lastMsg); @@ -671,7 +671,7 @@ BOOLEAN GetDevicePath2( } else { - SetLastMsg("Idd device: HeapAlloc failed, last error 0x%x\n", GetLastError()); + SetLastMsg("Idd device: Failed GetDevicePath2 HeapAlloc, last error 0x%x\n", GetLastError()); if (g_printMsg) { printf(g_lastMsg); @@ -687,7 +687,7 @@ BOOLEAN GetDevicePath2( &requiredLength, NULL)) { - SetLastMsg("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError()); + SetLastMsg("Idd device: Failed GetDevicePath2 SetupDiGetDeviceInterfaceDetail, last error 0x%x\n", GetLastError()); if (g_printMsg) { printf(g_lastMsg); @@ -698,7 +698,7 @@ BOOLEAN GetDevicePath2( hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceDetailData->DevicePath); if (FAILED(hr)) { - SetLastMsg("Error: StringCchCopy failed with HRESULT 0x%x", hr); + SetLastMsg("Error: Failed GetDevicePath2 StringCchCopy HRESULT 0x%x", hr); if (g_printMsg) { printf(g_lastMsg); @@ -737,7 +737,7 @@ HANDLE DeviceOpenHandle() } if (_tcslen(devicePath) == 0) { - SetLastMsg("GetDevicePath got empty device path\n"); + SetLastMsg("DeviceOpenHandle GetDevicePath got empty device path\n"); if (g_printMsg) { printf(g_lastMsg); @@ -759,7 +759,7 @@ HANDLE DeviceOpenHandle() if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) { DWORD error = GetLastError(); - SetLastMsg("CreateFile failed 0x%lx\n", error); + SetLastMsg("Failed DeviceOpenHandle CreateFile 0x%lx\n", error); if (g_printMsg) { printf(g_lastMsg); From 6f5ff0ac0e51375b1c0c784ae4e4aad906676b6d Mon Sep 17 00:00:00 2001 From: fufesou Date: Sat, 6 May 2023 18:45:58 +0800 Subject: [PATCH 02/13] plugin_framework, Remove plugin enable option Signed-off-by: fufesou --- .../desktop/pages/desktop_setting_page.dart | 13 +----- src/flutter_ffi.rs | 36 --------------- src/plugin/config.rs | 44 ++++--------------- 3 files changed, 10 insertions(+), 83 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 29b778afb..e6cfd516a 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1512,18 +1512,7 @@ class _PluginState extends State<_Plugin> { List _buildCards(DescModel model) => [ _Card( title: 'Plugin', - children: [ - _Checkbox( - label: 'Enable', - getValue: () => bind.pluginIsEnabled() ?? false, - setValue: (bool v) async { - if (!v) { - clearLocations(); - } - await bind.pluginEnable(v: v); - }, - ), - ], + children: [], ), ...model.all.entries .map((entry) => PluginCard(pluginId: entry.key, desc: entry.value)) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 458313fce..8f795d1f9 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1545,42 +1545,6 @@ pub fn plugin_id_is_enabled(_id: String) -> SyncReturn { } } -pub fn plugin_enable(_v: bool) { - #[cfg(feature = "plugin_framework")] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - allow_err!(crate::plugin::ipc::set_manager_config( - "enabled", - _v.to_string() - )); - if _v { - allow_err!(crate::plugin::load_plugins()); - } else { - crate::plugin::unload_plugins(); - } - } -} - -pub fn plugin_is_enabled() -> SyncReturn> { - #[cfg(feature = "plugin_framework")] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - 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 { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/plugin/config.rs b/src/plugin/config.rs index 076a70a17..f939cf6bc 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -214,7 +214,6 @@ const MANAGER_VERSION: &str = "0.1.0"; #[derive(Debug, Serialize, Deserialize)] pub struct ManagerConfig { pub version: String, - pub enabled: bool, #[serde(default)] pub options: HashMap, #[serde(default)] @@ -225,7 +224,6 @@ impl Default for ManagerConfig { fn default() -> Self { Self { version: MANAGER_VERSION.to_owned(), - enabled: true, options: HashMap::new(), plugins: HashMap::new(), } @@ -241,43 +239,19 @@ impl ManagerConfig { #[inline] pub fn get_option(key: &str) -> Option { - if key == "enabled" { - Some(CONFIG_MANAGER.lock().unwrap().enabled.to_string()) - } else { - CONFIG_MANAGER - .lock() - .unwrap() - .options - .get(key) - .map(|s| s.to_owned()) - } - } - - fn set_option_enabled(enabled: bool) -> ResultType<()> { - let mut lock = CONFIG_MANAGER.lock().unwrap(); - lock.enabled = enabled; - hbb_common::config::store_path(Self::path(), &*lock) - } - - fn set_option_not_enabled(key: &str, value: &str) -> ResultType<()> { - let mut lock = CONFIG_MANAGER.lock().unwrap(); - lock.options.insert(key.to_owned(), value.to_owned()); - hbb_common::config::store_path(Self::path(), &*lock) + CONFIG_MANAGER + .lock() + .unwrap() + .options + .get(key) + .map(|s| s.to_owned()) } #[inline] pub fn set_option(key: &str, value: &str) { - if key == "enabled" { - let enabled = bool::from_str(value).unwrap_or(false); - allow_err!(Self::set_option_enabled(enabled)); - if enabled { - allow_err!(super::load_plugins()); - } else { - super::unload_plugins(); - } - } else { - allow_err!(Self::set_option_not_enabled(key, value)); - } + let mut lock = CONFIG_MANAGER.lock().unwrap(); + lock.options.insert(key.to_owned(), value.to_owned()); + allow_err!(hbb_common::config::store_path(Self::path(), &*lock)); } #[inline] From db71dd039db3f90fe43bf10a38c6eea217bf6b2f Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 9 May 2023 19:47:26 +0800 Subject: [PATCH 03/13] plugin_framework, manager, install plugin Signed-off-by: fufesou --- Cargo.lock | 158 +++++++- Cargo.toml | 1 + .../desktop/pages/desktop_setting_page.dart | 37 +- flutter/lib/models/model.dart | 6 +- flutter/lib/plugin/desc.dart | 180 --------- flutter/lib/plugin/manager.dart | 270 +++++++++++++ flutter/lib/plugin/model.dart | 2 +- flutter/lib/plugin/widget.dart | 4 +- libs/hbb_common/Cargo.toml | 2 +- libs/hbb_common/src/compress.rs | 50 ++- libs/hbb_common/src/fs.rs | 4 +- src/common.rs | 17 +- src/core_main.rs | 14 +- src/flutter_ffi.rs | 38 +- src/plugin/config.rs | 2 +- src/plugin/desc.rs | 71 ++-- src/plugin/ipc.rs | 53 +-- src/plugin/manager.rs | 370 ++++++++++++++++++ src/plugin/mod.rs | 33 +- src/plugin/plugins.rs | 133 ++++--- src/server/input_service.rs | 5 +- 21 files changed, 1078 insertions(+), 372 deletions(-) delete mode 100644 flutter/lib/plugin/desc.dart create mode 100644 flutter/lib/plugin/manager.dart create mode 100644 src/plugin/manager.rs diff --git a/Cargo.lock b/Cargo.lock index bb1f01897..8145032b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.7.6" @@ -404,6 +416,12 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bindgen" version = "0.59.2" @@ -578,6 +596,27 @@ dependencies = [ "serde 1.0.163", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cairo-rs" version = "0.16.7" @@ -737,6 +776,15 @@ dependencies = [ "regex", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -974,6 +1022,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.5.0" @@ -1525,6 +1579,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -2814,7 +2869,7 @@ dependencies = [ "tokio-util", "toml 0.7.3", "winapi 0.3.9", - "zstd", + "zstd 0.12.3+zstd.1.5.2", ] [[package]] @@ -2862,6 +2917,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "hound" version = "3.5.0" @@ -4074,6 +4138,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl-probe" version = "0.1.5" @@ -4267,6 +4337,17 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.12" @@ -4279,6 +4360,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -5175,6 +5268,7 @@ dependencies = [ "winreg 0.10.1", "winres", "wol-rs", + "zip", ] [[package]] @@ -5721,6 +5815,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "0.15.44" @@ -7291,19 +7391,58 @@ dependencies = [ ] [[package]] -name = "zstd" -version = "0.9.2+zstd.1.5.1" +name = "zip" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125" dependencies = [ - "zstd-safe", + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time 0.3.20", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe 6.0.5+zstd.1.5.4", ] [[package]] name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" dependencies = [ "libc", "zstd-sys", @@ -7311,12 +7450,13 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", + "pkg-config", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f4a721073..5c9535a5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ chrono = "0.4" cidr-utils = "0.5" libloading = "0.8" fon = "0.6" +zip = "0.6.5" [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] cpal = "0.15" diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index e6cfd516a..ae3889447 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -10,7 +10,7 @@ import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; -import 'package:flutter_hbb/plugin/desc.dart'; +import 'package:flutter_hbb/plugin/manager.dart'; import 'package:flutter_hbb/plugin/model.dart'; import 'package:flutter_hbb/plugin/common.dart'; import 'package:flutter_hbb/plugin/widget.dart'; @@ -1449,12 +1449,10 @@ class _CheckboxState extends State<_Checkbox> { } class PluginCard extends StatefulWidget { - final PluginId pluginId; - final Desc desc; + final PluginInfo plugin; const PluginCard({ Key? key, - required this.pluginId, - required this.desc, + required this.plugin, }) : super(key: key); @override @@ -1462,40 +1460,43 @@ class PluginCard extends StatefulWidget { } class PluginCardState extends State { + PluginId get pluginId => widget.plugin.meta.id; + String get pluginName => widget.plugin.meta.name; + @override Widget build(BuildContext context) { final children = [ _Button( 'Reload', () async { - clearPlugin(widget.pluginId); - await bind.pluginReload(id: widget.pluginId); + clearPlugin(pluginId); + await bind.pluginReload(id: pluginId); setState(() {}); }, ), _Checkbox( label: 'Enable', - getValue: () => bind.pluginIdIsEnabled(id: widget.pluginId), + getValue: () => bind.pluginIsEnabled(id: pluginId), setValue: (bool v) async { if (!v) { - clearPlugin(widget.pluginId); + clearPlugin(pluginId); } - await bind.pluginIdEnable(id: widget.pluginId, v: v); + await bind.pluginEnable(id: pluginId, v: v); setState(() {}); }, ), ]; - final model = getPluginModel(kLocationHostMainPlugin, widget.pluginId); + final model = getPluginModel(kLocationHostMainPlugin, pluginId); if (model != null) { children.add(PluginItem( - pluginId: widget.pluginId, + pluginId: pluginId, peerId: '', location: kLocationHostMainPlugin, pluginModel: model, isMenu: false, )); } - return _Card(title: widget.desc.name, children: children); + return _Card(title: pluginName, children: children); } } @@ -1509,14 +1510,12 @@ class _Plugin extends StatefulWidget { class _PluginState extends State<_Plugin> { // temp checkbox widget - List _buildCards(DescModel model) => [ + List _buildCards(PluginManager model) => [ _Card( title: 'Plugin', children: [], ), - ...model.all.entries - .map((entry) => PluginCard(pluginId: entry.key, desc: entry.value)) - .toList(), + ...model.plugins.map((entry) => PluginCard(plugin: entry)).toList(), ]; @override @@ -1525,8 +1524,8 @@ class _PluginState extends State<_Plugin> { return DesktopScrollWrapper( scrollController: scrollController, child: ChangeNotifierProvider.value( - value: DescModel.instance, - child: Consumer(builder: (context, model, child) { + value: pluginManager, + child: Consumer(builder: (context, model, child) { return ListView( physics: DraggableNeverScrollableScrollPhysics(), controller: scrollController, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index e40f0615f..a91e1dc6f 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -17,7 +17,7 @@ import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/plugin/event.dart'; -import 'package:flutter_hbb/plugin/desc.dart'; +import 'package:flutter_hbb/plugin/manager.dart'; import 'package:flutter_hbb/plugin/widget.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; @@ -230,8 +230,8 @@ class FfiModel with ChangeNotifier { parent.target?.serverModel.updateVoiceCallState(evt); } else if (name == 'fingerprint') { FingerprintState.find(peerId).value = evt['fingerprint'] ?? ''; - } else if (name == 'plugin_desc') { - updateDesc(evt); + } else if (name == 'plugin_manager') { + pluginManager.handleEvent(evt); } else if (name == 'plugin_event') { handlePluginEvent( evt, peerId, (Map e) => handleMsgBox(e, peerId)); diff --git a/flutter/lib/plugin/desc.dart b/flutter/lib/plugin/desc.dart deleted file mode 100644 index b46ec08b1..000000000 --- a/flutter/lib/plugin/desc.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'dart:convert'; -import 'dart:collection'; -import 'package:flutter/foundation.dart'; - -import './common.dart'; - -const String kValueTrue = '1'; -const String kValueFalse = '0'; - -class UiType { - String key; - String text; - String tooltip; - String action; - - UiType(this.key, this.text, this.tooltip, this.action); - - UiType.fromJson(Map json) - : key = json['key'] ?? '', - text = json['text'] ?? '', - tooltip = json['tooltip'] ?? '', - action = json['action'] ?? ''; - - static UiType? create(Map json) { - if (json['t'] == 'Button') { - return UiButton.fromJson(json['c']); - } else if (json['t'] == 'Checkbox') { - return UiCheckbox.fromJson(json['c']); - } else { - return null; - } - } -} - -class UiButton extends UiType { - String icon; - - UiButton( - {required String key, - required String text, - required this.icon, - required String tooltip, - required String action}) - : super(key, text, tooltip, action); - - UiButton.fromJson(Map json) - : icon = json['icon'] ?? '', - super.fromJson(json); -} - -class UiCheckbox extends UiType { - UiCheckbox( - {required String key, - required String text, - required String tooltip, - required String action}) - : super(key, text, tooltip, action); - - UiCheckbox.fromJson(Map json) : super.fromJson(json); -} - -class Location { - // location key: - // host|main|settings|plugin - // client|remote|toolbar|display - HashMap ui; - - Location(this.ui); - Location.fromJson(Map json) : ui = HashMap() { - (json['ui'] as Map).forEach((key, value) { - var ui = UiType.create(value); - if (ui != null) { - this.ui[ui.key] = ui; - } - }); - } -} - -class ConfigItem { - String key; - String description; - String defaultValue; - - ConfigItem(this.key, this.defaultValue, this.description); - ConfigItem.fromJson(Map json) - : key = json['key'] ?? '', - description = json['description'] ?? '', - defaultValue = json['default'] ?? ''; - - static String get trueValue => kValueTrue; - static String get falseValue => kValueFalse; - static bool isTrue(String value) => value == kValueTrue; - static bool isFalse(String value) => value == kValueFalse; -} - -class Config { - List shared; - List peer; - - Config(this.shared, this.peer); - Config.fromJson(Map json) - : shared = (json['shared'] as List) - .map((e) => ConfigItem.fromJson(e)) - .toList(), - peer = (json['peer'] as List) - .map((e) => ConfigItem.fromJson(e)) - .toList(); -} - -class Desc { - String id; - String name; - String version; - String description; - String author; - String home; - String license; - String published; - String released; - String github; - Location location; - Config config; - - Desc( - this.id, - this.name, - this.version, - this.description, - this.author, - this.home, - this.license, - this.published, - this.released, - this.github, - this.location, - this.config); - - Desc.fromJson(Map json) - : id = json['id'] ?? '', - name = json['name'] ?? '', - version = json['version'] ?? '', - description = json['description'] ?? '', - author = json['author'] ?? '', - home = json['home'] ?? '', - license = json['license'] ?? '', - published = json['published'] ?? '', - released = json['released'] ?? '', - github = json['github'] ?? '', - location = Location.fromJson(json['location']), - config = Config.fromJson(json['config']); -} - -class DescModel with ChangeNotifier { - final data = {}; - - DescModel._(); - - void _updateDesc(Map desc) { - 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) { - return data[id]; - } - - Map get all => data; - - static final DescModel _instance = DescModel._(); - static DescModel get instance => _instance; -} - -void updateDesc(Map desc) => - DescModel.instance._updateDesc(desc); -Desc? getDesc(String id) => DescModel.instance._getDesc(id); diff --git a/flutter/lib/plugin/manager.dart b/flutter/lib/plugin/manager.dart new file mode 100644 index 000000000..c445efaf0 --- /dev/null +++ b/flutter/lib/plugin/manager.dart @@ -0,0 +1,270 @@ +// The plugin manager is a singleton class that manages the plugins. +// 1. It merge metadata and the desc of plugins. + +import 'dart:collection'; +import 'package:flutter/material.dart'; + +const String kValueTrue = '1'; +const String kValueFalse = '0'; + +class ConfigItem { + String key; + String description; + String defaultValue; + + ConfigItem(this.key, this.defaultValue, this.description); + ConfigItem.fromJson(Map json) + : key = json['key'] ?? '', + description = json['description'] ?? '', + defaultValue = json['default'] ?? ''; + + static String get trueValue => kValueTrue; + static String get falseValue => kValueFalse; + static bool isTrue(String value) => value == kValueTrue; + static bool isFalse(String value) => value == kValueFalse; +} + +class UiType { + String key; + String text; + String tooltip; + String action; + + UiType(this.key, this.text, this.tooltip, this.action); + + UiType.fromJson(Map json) + : key = json['key'] ?? '', + text = json['text'] ?? '', + tooltip = json['tooltip'] ?? '', + action = json['action'] ?? ''; + + static UiType? create(Map json) { + if (json['t'] == 'Button') { + return UiButton.fromJson(json['c']); + } else if (json['t'] == 'Checkbox') { + return UiCheckbox.fromJson(json['c']); + } else { + return null; + } + } +} + +class UiButton extends UiType { + String icon; + + UiButton( + {required String key, + required String text, + required this.icon, + required String tooltip, + required String action}) + : super(key, text, tooltip, action); + + UiButton.fromJson(Map json) + : icon = json['icon'] ?? '', + super.fromJson(json); +} + +class UiCheckbox extends UiType { + UiCheckbox( + {required String key, + required String text, + required String tooltip, + required String action}) + : super(key, text, tooltip, action); + + UiCheckbox.fromJson(Map json) : super.fromJson(json); +} + +class Location { + // location key: + // host|main|settings|plugin + // client|remote|toolbar|display + HashMap ui; + + Location(this.ui); + Location.fromJson(Map json) : ui = HashMap() { + (json['ui'] as Map).forEach((key, value) { + var ui = UiType.create(value); + if (ui != null) { + this.ui[ui.key] = ui; + } + }); + } +} + +class PublishInfo { + PublishInfo({ + required this.lastReleased, + required this.published, + }); + + final DateTime lastReleased; + final DateTime published; +} + +class Meta { + Meta({ + required this.id, + required this.name, + required this.version, + required this.description, + required this.author, + required this.home, + required this.license, + required this.publishInfo, + required this.source, + }); + + final String id; + final String name; + final String version; + final String description; + final String author; + final String home; + final String license; + final PublishInfo publishInfo; + final String source; +} + +class SourceInfo { + String name; // 1. RustDesk github 2. Local + String url; + String description; + + SourceInfo({ + required this.name, + required this.url, + required this.description, + }); +} + +class PluginInfo with ChangeNotifier { + SourceInfo sourceInfo; + Meta meta; + String installedVersion; // It is empty if not installed. + DateTime installTime; + String invalidReason; // It is empty if valid. + + PluginInfo({ + required this.sourceInfo, + required this.meta, + required this.installedVersion, + required this.installTime, + required this.invalidReason, + }); + + void update(PluginInfo plugin) { + assert(plugin.meta.id == meta.id, 'Plugin id not match'); + if (plugin.meta.id != meta.id) { + // log error + return; + } + sourceInfo = plugin.sourceInfo; + meta = plugin.meta; + installedVersion = plugin.installedVersion; + installTime = plugin.installTime; + invalidReason = plugin.invalidReason; + notifyListeners(); + } +} + +class PluginManager with ChangeNotifier { + String failedReason = ''; // The reason of failed to load plugins. + final List _plugins = []; + + PluginManager._(); + static final PluginManager _instance = PluginManager._(); + static PluginManager get instance => _instance; + + List get plugins => _plugins; + + PluginInfo? getPlugin(String id) { + for (var p in _plugins) { + if (p.meta.id == id) { + return p; + } + } + return null; + } + + void handleEvent(Map evt) { + if (evt['plugin_list'] != null) { + _handlePluginList(evt['plugin_list']); + } else if (evt['plugin_update'] != null) { + _handlePluginUpdate(evt['plugin_update']); + } else { + debugPrint('Failed to handle manager event: $evt'); + } + } + + void _handlePluginUpdate(Map evt) { + final plugin = _getPluginFromEvent(evt); + if (plugin == null) { + return; + } + for (var i = 0; i < _plugins.length; i++) { + if (_plugins[i].meta.id == plugin.meta.id) { + _plugins[i].update(plugin); + return; + } + } + } + + void _handlePluginList(List evt) { + _plugins.clear(); + + for (var p in evt) { + final plugin = _getPluginFromEvent(p); + if (plugin == null) { + continue; + } + _plugins.add(plugin); + } + + notifyListeners(); + } + + PluginInfo? _getPluginFromEvent(Map evt) { + final s = evt['source']; + assert(s != null, 'Source is null'); + if (s == null) { + return null; + } + final source = SourceInfo( + name: s['name'], + url: s['url'] ?? '', + description: s['description'] ?? '', + ); + + final m = evt['meta']; + assert(m != null, 'Meta is null'); + if (m == null) { + return null; + } + final meta = Meta( + id: m['id'], + name: m['name'], + version: m['version'], + description: m['description'] ?? '', + author: m['author'], + home: m['home'] ?? '', + license: m['license'] ?? '', + source: m['source'] ?? '', + publishInfo: PublishInfo( + lastReleased: DateTime.parse( + m['publish_info']?['lastReleased'] ?? '1970-01-01T00+00:00'), + published: DateTime.parse( + m['publish_info']?['published'] ?? '1970-01-01T00+00:00')), + ); + return PluginInfo( + sourceInfo: source, + meta: meta, + installedVersion: evt['installed_version'], + installTime: evt['install_time'], + invalidReason: evt['invalid_reason'] ?? '', + ); + } +} + +PluginManager get pluginManager => PluginManager.instance; diff --git a/flutter/lib/plugin/model.dart b/flutter/lib/plugin/model.dart index 120f33117..44579bcd6 100644 --- a/flutter/lib/plugin/model.dart +++ b/flutter/lib/plugin/model.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import './common.dart'; -import './desc.dart'; +import './manager.dart'; final Map _locationModels = {}; final Map _optionModels = {}; diff --git a/flutter/lib/plugin/widget.dart b/flutter/lib/plugin/widget.dart index 99a8b3ace..19ad4cbc6 100644 --- a/flutter/lib/plugin/widget.dart +++ b/flutter/lib/plugin/widget.dart @@ -10,7 +10,7 @@ import 'package:get/get.dart'; import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'package:flutter_hbb/models/platform_model.dart'; -import './desc.dart'; +import './manager.dart'; import './model.dart'; import './common.dart'; @@ -247,7 +247,7 @@ class PluginItem extends StatelessWidget { }) { final event = MsgFromUi( id: pluginId, - name: getDesc(pluginId)?.name ?? '', + name: pluginManager.getPlugin(pluginId)?.meta.name ?? '', location: location, key: key, value: diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index 50b690f4a..e886ad1f5 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -16,7 +16,7 @@ bytes = { version = "1.4", features = ["serde"] } log = "0.4" env_logger = "0.10" socket2 = { version = "0.3", features = ["reuseport"] } -zstd = "0.9" +zstd = "0.12" quinn = {version = "0.9", optional = true } anyhow = "1.0" futures-util = "0.3" diff --git a/libs/hbb_common/src/compress.rs b/libs/hbb_common/src/compress.rs index e7668a949..c52dd93a1 100644 --- a/libs/hbb_common/src/compress.rs +++ b/libs/hbb_common/src/compress.rs @@ -1,23 +1,28 @@ -use std::cell::RefCell; -use zstd::block::{Compressor, Decompressor}; +use std::{cell::RefCell, io}; +use zstd::bulk::{Compressor, Decompressor}; +// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(), +// which is currently 22. Levels >= 20 +// Default level is ZSTD_CLEVEL_DEFAULT==3. +// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT thread_local! { - static COMPRESSOR: RefCell = RefCell::new(Compressor::new()); - static DECOMPRESSOR: RefCell = RefCell::new(Decompressor::new()); + static COMPRESSOR: RefCell>> = RefCell::new(Compressor::new(crate::config::COMPRESS_LEVEL)); + static DECOMPRESSOR: RefCell>> = RefCell::new(Decompressor::new()); } -/// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(), -/// which is currently 22. Levels >= 20 -/// Default level is ZSTD_CLEVEL_DEFAULT==3. -/// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT -pub fn compress(data: &[u8], level: i32) -> Vec { +pub fn compress(data: &[u8]) -> Vec { let mut out = Vec::new(); COMPRESSOR.with(|c| { if let Ok(mut c) = c.try_borrow_mut() { - match c.compress(data, level) { - Ok(res) => out = res, + match &mut *c { + Ok(c) => match c.compress(data) { + Ok(res) => out = res, + Err(err) => { + crate::log::debug!("Failed to compress: {}", err); + } + }, Err(err) => { - crate::log::debug!("Failed to compress: {}", err); + crate::log::debug!("Failed to get compressor: {}", err); } } } @@ -29,14 +34,21 @@ pub fn decompress(data: &[u8]) -> Vec { let mut out = Vec::new(); DECOMPRESSOR.with(|d| { if let Ok(mut d) = d.try_borrow_mut() { - const MAX: usize = 1024 * 1024 * 64; - const MIN: usize = 1024 * 1024; - let mut n = 30 * data.len(); - n = n.clamp(MIN, MAX); - match d.decompress(data, n) { - Ok(res) => out = res, + match &mut *d { + Ok(d) => { + const MAX: usize = 1024 * 1024 * 64; + const MIN: usize = 1024 * 1024; + let mut n = 30 * data.len(); + n = n.clamp(MIN, MAX); + match d.decompress(data, n) { + Ok(res) => out = res, + Err(err) => { + crate::log::debug!("Failed to decompress: {}", err); + } + } + } Err(err) => { - crate::log::debug!("Failed to decompress: {}", err); + crate::log::debug!("Failed to get decompressor: {}", err); } } } diff --git a/libs/hbb_common/src/fs.rs b/libs/hbb_common/src/fs.rs index 41160f49d..6b8205acf 100644 --- a/libs/hbb_common/src/fs.rs +++ b/libs/hbb_common/src/fs.rs @@ -10,7 +10,7 @@ use crate::{bail, get_version_number, message_proto::*, ResultType, Stream}; // https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html use crate::{ compress::{compress, decompress}, - config::{Config, COMPRESS_LEVEL}, + config::Config, }; pub fn read_dir(path: &Path, include_hidden: bool) -> ResultType { @@ -481,7 +481,7 @@ impl TransferJob { } else { self.finished_size += offset as u64; if !is_compressed_file(name) { - let tmp = compress(&buf, COMPRESS_LEVEL); + let tmp = compress(&buf); if tmp.len() < buf.len() { buf = tmp; compressed = true; diff --git a/src/common.rs b/src/common.rs index f8836e94f..7187b02cc 100644 --- a/src/common.rs +++ b/src/common.rs @@ -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> = Arc::new(Mutex::new(())); } +pub struct SimpleCallOnReturn { + pub b: bool, + pub f: Box, +} + +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(); diff --git a/src/core_main.rs b/src/core_main.rs index 00e7b9829..714e361c3 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -112,7 +112,7 @@ pub fn core_main() -> Option> { #[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> { #[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()); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 8f795d1f9..444b3dccb 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -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 { +pub fn plugin_is_enabled(_id: String) -> SyncReturn { #[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}; diff --git a/src/plugin/config.rs b/src/plugin/config.rs index f939cf6bc..4f44ced42 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -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); } diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs index 5aee35b96..dc196d0ea 100644 --- a/src/plugin/desc.rs +++ b/src/plugin/desc.rs @@ -46,18 +46,29 @@ pub struct Config { pub peer: Vec, } +#[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, @@ -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 { diff --git a/src/plugin/ipc.rs b/src/plugin/ipc.rs index 1c41e4158..a66fd4c72 100644 --- a/src/plugin/ipc.rs +++ b/src/plugin/ipc.rs @@ -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), ManagerConfig(String, Option), ManagerPluginConfig(String, String, Option), + 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> { @@ -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)); } + _ => {} } } diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs new file mode 100644 index 000000000..e1d9ead9b --- /dev/null +++ b/src/plugin/manager.rs @@ -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>> = Arc::new(Mutex::new(HashMap::new())); +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ManagerMeta { + pub version: String, + pub description: String, + pub plugins: Vec, +} + +#[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 { + // 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 { + 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::() { + 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) { + let mut plugin_list = plugins.values().collect::>(); + 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); + } +} diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index db27ea31c..cd5e45a38 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -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 { + // 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 { + Ok(get_plugins_dir()?.join(id)) +} + #[inline] fn cstr_to_string(cstr: *const c_char) -> ResultType { Ok(String::from_utf8(unsafe { diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index 1c8211e21..01a60c1bd 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -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>> = 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::::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>> { + PLUGIN_INFO.clone() +} + pub(super) fn get_desc_conf(id: &str) -> Option { PLUGIN_INFO .read() diff --git a/src/server/input_service.rs b/src/server/input_service.rs index f31ce18ee..ed34ed00c 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -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); From 4eb6bd82a4b9016245183d4e80951a352a3c1b70 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 9 May 2023 22:59:38 +0800 Subject: [PATCH 04/13] plugin_framework, handle plugin list Signed-off-by: fufesou --- flutter/lib/main.dart | 1 + flutter/lib/models/model.dart | 1 + flutter/lib/plugin/manager.dart | 19 +++++++----- libs/hbb_common/Cargo.toml | 2 +- libs/hbb_common/src/lib.rs | 1 + src/flutter_ffi.rs | 2 +- src/plugin/manager.rs | 53 ++++++++++++++++++++------------- src/plugin/mod.rs | 11 +++++-- 8 files changed, 57 insertions(+), 33 deletions(-) diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 68752cf7d..318f9c2cc 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -126,6 +126,7 @@ void runMainApp(bool startService) async { // await windowManager.ensureInitialized(); gFFI.serverModel.startService(); bind.pluginSyncUi(syncTo: kAppTypeMain); + bind.pluginListReload(); } gFFI.userModel.refreshCurrentUser(); runApp(App()); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a91e1dc6f..48e890f4a 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -231,6 +231,7 @@ class FfiModel with ChangeNotifier { } else if (name == 'fingerprint') { FingerprintState.find(peerId).value = evt['fingerprint'] ?? ''; } else if (name == 'plugin_manager') { + debugPrint('REMOVE ME ==================== plugin_manager $evt'); pluginManager.handleEvent(evt); } else if (name == 'plugin_event') { handlePluginEvent( diff --git a/flutter/lib/plugin/manager.dart b/flutter/lib/plugin/manager.dart index c445efaf0..326b2a5e4 100644 --- a/flutter/lib/plugin/manager.dart +++ b/flutter/lib/plugin/manager.dart @@ -1,6 +1,7 @@ // The plugin manager is a singleton class that manages the plugins. // 1. It merge metadata and the desc of plugins. +import 'dart:convert'; import 'dart:collection'; import 'package:flutter/material.dart'; @@ -211,17 +212,19 @@ class PluginManager with ChangeNotifier { } } - void _handlePluginList(List evt) { + void _handlePluginList(String pluginList) { _plugins.clear(); - - for (var p in evt) { - final plugin = _getPluginFromEvent(p); - if (plugin == null) { - continue; + try { + for (var p in json.decode(pluginList) as List) { + final plugin = _getPluginFromEvent(p); + if (plugin == null) { + continue; + } + _plugins.add(plugin); } - _plugins.add(plugin); + } catch (e) { + debugPrint('Failed to decode plugin list \'$pluginList\''); } - notifyListeners(); } diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index e886ad1f5..bff36093f 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -35,6 +35,7 @@ chrono = "0.4" backtrace = "0.3" libc = "0.2" dlopen = "0.1" +toml = "0.7" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] mac_address = "1.1" @@ -55,5 +56,4 @@ winapi = { version = "0.3", features = ["winuser"] } osascript = "0.3" [dev-dependencies] -toml = "0.7" serde_json = "1.0" diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index e80b9e609..ba21edc7e 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -46,6 +46,7 @@ pub mod keyboard; pub use sysinfo; #[cfg(not(any(target_os = "android", target_os = "ios")))] pub use dlopen; +pub use toml; #[cfg(feature = "quic")] pub type Stream = quic::Connection; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 444b3dccb..4d7c21dfd 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1570,7 +1570,7 @@ pub fn plugin_list_reload() { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { - crate::plugin::load_plugin_list(false); + crate::plugin::load_plugin_list(); } } diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index e1d9ead9b..3d8fbef5a 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -3,7 +3,7 @@ use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *}; use crate::{common::is_server, flutter}; -use hbb_common::{allow_err, bail, log, tokio}; +use hbb_common::{allow_err, bail, config::load_path, log, tokio, toml}; use serde_derive::{Deserialize, Serialize}; use serde_json; use std::{ @@ -21,7 +21,7 @@ lazy_static::lazy_static! { static ref PLUGIN_INFO: Arc>> = Arc::new(Mutex::new(HashMap::new())); } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct ManagerMeta { pub version: String, pub description: String, @@ -45,24 +45,47 @@ pub struct PluginInfo { } static PLUGIN_SOURCE_LOCAL: &str = "local"; -pub(super) static PLUGIN_SOURCE_LOCAL_URL: &str = "plugins"; +#[cfg(not(debug_assertions))] fn get_plugin_source_list() -> Vec { // 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(), }] } +#[cfg(debug_assertions)] +fn get_source_plugins() -> HashMap { + let meta_file = super::get_plugins_dir().unwrap().join("meta.toml"); + let mut plugins = HashMap::new(); + let meta = load_path::(meta_file); + let source = PluginSource { + name: "rustdesk".to_string(), + url: "https://github.com/fufesou/rustdesk-plugins".to_string(), + description: "".to_string(), + }; + 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(), + }, + ); + } + plugins +} + +#[cfg(not(debug_assertions))] fn get_source_plugins() -> HashMap { let mut plugins = HashMap::new(); for source in get_plugin_source_list().into_iter() { - let url = format!("{}/meta.json", source.url); + let url = format!("{}/meta.toml", source.url); match reqwest::blocking::get(&url) { Ok(resp) => { if !resp.status().is_success() { @@ -109,16 +132,8 @@ fn send_plugin_list_event(plugins: &HashMap) { } } -pub fn load_plugin_list(load_local: bool) { +pub fn load_plugin_list() { 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) { @@ -130,7 +145,7 @@ pub fn load_plugin_list(load_local: bool) { PluginInfo { source: PluginSource { name: PLUGIN_SOURCE_LOCAL.to_string(), - url: PLUGIN_SOURCE_LOCAL_URL.to_string(), + url: PLUGIN_SOURCE_LOCAL_DIR.to_string(), description: "".to_string(), }, plugin: info.desc.meta().clone(), @@ -163,9 +178,7 @@ pub fn install_plugin(id: &str) -> ResultType<()> { } } -pub(super) fn remove_plugins() { - -} +pub(super) fn remove_plugins() {} // 1. Add to uninstall list. // 2. Try remove. diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index cd5e45a38..c539196f0 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,4 +1,4 @@ -use hbb_common::{libc, tokio, ResultType}; +use hbb_common::{libc, tokio, ResultType, allow_err, log}; use std::{ env, ffi::{c_char, c_int, c_void, CStr}, @@ -38,6 +38,8 @@ pub const EVENT_ON_CONN_SERVER: &str = "on_conn_server"; pub const EVENT_ON_CONN_CLOSE_CLIENT: &str = "on_conn_close_client"; pub const EVENT_ON_CONN_CLOSE_SERVER: &str = "on_conn_close_server"; +static PLUGIN_SOURCE_LOCAL_DIR: &str = "plugins"; + pub use config::{ManagerConfig, PeerConfig, SharedConfig}; use crate::common::is_server; @@ -91,14 +93,17 @@ pub fn init() { std::thread::spawn(move || manager::start_ipc()); if is_server() { manager::remove_plugins(); + allow_err!(plugins::load_plugins()); } - load_plugin_list(true); + load_plugin_list(); } #[inline] fn get_plugins_dir() -> ResultType { // to-do: linux and macos - Ok(PathBuf::from(env::var("ProgramData")?).join(manager::PLUGIN_SOURCE_LOCAL_URL)) + Ok(PathBuf::from(env::var("ProgramData")?) + .join("RustDesk") + .join(PLUGIN_SOURCE_LOCAL_DIR)) } #[inline] From b06fad0e4367e4d912d65466353b77209a235154 Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 9 May 2023 23:30:15 +0800 Subject: [PATCH 05/13] plugin_framework, handle plugin list Signed-off-by: fufesou --- flutter/lib/models/model.dart | 1 - flutter/lib/plugin/manager.dart | 35 +++++++++++++++++----- src/plugin/manager.rs | 52 ++++++++++++++++++--------------- src/plugin/mod.rs | 2 +- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 48e890f4a..a91e1dc6f 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -231,7 +231,6 @@ class FfiModel with ChangeNotifier { } else if (name == 'fingerprint') { FingerprintState.find(peerId).value = evt['fingerprint'] ?? ''; } else if (name == 'plugin_manager') { - debugPrint('REMOVE ME ==================== plugin_manager $evt'); pluginManager.handleEvent(evt); } else if (name == 'plugin_event') { handlePluginEvent( diff --git a/flutter/lib/plugin/manager.dart b/flutter/lib/plugin/manager.dart index 326b2a5e4..c7a9c7b3f 100644 --- a/flutter/lib/plugin/manager.dart +++ b/flutter/lib/plugin/manager.dart @@ -223,7 +223,7 @@ class PluginManager with ChangeNotifier { _plugins.add(plugin); } } catch (e) { - debugPrint('Failed to decode plugin list \'$pluginList\''); + debugPrint('Failed to decode $e, plugin list \'$pluginList\''); } notifyListeners(); } @@ -245,6 +245,22 @@ class PluginManager with ChangeNotifier { if (m == null) { return null; } + + late DateTime lastReleased; + late DateTime published; + try { + lastReleased = DateTime.parse( + m['publish_info']?['last_released'] ?? '1970-01-01T00+00:00'); + } catch (e) { + lastReleased = DateTime.utc(1970); + } + try { + published = DateTime.parse( + m['publish_info']?['published'] ?? '1970-01-01T00+00:00'); + } catch (e) { + published = DateTime.utc(1970); + } + final meta = Meta( id: m['id'], name: m['name'], @@ -254,17 +270,22 @@ class PluginManager with ChangeNotifier { home: m['home'] ?? '', license: m['license'] ?? '', source: m['source'] ?? '', - publishInfo: PublishInfo( - lastReleased: DateTime.parse( - m['publish_info']?['lastReleased'] ?? '1970-01-01T00+00:00'), - published: DateTime.parse( - m['publish_info']?['published'] ?? '1970-01-01T00+00:00')), + publishInfo: + PublishInfo(lastReleased: lastReleased, published: published), ); + + late DateTime installTime; + try { + installTime = + DateTime.parse(evt['install_time'] ?? '1970-01-01T00+00:00'); + } catch (e) { + installTime = DateTime.utc(1970); + } return PluginInfo( sourceInfo: source, meta: meta, installedVersion: evt['installed_version'], - installTime: evt['install_time'], + installTime: installTime, invalidReason: evt['invalid_reason'] ?? '', ); } diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 3d8fbef5a..1828cd7e5 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -2,8 +2,10 @@ // 2. Install or uninstall. use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *}; -use crate::{common::is_server, flutter}; -use hbb_common::{allow_err, bail, config::load_path, log, tokio, toml}; +use crate::flutter; +#[cfg(not(debug_assertions))] +use hbb_common::toml; +use hbb_common::{allow_err, bail, config::load_path, log, tokio}; use serde_derive::{Deserialize, Serialize}; use serde_json; use std::{ @@ -38,7 +40,7 @@ pub struct PluginSource { #[derive(Debug, Serialize)] pub struct PluginInfo { pub source: PluginSource, - pub plugin: PluginMeta, + pub meta: PluginMeta, pub installed_version: String, pub install_time: String, pub invalid_reason: String, @@ -60,18 +62,18 @@ fn get_plugin_source_list() -> Vec { fn get_source_plugins() -> HashMap { let meta_file = super::get_plugins_dir().unwrap().join("meta.toml"); let mut plugins = HashMap::new(); - let meta = load_path::(meta_file); + let manager_meta = load_path::(meta_file); let source = PluginSource { name: "rustdesk".to_string(), url: "https://github.com/fufesou/rustdesk-plugins".to_string(), description: "".to_string(), }; - for plugin in meta.plugins.iter() { + for meta in manager_meta.plugins.iter() { plugins.insert( - plugin.id.clone(), + meta.id.clone(), PluginInfo { source: source.clone(), - plugin: plugin.clone(), + meta: meta.clone(), installed_version: "".to_string(), install_time: "".to_string(), invalid_reason: "".to_string(), @@ -95,22 +97,24 @@ fn get_source_plugins() -> HashMap { resp.status() ); } - match resp.json::() { - 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(), - }, - ); + if let Ok(text) = resp.text() { + match toml::from_str::(&text) { + Ok(manager_meta) => { + for meta in manager_meta.plugins.iter() { + plugins.insert( + meta.id.clone(), + PluginInfo { + source: source.clone(), + meta: meta.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 parse plugin list from '{}', {}", url, e), } } Err(e) => log::error!("Failed to get plugin list from '{}', {}", url, e), @@ -121,7 +125,7 @@ fn get_source_plugins() -> HashMap { fn send_plugin_list_event(plugins: &HashMap) { let mut plugin_list = plugins.values().collect::>(); - plugin_list.sort_by(|a, b| a.plugin.name.cmp(&b.plugin.name)); + plugin_list.sort_by(|a, b| a.meta.name.cmp(&b.meta.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); @@ -148,7 +152,7 @@ pub fn load_plugin_list() { url: PLUGIN_SOURCE_LOCAL_DIR.to_string(), description: "".to_string(), }, - plugin: info.desc.meta().clone(), + meta: info.desc.meta().clone(), installed_version: info.desc.meta().version.clone(), install_time: info.install_time.clone(), invalid_reason: "".to_string(), @@ -165,7 +169,7 @@ pub fn install_plugin(id: &str) -> ResultType<()> { Some(plugin) => { let _plugin_url = format!( "{}/plugins/{}/{}_{}.zip", - plugin.source.url, plugin.plugin.id, plugin.plugin.id, plugin.plugin.version + plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version ); #[cfg(windows)] let _res = diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index c539196f0..878f0b98b 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,4 +1,4 @@ -use hbb_common::{libc, tokio, ResultType, allow_err, log}; +use hbb_common::{allow_err, libc, log, ResultType}; use std::{ env, ffi::{c_char, c_int, c_void, CStr}, From 4ee0fd9676031e965d64d70131d7270205d5e79f Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 10 May 2023 18:58:45 +0800 Subject: [PATCH 06/13] plugin_framework, test install plugin Signed-off-by: fufesou --- .../desktop/pages/desktop_setting_page.dart | 71 +------ .../lib/desktop/widgets/remote_toolbar.dart | 2 +- flutter/lib/models/model.dart | 2 +- flutter/lib/plugin/model.dart | 28 +-- .../{widget.dart => widgets/desc_ui.dart} | 18 +- .../lib/plugin/widgets/desktop_settings.dart | 199 ++++++++++++++++++ src/flutter_ffi.rs | 3 +- src/plugin/desc.rs | 2 +- src/plugin/ipc.rs | 2 +- src/plugin/manager.rs | 58 +++-- src/plugin/mod.rs | 15 +- src/plugin/plugins.rs | 37 +--- 12 files changed, 271 insertions(+), 166 deletions(-) rename flutter/lib/plugin/{widget.dart => widgets/desc_ui.dart} (95%) create mode 100644 flutter/lib/plugin/widgets/desktop_settings.dart diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index ae3889447..74617a140 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -11,9 +11,7 @@ import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; -import 'package:flutter_hbb/plugin/model.dart'; -import 'package:flutter_hbb/plugin/common.dart'; -import 'package:flutter_hbb/plugin/widget.dart'; +import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -1448,58 +1446,6 @@ class _CheckboxState extends State<_Checkbox> { } } -class PluginCard extends StatefulWidget { - final PluginInfo plugin; - const PluginCard({ - Key? key, - required this.plugin, - }) : super(key: key); - - @override - State createState() => PluginCardState(); -} - -class PluginCardState extends State { - PluginId get pluginId => widget.plugin.meta.id; - String get pluginName => widget.plugin.meta.name; - - @override - Widget build(BuildContext context) { - final children = [ - _Button( - 'Reload', - () async { - clearPlugin(pluginId); - await bind.pluginReload(id: pluginId); - setState(() {}); - }, - ), - _Checkbox( - label: 'Enable', - getValue: () => bind.pluginIsEnabled(id: pluginId), - setValue: (bool v) async { - if (!v) { - clearPlugin(pluginId); - } - await bind.pluginEnable(id: pluginId, v: v); - setState(() {}); - }, - ), - ]; - final model = getPluginModel(kLocationHostMainPlugin, pluginId); - if (model != null) { - children.add(PluginItem( - pluginId: pluginId, - peerId: '', - location: kLocationHostMainPlugin, - pluginModel: model, - isMenu: false, - )); - } - return _Card(title: pluginName, children: children); - } -} - class _Plugin extends StatefulWidget { const _Plugin({Key? key}) : super(key: key); @@ -1508,18 +1454,9 @@ class _Plugin extends StatefulWidget { } class _PluginState extends State<_Plugin> { - // temp checkbox widget - - List _buildCards(PluginManager model) => [ - _Card( - title: 'Plugin', - children: [], - ), - ...model.plugins.map((entry) => PluginCard(plugin: entry)).toList(), - ]; - @override Widget build(BuildContext context) { + bind.pluginListReload(); final scrollController = ScrollController(); return DesktopScrollWrapper( scrollController: scrollController, @@ -1529,7 +1466,9 @@ class _PluginState extends State<_Plugin> { return ListView( physics: DraggableNeverScrollableScrollPhysics(), controller: scrollController, - children: _buildCards(model), + children: model.plugins + .map((entry) => DesktopSettingsCard(plugin: entry)) + .toList(), ).marginOnly(bottom: _kListViewBottomMargin); }), ), diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 83cefb047..fa70c1c7a 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -8,7 +8,7 @@ import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_hbb/plugin/widget.dart'; +import 'package:flutter_hbb/plugin/widgets/desc_ui.dart'; import 'package:flutter_hbb/plugin/common.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a91e1dc6f..e36b8d390 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -18,7 +18,7 @@ import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/plugin/event.dart'; import 'package:flutter_hbb/plugin/manager.dart'; -import 'package:flutter_hbb/plugin/widget.dart'; +import 'package:flutter_hbb/plugin/widgets/desc_ui.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:tuple/tuple.dart'; diff --git a/flutter/lib/plugin/model.dart b/flutter/lib/plugin/model.dart index 44579bcd6..4fc024e4c 100644 --- a/flutter/lib/plugin/model.dart +++ b/flutter/lib/plugin/model.dart @@ -22,16 +22,18 @@ class PluginModel with ChangeNotifier { final List uiList = []; final Map opts = {}; - void add(UiType ui) { + void add(List uiList) { bool found = false; - for (int i = 0; i < uiList.length; i++) { - if (uiList[i].key == ui.key) { - uiList[i] = ui; - found = true; + for (var ui in uiList) { + for (int i = 0; i < this.uiList.length; i++) { + if (this.uiList[i].key == ui.key) { + this.uiList[i] = ui; + found = true; + } + } + if (!found) { + this.uiList.add(ui); } - } - if (!found) { - uiList.add(ui); } notifyListeners(); } @@ -44,12 +46,12 @@ class PluginModel with ChangeNotifier { class LocationModel with ChangeNotifier { final Map pluginModels = {}; - void add(PluginId id, UiType ui) { + void add(PluginId id, List uiList) { if (pluginModels[id] != null) { - pluginModels[id]!.add(ui); + pluginModels[id]!.add(uiList); } else { var model = PluginModel(); - model.add(ui); + model.add(uiList); pluginModels[id] = model; notifyListeners(); } @@ -68,11 +70,11 @@ class LocationModel with ChangeNotifier { bool get isEmpty => pluginModels.isEmpty; } -void addLocationUi(String location, PluginId id, UiType ui) { +void addLocationUi(String location, PluginId id, List uiList) { if (_locationModels[location] == null) { _locationModels[location] = LocationModel(); } - _locationModels[location]?.add(id, ui); + _locationModels[location]?.add(id, uiList); } LocationModel? getLocationModel(String location) => _locationModels[location]; diff --git a/flutter/lib/plugin/widget.dart b/flutter/lib/plugin/widgets/desc_ui.dart similarity index 95% rename from flutter/lib/plugin/widget.dart rename to flutter/lib/plugin/widgets/desc_ui.dart index 19ad4cbc6..d67b298b5 100644 --- a/flutter/lib/plugin/widget.dart +++ b/flutter/lib/plugin/widgets/desc_ui.dart @@ -10,9 +10,9 @@ import 'package:get/get.dart'; import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; import 'package:flutter_hbb/models/platform_model.dart'; -import './manager.dart'; -import './model.dart'; -import './common.dart'; +import '../manager.dart'; +import '../model.dart'; +import '../common.dart'; // dup to flutter\lib\desktop\pages\desktop_setting_page.dart const double _kCheckBoxLeftMargin = 10; @@ -280,9 +280,15 @@ void handleReloading(Map evt, String peer) { return; } try { - final ui = UiType.create(json.decode(evt['ui'] as String)); - if (ui != null) { - addLocationUi(evt['location']!, evt['id']!, ui); + final uiList = []; + for (var e in json.decode(evt['ui'] as String)) { + final ui = UiType.create(e); + if (ui != null) { + uiList.add(ui); + } + } + if (uiList.isNotEmpty) { + addLocationUi(evt['location']!, evt['id']!, uiList); } } catch (e) { debugPrint('Failed handleReloading, json decode of ui, $e '); diff --git a/flutter/lib/plugin/widgets/desktop_settings.dart b/flutter/lib/plugin/widgets/desktop_settings.dart new file mode 100644 index 000000000..f1368fbef --- /dev/null +++ b/flutter/lib/plugin/widgets/desktop_settings.dart @@ -0,0 +1,199 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:flutter_hbb/plugin/model.dart'; +import 'package:flutter_hbb/plugin/common.dart'; +import 'package:get/get.dart'; + +import '../manager.dart'; +import './desc_ui.dart'; + +// to-do: use settings from desktop_setting_page.dart +const double _kCardFixedWidth = 540; +const double _kCardLeftMargin = 15; +const double _kContentHMargin = 15; +const double _kTitleFontSize = 20; +const double _kVersionFontSize = 12; + +class DesktopSettingsCard extends StatefulWidget { + final PluginInfo plugin; + DesktopSettingsCard({ + Key? key, + required this.plugin, + }) : super(key: key); + + @override + State createState() => _DesktopSettingsCardState(); +} + +class _DesktopSettingsCardState extends State { + PluginInfo get plugin => widget.plugin; + bool get installed => plugin.installedVersion.isNotEmpty; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Flexible( + child: SizedBox( + width: _kCardFixedWidth, + child: Card( + child: Column( + children: [ + header(), + body(), + ], + ).marginOnly(bottom: 10), + ).marginOnly(left: _kCardLeftMargin, top: 15), + ), + ), + ], + ); + } + + Widget header() { + return Row( + children: [ + headerNameVersion(), + headerInstallEnable(), + ], + ).marginOnly( + left: _kContentHMargin, + top: 10, + bottom: 10, + right: _kContentHMargin, + ); + } + + Widget headerNameVersion() { + return Expanded( + child: Row( + children: [ + Text( + translate(widget.plugin.meta.name), + textAlign: TextAlign.start, + style: const TextStyle( + fontSize: _kTitleFontSize, + ), + ), + SizedBox( + width: 5, + ), + Text( + plugin.meta.version, + textAlign: TextAlign.start, + style: const TextStyle( + fontSize: _kVersionFontSize, + ), + ) + ], + ), + ); + } + + Widget headerButton(String label, VoidCallback onPressed) { + return Container( + child: ElevatedButton( + onPressed: onPressed, + child: Text(label), + ), + ); + } + + Widget headerInstallEnable() { + final installButton = headerButton(installed ? 'uninstall' : 'install', () { + bind.pluginInstall( + id: plugin.meta.id, + b: !installed, + ); + }); + + if (installed) { + final needUpdate = + plugin.installedVersion.compareTo(plugin.meta.version) < 0; + final updateButton = needUpdate + ? headerButton('update', () { + bind.pluginInstall( + id: plugin.meta.id, + b: !installed, + ); + }) + : Container(); + + final isEnabled = bind.pluginIsEnabled(id: plugin.meta.id); + final enableButton = !installed + ? Container() + : headerButton(isEnabled ? 'disable' : 'enable', () { + if (isEnabled) { + clearPlugin(plugin.meta.id); + } + bind.pluginEnable(id: plugin.meta.id, v: !isEnabled); + setState(() {}); + }); + return Row( + children: [ + updateButton, + SizedBox( + width: 10, + ), + installButton, + SizedBox( + width: 10, + ), + enableButton, + ], + ); + } else { + return installButton; + } + } + + Widget body() { + return Column(children: [ + author(), + description(), + more(), + ]).marginOnly( + left: _kCardLeftMargin, + top: 4, + right: _kContentHMargin, + ); + } + + Widget author() { + return Align( + alignment: Alignment.centerLeft, + child: Text(plugin.meta.author), + ); + } + + Widget description() { + return Align( + alignment: Alignment.centerLeft, + child: Text(plugin.meta.description), + ); + } + + Widget more() { + if (!installed) { + return Container(); + } + + final List children = []; + final model = getPluginModel(kLocationHostMainPlugin, plugin.meta.id); + if (model != null) { + children.add(PluginItem( + pluginId: plugin.meta.id, + peerId: '', + location: kLocationHostMainPlugin, + pluginModel: model, + isMenu: false, + )); + } + return ExpansionTile( + title: Text('Options'), + controlAffinity: ListTileControlAffinity.leading, + children: children, + ); + } +} diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 4d7c21dfd..59fe91ced 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1498,7 +1498,7 @@ pub fn plugin_reload(_id: String) { } #[inline] -pub fn plugin_enable(_id: String, _v: bool) { +pub fn plugin_enable(_id: String, _v: bool) -> SyncReturn<()> { #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] { @@ -1512,6 +1512,7 @@ pub fn plugin_enable(_id: String, _v: bool) { } else { crate::plugin::unload_plugin(&_id); } + SyncReturn(()) } } diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs index dc196d0ea..511d187a4 100644 --- a/src/plugin/desc.rs +++ b/src/plugin/desc.rs @@ -30,7 +30,7 @@ pub enum UiType { #[derive(Debug, Serialize, Deserialize)] pub struct Location { - pub ui: HashMap, + pub ui: HashMap>, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/plugin/ipc.rs b/src/plugin/ipc.rs index a66fd4c72..73742cbda 100644 --- a/src/plugin/ipc.rs +++ b/src/plugin/ipc.rs @@ -152,7 +152,7 @@ async fn set_manager_plugin_config_async(id: &str, name: &str, value: String) -> Ok(()) } -async fn load_plugin_async(id: &str) -> ResultType<()> { +pub async fn load_plugin_async(id: &str) -> ResultType<()> { let mut c = connect(1000, "").await?; c.send(&Data::Plugin(Plugin::Load(id.to_owned()))).await?; Ok(()) diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 1828cd7e5..cfbc6216c 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -3,9 +3,7 @@ use super::{desc::Meta as PluginMeta, ipc::InstallStatus, *}; use crate::flutter; -#[cfg(not(debug_assertions))] -use hbb_common::toml; -use hbb_common::{allow_err, bail, config::load_path, log, tokio}; +use hbb_common::{allow_err, bail, log, tokio, toml}; use serde_derive::{Deserialize, Serialize}; use serde_json; use std::{ @@ -48,42 +46,15 @@ pub struct PluginInfo { static PLUGIN_SOURCE_LOCAL: &str = "local"; -#[cfg(not(debug_assertions))] fn get_plugin_source_list() -> Vec { // Only one source for now. vec![PluginSource { name: "rustdesk".to_string(), - url: "https://github.com/fufesou/rustdesk-plugins".to_string(), + url: "https://raw.githubusercontent.com/fufesou/rustdesk-plugins/main".to_string(), description: "".to_string(), }] } -#[cfg(debug_assertions)] -fn get_source_plugins() -> HashMap { - let meta_file = super::get_plugins_dir().unwrap().join("meta.toml"); - let mut plugins = HashMap::new(); - let manager_meta = load_path::(meta_file); - let source = PluginSource { - name: "rustdesk".to_string(), - url: "https://github.com/fufesou/rustdesk-plugins".to_string(), - description: "".to_string(), - }; - for meta in manager_meta.plugins.iter() { - plugins.insert( - meta.id.clone(), - PluginInfo { - source: source.clone(), - meta: meta.clone(), - installed_version: "".to_string(), - install_time: "".to_string(), - invalid_reason: "".to_string(), - }, - ); - } - plugins -} - -#[cfg(not(debug_assertions))] fn get_source_plugins() -> HashMap { let mut plugins = HashMap::new(); for source in get_plugin_source_list().into_iter() { @@ -142,7 +113,8 @@ pub fn load_plugin_list() { 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(); + p.installed_version = info.desc.meta().version.clone(); + p.invalid_reason = "".to_string(); } else { plugins.insert( id.to_string(), @@ -171,9 +143,10 @@ pub fn install_plugin(id: &str) -> ResultType<()> { "{}/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))?; + crate::platform::elevate(&format!("--plugin-install {} {}", id, _plugin_url))?; Ok(()) } None => { @@ -223,7 +196,8 @@ async fn handle_conn(mut stream: crate::ipc::Connection) { } InstallStatus::Finished => { allow_err!(super::plugins::load_plugin(&id)); - allow_err!(super::ipc::load_plugin(id)); + allow_err!(super::ipc::load_plugin_async(id).await); + load_plugin_list(); push_install_event(&id, "finished"); } InstallStatus::FailedCreating => { @@ -373,15 +347,31 @@ pub(super) mod install { } let filename = plugin_dir.join(format!("{}.zip", id)); + + // download if !download_file(id, url, &filename) { return; } + + let filename_to_remove = filename.clone(); + let _call_on_ret = crate::common::SimpleCallOnReturn { + b: true, + f: Box::new(move || { + if let Err(e) = std::fs::remove_file(&filename_to_remove) { + log::error!("Failed to remove plugin file: {}", e); + } + }), + }; + + // install 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; } + + // finished send_install_status(id, InstallStatus::Finished); } } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 878f0b98b..9542171f3 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -27,7 +27,6 @@ pub use 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"; @@ -90,12 +89,12 @@ impl PluginReturn { } pub fn init() { - std::thread::spawn(move || manager::start_ipc()); - if is_server() { + if !is_server() { + std::thread::spawn(move || manager::start_ipc()); + } else { manager::remove_plugins(); - allow_err!(plugins::load_plugins()); } - load_plugin_list(); + allow_err!(plugins::load_plugins()); } #[inline] @@ -134,10 +133,10 @@ fn str_to_cstr_ret(s: &str) -> *const c_char { } #[inline] -fn free_c_ptr(ret: *mut c_void) { - if !ret.is_null() { +fn free_c_ptr(p: *mut c_void) { + if !p.is_null() { unsafe { - libc::free(ret); + libc::free(p); } } } diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index 01a60c1bd..ee4f1691f 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -157,6 +157,7 @@ struct InitData { impl Drop for InitData { fn drop(&mut self) { free_c_ptr(self.version as _); + free_c_ptr(self.info as _); } } @@ -350,18 +351,9 @@ fn load_plugin_path(path: &str) -> ResultType<()> { 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: ptr_version as _, - info: ptr_info as _, + version: str_to_cstr_ret(crate::VERSION), + info: str_to_cstr_ret(&init_info) as _, cbs: Callbacks { msg: callback_msg::cb_msg, get_conf: config::cb_get_conf, @@ -378,7 +370,6 @@ fn load_plugin_path(path: &str) -> ResultType<()> { // update ui // Ui may be not ready now, so we need to update again once ui is ready. - update_ui_plugin_desc(&desc, None); reload_ui(&desc, None); let install_time = PathBuf::from(path) @@ -404,7 +395,6 @@ fn load_plugin_path(path: &str) -> ResultType<()> { pub fn sync_ui(sync_to: String) { for plugin in PLUGIN_INFO.read().unwrap().values() { - update_ui_plugin_desc(&plugin.desc, Some(&sync_to)); reload_ui(&plugin.desc, Some(&sync_to)); } } @@ -640,27 +630,6 @@ fn reload_ui(desc: &Desc, sync_to: Option<&str>) { } } -fn update_ui_plugin_desc(desc: &Desc, sync_to: Option<&str>) { - // This function is rarely used. There's no need to care about serialization efficiency here. - if let Ok(desc_str) = serde_json::to_string(desc) { - let mut m = HashMap::new(); - m.insert("name", MSG_TO_UI_TYPE_PLUGIN_DESC); - m.insert("desc", &desc_str); - let event = serde_json::to_string(&m).unwrap_or("".to_owned()); - match sync_to { - Some(channel) => { - let _res = flutter::push_global_event(channel, event.clone()); - } - None => { - let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone()); - let _res = - flutter::push_global_event(flutter::APP_TYPE_DESKTOP_REMOTE, event.clone()); - let _res = flutter::push_global_event(flutter::APP_TYPE_CM, event.clone()); - } - } - } -} - pub(super) fn get_plugin_infos() -> Arc>> { PLUGIN_INFO.clone() } From 095ac46255f11d17ea01f34226d7035d9427ac34 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 10 May 2023 23:57:46 +0800 Subject: [PATCH 07/13] plugin_framework, test plugin manager, uninstall is not fully tested Signed-off-by: fufesou --- .../desktop/pages/desktop_setting_page.dart | 13 +- flutter/lib/plugin/manager.dart | 77 ++++++++-- .../lib/plugin/widgets/desktop_settings.dart | 35 +++-- src/common.rs | 10 +- src/core_main.rs | 2 +- src/flutter_ffi.rs | 9 +- src/plugin/config.rs | 66 ++++++-- src/plugin/ipc.rs | 17 +++ src/plugin/manager.rs | 143 +++++++++++++++--- src/plugin/mod.rs | 15 +- src/plugin/plugins.rs | 55 +++---- src/server.rs | 1 + 12 files changed, 327 insertions(+), 116 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 74617a140..3202d0d35 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1466,15 +1466,22 @@ class _PluginState extends State<_Plugin> { return ListView( physics: DraggableNeverScrollableScrollPhysics(), controller: scrollController, - children: model.plugins - .map((entry) => DesktopSettingsCard(plugin: entry)) - .toList(), + children: model.plugins.map((entry) => pluginCard(entry)).toList(), ).marginOnly(bottom: _kListViewBottomMargin); }), ), ); } + Widget pluginCard(PluginInfo plugin) { + return ChangeNotifierProvider.value( + value: plugin, + child: Consumer( + builder: (context, model, child) => DesktopSettingsCard(plugin: model), + ), + ); + } + Widget accountAction() { return Obx(() => _Button( gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout', diff --git a/flutter/lib/plugin/manager.dart b/flutter/lib/plugin/manager.dart index c7a9c7b3f..d605aab38 100644 --- a/flutter/lib/plugin/manager.dart +++ b/flutter/lib/plugin/manager.dart @@ -144,17 +144,20 @@ class PluginInfo with ChangeNotifier { SourceInfo sourceInfo; Meta meta; String installedVersion; // It is empty if not installed. - DateTime installTime; + String failedMsg; String invalidReason; // It is empty if valid. PluginInfo({ required this.sourceInfo, required this.meta, required this.installedVersion, - required this.installTime, required this.invalidReason, + this.failedMsg = '', }); + bool get installed => installedVersion.isNotEmpty; + bool get needUpdate => installed && installedVersion != meta.version; + void update(PluginInfo plugin) { assert(plugin.meta.id == meta.id, 'Plugin id not match'); if (plugin.meta.id != meta.id) { @@ -164,10 +167,28 @@ class PluginInfo with ChangeNotifier { sourceInfo = plugin.sourceInfo; meta = plugin.meta; installedVersion = plugin.installedVersion; - installTime = plugin.installTime; invalidReason = plugin.invalidReason; notifyListeners(); } + + void setInstall(String msg) { + if (msg == "finished") { + msg = ''; + } + failedMsg = msg; + if (msg.isEmpty) { + installedVersion = meta.version; + } + notifyListeners(); + } + + void setUninstall(String msg) { + failedMsg = msg; + if (msg.isEmpty) { + installedVersion = ''; + } + notifyListeners(); + } } class PluginManager with ChangeNotifier { @@ -194,11 +215,27 @@ class PluginManager with ChangeNotifier { _handlePluginList(evt['plugin_list']); } else if (evt['plugin_update'] != null) { _handlePluginUpdate(evt['plugin_update']); + } else if (evt['plugin_install'] != null && evt['id'] != null) { + _handlePluginInstall(evt['id'], evt['plugin_install']); + } else if (evt['plugin_uninstall'] != null && evt['id'] != null) { + _handlePluginUninstall(evt['id'], evt['plugin_uninstall']); } else { debugPrint('Failed to handle manager event: $evt'); } } + void _sortPlugins() { + plugins.sort((a, b) { + if (a.installed) { + return -1; + } else if (b.installed) { + return 1; + } else { + return 0; + } + }); + } + void _handlePluginUpdate(Map evt) { final plugin = _getPluginFromEvent(evt); if (plugin == null) { @@ -207,6 +244,8 @@ class PluginManager with ChangeNotifier { for (var i = 0; i < _plugins.length; i++) { if (_plugins[i].meta.id == plugin.meta.id) { _plugins[i].update(plugin); + _sortPlugins(); + notifyListeners(); return; } } @@ -225,9 +264,32 @@ class PluginManager with ChangeNotifier { } catch (e) { debugPrint('Failed to decode $e, plugin list \'$pluginList\''); } + _sortPlugins(); notifyListeners(); } + void _handlePluginInstall(String id, String msg) { + for (var i = 0; i < _plugins.length; i++) { + if (_plugins[i].meta.id == id) { + _plugins[i].setInstall(msg); + _sortPlugins(); + notifyListeners(); + return; + } + } + } + + void _handlePluginUninstall(String id, String msg) { + for (var i = 0; i < _plugins.length; i++) { + if (_plugins[i].meta.id == id) { + _plugins[i].setUninstall(msg); + _sortPlugins(); + notifyListeners(); + return; + } + } + } + PluginInfo? _getPluginFromEvent(Map evt) { final s = evt['source']; assert(s != null, 'Source is null'); @@ -273,19 +335,10 @@ class PluginManager with ChangeNotifier { publishInfo: PublishInfo(lastReleased: lastReleased, published: published), ); - - late DateTime installTime; - try { - installTime = - DateTime.parse(evt['install_time'] ?? '1970-01-01T00+00:00'); - } catch (e) { - installTime = DateTime.utc(1970); - } return PluginInfo( sourceInfo: source, meta: meta, installedVersion: evt['installed_version'], - installTime: installTime, invalidReason: evt['invalid_reason'] ?? '', ); } diff --git a/flutter/lib/plugin/widgets/desktop_settings.dart b/flutter/lib/plugin/widgets/desktop_settings.dart index f1368fbef..232df001f 100644 --- a/flutter/lib/plugin/widgets/desktop_settings.dart +++ b/flutter/lib/plugin/widgets/desktop_settings.dart @@ -28,10 +28,13 @@ class DesktopSettingsCard extends StatefulWidget { class _DesktopSettingsCardState extends State { PluginInfo get plugin => widget.plugin; - bool get installed => plugin.installedVersion.isNotEmpty; + bool get installed => plugin.installed; + + bool isEnabled = false; @override Widget build(BuildContext context) { + isEnabled = bind.pluginIsEnabled(id: plugin.meta.id); return Row( children: [ Flexible( @@ -70,7 +73,7 @@ class _DesktopSettingsCardState extends State { child: Row( children: [ Text( - translate(widget.plugin.meta.name), + widget.plugin.meta.name, textAlign: TextAlign.start, style: const TextStyle( fontSize: _kTitleFontSize, @@ -95,24 +98,25 @@ class _DesktopSettingsCardState extends State { return Container( child: ElevatedButton( onPressed: onPressed, - child: Text(label), + child: Text(translate(label)), ), ); } Widget headerInstallEnable() { - final installButton = headerButton(installed ? 'uninstall' : 'install', () { - bind.pluginInstall( - id: plugin.meta.id, - b: !installed, - ); - }); + final installButton = headerButton( + installed ? 'Uninstall' : 'Install', + () { + bind.pluginInstall( + id: plugin.meta.id, + b: !installed, + ); + }, + ); if (installed) { - final needUpdate = - plugin.installedVersion.compareTo(plugin.meta.version) < 0; - final updateButton = needUpdate - ? headerButton('update', () { + final updateButton = plugin.needUpdate + ? headerButton('Update', () { bind.pluginInstall( id: plugin.meta.id, b: !installed, @@ -120,10 +124,9 @@ class _DesktopSettingsCardState extends State { }) : Container(); - final isEnabled = bind.pluginIsEnabled(id: plugin.meta.id); final enableButton = !installed ? Container() - : headerButton(isEnabled ? 'disable' : 'enable', () { + : headerButton(isEnabled ? 'Disable' : 'Enable', () { if (isEnabled) { clearPlugin(plugin.meta.id); } @@ -175,7 +178,7 @@ class _DesktopSettingsCardState extends State { } Widget more() { - if (!installed) { + if (!(installed && isEnabled)) { return Container(); } diff --git a/src/common.rs b/src/common.rs index 7187b02cc..959ce2896 100644 --- a/src/common.rs +++ b/src/common.rs @@ -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> = 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] diff --git a/src/core_main.rs b/src/core_main.rs index 714e361c3..bda6580ee 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -245,7 +245,7 @@ pub fn core_main() -> Option> { #[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" { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 59fe91ced..dad4d2782 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -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); } } } diff --git a/src/plugin/config.rs b/src/plugin/config.rs index 4f44ced42..a23690a1b 100644 --- a/src/plugin/config.rs +++ b/src/plugin/config.rs @@ -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; @@ -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(()) } } diff --git a/src/plugin/ipc.rs b/src/plugin/ipc.rs index 73742cbda..1ca5521b0 100644 --- a/src/plugin/ipc.rs +++ b/src/plugin/ipc.rs @@ -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> { 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); + } _ => {} } } diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index cfbc6216c..9480445bf 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -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 { 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) { 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> { + 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) => { diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 9542171f3..7ec690c59 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -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] diff --git a/src/plugin/plugins.rs b/src/plugin/plugins.rs index ee4f1691f..2207e4757 100644 --- a/src/plugin/plugins.rs +++ b/src/plugin/plugins.rs @@ -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::::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 { .get(id) .map(|info| info.desc.config().clone()) } + +pub(super) fn get_version(id: &str) -> Option { + PLUGIN_INFO + .read() + .unwrap() + .get(id) + .map(|info| info.desc.meta().version.clone()) +} diff --git a/src/server.rs b/src/server.rs index b6cbdac44..89e57c79e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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); From 8e117b6dde2cc061157f622ea01b086e57440e38 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 11 May 2023 16:54:57 +0800 Subject: [PATCH 08/13] plugin_framework, mid commit Signed-off-by: fufesou --- src/core_main.rs | 3 -- src/platform/linux.rs | 33 +++++++----------- src/platform/mod.rs | 21 +++++++++++ src/plugin/desc.rs | 2 ++ src/plugin/manager.rs | 81 ++++++++++++++++++++++++++++++++----------- src/plugin/mod.rs | 21 +++++++++-- 6 files changed, 115 insertions(+), 46 deletions(-) diff --git a/src/core_main.rs b/src/core_main.rs index bda6580ee..4fc4c2080 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -248,9 +248,6 @@ pub fn core_main() -> Option> { crate::plugin::install_plugin_with_url(&args[1], &args[2]); } return None; - } else if args[0] == "--plugin-uninstall" { - // Do nothing - return None; } } } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index c80ab907a..ca35fc32b 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -9,10 +9,10 @@ use hbb_common::{ regex::{Captures, Regex}, }; use std::{ - string::String, cell::RefCell, path::{Path, PathBuf}, process::{Child, Command}, + string::String, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -453,9 +453,7 @@ pub fn get_active_username() -> String { pub fn get_user_home_by_name(username: &str) -> Option { return match get_user_by_name(username) { - None => { - None - } + None => None, Some(user) => { let home = user.home_dir(); if Path::is_dir(home) { @@ -464,7 +462,7 @@ pub fn get_user_home_by_name(username: &str) -> Option { None } } - } + }; } pub fn get_active_user_home() -> Option { @@ -835,12 +833,7 @@ mod desktop { } fn get_display(&mut self) { - let display_envs = vec![ - GNOME_SESSION_BINARY, - XFCE4_PANEL, - SDDM_GREETER, - PLASMA_X11, - ]; + let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11]; for diplay_env in display_envs { self.display = get_env_tries("DISPLAY", &self.uid, diplay_env, 10); if !self.display.is_empty() { @@ -873,8 +866,8 @@ mod desktop { auth_found = true; } else if auth_found { if std::path::Path::new(v).is_absolute() - && std::path::Path::new(v).exists() { - + && std::path::Path::new(v).exists() + { self.xauth = v.to_string(); } else { if let Some(pid) = line.split_whitespace().nth(1) { @@ -903,12 +896,7 @@ mod desktop { fn get_xauth(&mut self) { // try by direct access to window manager process by name - let display_envs = vec![ - GNOME_SESSION_BINARY, - XFCE4_PANEL, - SDDM_GREETER, - PLASMA_X11, - ]; + let display_envs = vec![GNOME_SESSION_BINARY, XFCE4_PANEL, SDDM_GREETER, PLASMA_X11]; for diplay_env in display_envs { self.xauth = get_env_tries("XAUTHORITY", &self.uid, diplay_env, 10); if !self.xauth.is_empty() { @@ -928,7 +916,7 @@ mod desktop { gdm } else { let username = &self.username; - match get_user_home_by_name(username) { + match get_user_home_by_name(username) { None => { if username == "root" { format!("/{}/.Xauthority", username) @@ -942,7 +930,10 @@ mod desktop { } } Some(home) => { - format!("{}/.Xauthority", home.as_path().to_string_lossy().to_string()) + format!( + "{}/.Xauthority", + home.as_path().to_string_lossy().to_string() + ) } } }; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index e382b0b13..454058b5a 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -54,6 +54,27 @@ pub fn get_active_username() -> String { #[cfg(target_os = "android")] pub const PA_SAMPLE_RATE: u32 = 48000; +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub fn run_as_root(arg: Vec<&str>) -> ResultType> { + let cmd = std::env::current_exe()?; + match cmd.to_str() { + Some(cmd) => { + let mut args = vec![cmd]; + args.append(&mut arg.clone()); + // -E required for opensuse + #[cfg(target_os = "linux")] + if is_opensuse() { + args.insert(0, "-E"); + } + let task = Command::new("sudo").args(args).spawn()?; + Ok(Some(task)) + } + None => { + bail!("Failed to get current exe as str"); + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/plugin/desc.rs b/src/plugin/desc.rs index 511d187a4..04c85d6b0 100644 --- a/src/plugin/desc.rs +++ b/src/plugin/desc.rs @@ -58,6 +58,8 @@ pub struct Meta { pub name: String, pub version: String, pub description: String, + #[serde(default)] + pub platforms: String, pub author: String, pub home: String, pub license: String, diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 9480445bf..2aaddd55f 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -19,6 +19,13 @@ const MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL: &str = "plugin_uninstall"; const IPC_PLUGIN_POSTFIX: &str = "_plugin"; +#[cfg(target_os = "windows")] +const PLUGIN_PLATFORM: &str = "Windows"; +#[cfg(target_os = "linux")] +const PLUGIN_PLATFORM: &str = "Linux"; +#[cfg(target_os = "macos")] +const PLUGIN_PLATFORM: &str = "MacOS"; + lazy_static::lazy_static! { static ref PLUGIN_INFO: Arc>> = Arc::new(Mutex::new(HashMap::new())); } @@ -73,6 +80,9 @@ fn get_source_plugins() -> HashMap { match toml::from_str::(&text) { Ok(manager_meta) => { for meta in manager_meta.plugins.iter() { + if !meta.platforms.contains(PLUGIN_PLATFORM) { + continue; + } plugins.insert( meta.id.clone(), PluginInfo { @@ -147,33 +157,59 @@ pub fn load_plugin_list() { pub fn install_plugin(id: &str) -> ResultType<()> { match PLUGIN_INFO.lock().unwrap().get(id) { Some(plugin) => { + let mut same_plugin_exists = false; + if let Some(version) = super::plugins::get_version(id) { + if version == plugin.meta.version { + same_plugin_exists = true; + } + } + + let plugin_url = format!( + "{}/plugins/{}/{}/{}_{}.zip", + plugin.source.url, + plugin.meta.id, + PLUGIN_PLATFORM, + plugin.meta.id, + plugin.meta.version + ); + + let allowed_install; #[cfg(windows)] { - 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"); + allowed_install = crate::platform::elevate(&args)?; + } + #[cfg(target_os = "linux")] + { + let mut args = vec!["--plugin-install", id]; + if !same_plugin_exists { + args.push(&plugin_url); } + allowed_install = match crate::platform::run_as_root(&args) { + Ok(child) => match child.wait() { + Ok(0) => true, + Ok(code) => { + log::error!("Failed to wait install process, process code: {}", code); + true + } + Err(e) => { + log::error!("Failed to wait install process, error: {}", e); + false + } + }, + } + } + + if allowed_install && 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(()) } @@ -220,7 +256,8 @@ pub(super) fn remove_plugins() -> ResultType<()> { pub fn uninstall_plugin(id: &str, called_by_ui: bool) { if called_by_ui { - match crate::platform::elevate(&format!("--plugin-uninstall {}", id)) { + #[cfg(target_os = "windows")] + match crate::platform::check_super_user_permission() { Ok(true) => { if let Err(e) = super::ipc::uninstall_plugin(id) { log::error!("Failed to uninstall plugin '{}': {}", id, e); @@ -236,7 +273,11 @@ pub fn uninstall_plugin(id: &str, called_by_ui: bool) { return; } Err(e) => { - log::error!("Failed to uninstall plugin '{}': {}", id, e); + log::error!( + "Failed to uninstall plugin '{}', check permission error: {}", + id, + e + ); push_uninstall_event(id, "failed"); return; } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 7ec690c59..9a72d2aed 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -100,10 +100,27 @@ pub fn init() { } } +#[inline] +#[cfg(target_os = "windows")] +fn get_share_dir() -> ResultType { + Ok(PathBuf::from(env::var("ProgramData")?)) +} + +#[inline] +#[cfg(target_os = "linux")] +fn get_share_dir() -> ResultType { + Ok(PathBuf::from("/usr/share")) +} + +#[inline] +#[cfg(target_os = "macos")] +fn get_share_dir() -> ResultType { + Ok(PathBuf::from("/Library/Application Support")) +} + #[inline] fn get_plugins_dir() -> ResultType { - // to-do: linux and macos - Ok(PathBuf::from(env::var("ProgramData")?) + Ok(get_share_dir()? .join("RustDesk") .join(PLUGIN_SOURCE_LOCAL_DIR)) } From 7190d451d4ef70c170d941cd468ee12af5646ffd Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 11 May 2023 18:13:23 +0800 Subject: [PATCH 09/13] plugin_framework, plugin manager, debug linux Signed-off-by: fufesou --- src/platform/linux.rs | 19 +++++++++++++++++++ src/platform/mod.rs | 21 --------------------- src/plugin/ipc.rs | 2 -- src/plugin/manager.rs | 26 ++++++++++++++++++-------- src/plugin/mod.rs | 3 ++- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index ca35fc32b..1e39b1f07 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -660,6 +660,25 @@ pub fn check_super_user_permission() -> ResultType { Ok(status.success() && status.code() == Some(0)) } +pub fn elevate(arg: Vec<&str>) -> ResultType> { + let cmd = std::env::current_exe()?; + match cmd.to_str() { + Some(cmd) => { + let mut args = vec![cmd]; + args.append(&mut arg.clone()); + // -E required for opensuse + if is_opensuse() { + args.insert(0, "-E"); + } + let task = Command::new("pkexec").args(args).spawn()?; + Ok(Some(task)) + } + None => { + hbb_common::bail!("Failed to get current exe as str"); + } + } +} + type GtkSettingsPtr = *mut c_void; type GObjectPtr = *mut c_void; #[link(name = "gtk-3")] diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 454058b5a..e382b0b13 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -54,27 +54,6 @@ pub fn get_active_username() -> String { #[cfg(target_os = "android")] pub const PA_SAMPLE_RATE: u32 = 48000; -#[cfg(any(target_os = "linux", target_os = "macos"))] -pub fn run_as_root(arg: Vec<&str>) -> ResultType> { - let cmd = std::env::current_exe()?; - match cmd.to_str() { - Some(cmd) => { - let mut args = vec![cmd]; - args.append(&mut arg.clone()); - // -E required for opensuse - #[cfg(target_os = "linux")] - if is_opensuse() { - args.insert(0, "-E"); - } - let task = Command::new("sudo").args(args).spawn()?; - Ok(Some(task)) - } - None => { - bail!("Failed to get current exe as str"); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/plugin/ipc.rs b/src/plugin/ipc.rs index 1ca5521b0..8b646a9ae 100644 --- a/src/plugin/ipc.rs +++ b/src/plugin/ipc.rs @@ -2,8 +2,6 @@ use crate::ipc::{connect, Connection, Data}; use hbb_common::{allow_err, log, tokio, ResultType}; use serde_derive::{Deserialize, Serialize}; -#[cfg(not(windows))] -use std::{fs::File, io::prelude::*}; #[derive(Debug, Serialize, Deserialize, Clone)] pub enum InstallStatus { diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 2aaddd55f..8ea72169e 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -190,19 +190,30 @@ pub fn install_plugin(id: &str) -> ResultType<()> { if !same_plugin_exists { args.push(&plugin_url); } - allowed_install = match crate::platform::run_as_root(&args) { - Ok(child) => match child.wait() { - Ok(0) => true, - Ok(code) => { - log::error!("Failed to wait install process, process code: {}", code); - true + allowed_install = match crate::platform::elevate(args) { + Ok(Some(mut child)) => match child.wait() { + Ok(status) => { + if status.success() { + true + } else { + log::error!( + "Failed to wait install process, process status: {:?}", + status + ); + false + } } Err(e) => { log::error!("Failed to wait install process, error: {}", e); false } }, - } + Ok(None) => false, + Err(e) => { + log::error!("Failed to run install process, error: {}", e); + false + } + }; } if allowed_install && same_plugin_exists { @@ -256,7 +267,6 @@ pub(super) fn remove_plugins() -> ResultType<()> { pub fn uninstall_plugin(id: &str, called_by_ui: bool) { if called_by_ui { - #[cfg(target_os = "windows")] match crate::platform::check_super_user_permission() { Ok(true) => { if let Err(e) = super::ipc::uninstall_plugin(id) { diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 9a72d2aed..19fc11979 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -1,6 +1,7 @@ use hbb_common::{libc, log, ResultType}; +#[cfg(target_os = "windows")] +use std::env; use std::{ - env, ffi::{c_char, c_int, c_void, CStr}, path::PathBuf, ptr::null, From 8a70bddd762b177a8b470e13499780fce00f2884 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 11 May 2023 22:54:22 +0800 Subject: [PATCH 10/13] plugin_framework, test macos Signed-off-by: fufesou --- src/platform/linux.rs | 10 +- src/platform/macos.mm | 62 +++++++----- src/platform/macos.rs | 36 ++++++- src/plugin/manager.rs | 219 +++++++++++++++++++++++++++--------------- 4 files changed, 219 insertions(+), 108 deletions(-) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 1e39b1f07..5ca8052f3 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -660,17 +660,17 @@ pub fn check_super_user_permission() -> ResultType { Ok(status.success() && status.code() == Some(0)) } -pub fn elevate(arg: Vec<&str>) -> ResultType> { +pub fn elevate(args: Vec<&str>) -> ResultType> { let cmd = std::env::current_exe()?; match cmd.to_str() { Some(cmd) => { - let mut args = vec![cmd]; - args.append(&mut arg.clone()); + let mut args_with_exe = vec![cmd]; + args_with_exe.append(&mut args.clone()); // -E required for opensuse if is_opensuse() { - args.insert(0, "-E"); + args_with_exe.insert(0, "-E"); } - let task = Command::new("pkexec").args(args).spawn()?; + let task = Command::new("pkexec").args(args_with_exe).spawn()?; Ok(Some(task)) } None => { diff --git a/src/platform/macos.mm b/src/platform/macos.mm index a252a9a8f..d40a56014 100644 --- a/src/platform/macos.mm +++ b/src/platform/macos.mm @@ -42,31 +42,45 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) { #endif } +extern "C" bool Elevate(char* process, char** args) { + AuthorizationRef authRef; + OSStatus status; + + status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults, &authRef); + if (status != errAuthorizationSuccess) { + printf("Failed to create AuthorizationRef\n"); + return false; + } + + AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights authRights = {1, &authItem}; + AuthorizationFlags flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); + if (status != errAuthorizationSuccess) { + printf("Failed to authorize\n"); + return false; + } + + if (process != NULL) { + FILE *pipe = NULL; + status = AuthorizationExecuteWithPrivileges(authRef, process, kAuthorizationFlagDefaults, args, &pipe); + if (status != errAuthorizationSuccess) { + printf("Failed to run as root\n"); + AuthorizationFree(authRef, kAuthorizationFlagDefaults); + return false; + } + } + + AuthorizationFree(authRef, kAuthorizationFlagDefaults); + return true; +} + extern "C" bool MacCheckAdminAuthorization() { - AuthorizationRef authRef; - OSStatus status; - - status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, - kAuthorizationFlagDefaults, &authRef); - if (status != errAuthorizationSuccess) { - printf("Failed to create AuthorizationRef\n"); - return false; - } - - AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0}; - AuthorizationRights authRights = {1, &authItem}; - AuthorizationFlags flags = kAuthorizationFlagDefaults | - kAuthorizationFlagInteractionAllowed | - kAuthorizationFlagPreAuthorize | - kAuthorizationFlagExtendRights; - status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); - if (status != errAuthorizationSuccess) { - printf("Failed to authorize\n"); - return false; - } - - AuthorizationFree(authRef, kAuthorizationFlagDefaults); - return true; + return Elevate(NULL, NULL); } extern "C" float BackingScaleFactor() { diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 954d858bd..9a2c8f918 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -17,11 +17,15 @@ use core_graphics::{ display::{kCGNullWindowID, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo}, window::{kCGWindowName, kCGWindowOwnerPID}, }; -use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution}; +use hbb_common::{allow_err, anyhow::anyhow, bail, libc, log, message_proto::Resolution}; use include_dir::{include_dir, Dir}; use objc::{class, msg_send, sel, sel_impl}; use scrap::{libc::c_void, quartz::ffi::*}; -use std::path::PathBuf; +use std::{ + ffi::{c_char, CString}, + mem::size_of, + path::PathBuf, +}; static PRIVILEGES_SCRIPTS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts"); @@ -35,6 +39,7 @@ extern "C" { fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL; fn InputMonitoringAuthStatus(_: BOOL) -> BOOL; fn MacCheckAdminAuthorization() -> BOOL; + fn Elevate(process: *const c_char, args: *const *const c_char) -> BOOL; fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL; fn MacGetModes( display: u32, @@ -671,3 +676,30 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType< pub fn check_super_user_permission() -> ResultType { unsafe { Ok(MacCheckAdminAuthorization() == YES) } } + +pub fn elevate(args: Vec<&str>) -> ResultType { + let cmd = std::env::current_exe()?; + match cmd.to_str() { + Some(cmd) => { + let cmd = CString::new(cmd)?; + let mut cstring_args = Vec::new(); + for arg in args.iter() { + cstring_args.push(CString::new(*arg)?); + } + unsafe { + let args_ptr: *mut *const c_char = + libc::malloc((cstring_args.len() + 1) * size_of::<*const c_char>()) as _; + for i in 0..cstring_args.len() { + *args_ptr.add(i) = cstring_args[i].as_ptr() as _; + } + *args_ptr.add(cstring_args.len()) = std::ptr::null() as _; + let r = Elevate(cmd.as_ptr() as _, args_ptr as _); + libc::free(args_ptr as _); + Ok(r == YES) + } + } + None => { + bail!("Failed to get current exe str"); + } + } +} diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 8ea72169e..0c60f982f 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -20,11 +20,11 @@ const MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL: &str = "plugin_uninstall"; const IPC_PLUGIN_POSTFIX: &str = "_plugin"; #[cfg(target_os = "windows")] -const PLUGIN_PLATFORM: &str = "Windows"; +const PLUGIN_PLATFORM: &str = "windows"; #[cfg(target_os = "linux")] -const PLUGIN_PLATFORM: &str = "Linux"; +const PLUGIN_PLATFORM: &str = "linux"; #[cfg(target_os = "macos")] -const PLUGIN_PLATFORM: &str = "MacOS"; +const PLUGIN_PLATFORM: &str = "macos"; lazy_static::lazy_static! { static ref PLUGIN_INFO: Arc>> = Arc::new(Mutex::new(HashMap::new())); @@ -67,38 +67,82 @@ fn get_source_plugins() -> HashMap { let mut plugins = HashMap::new(); for source in get_plugin_source_list().into_iter() { let url = format!("{}/meta.toml", 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 reqwest::blocking::get(&url) { + // Ok(resp) => { + // if !resp.status().is_success() { + // log::error!( + // "Failed to get plugin list from '{}', status code: {}", + // url, + // resp.status() + // ); + // } + // if let Ok(text) = resp.text() { + // match toml::from_str::(&text) { + // Ok(manager_meta) => { + // for meta in manager_meta.plugins.iter() { + // if !meta.platforms.to_uppercase().contains(&PLUGIN_PLATFORM.to_uppercase()) { + // continue; + // } + // plugins.insert( + // meta.id.clone(), + // PluginInfo { + // source: source.clone(), + // meta: meta.clone(), + // installed_version: "".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), + // } + + let text = r#" +version = "v0.1.0" +description = "" + +[[plugins]] +id = "RustDesk.c.privacy-mode" +name = "Privacy Mode" +version = "v0.1.0" +description = "This plugin can enable private mode to prevent others from seeing your operations." +platforms = "Windows,Linux,MacOS" +author = "RustDesk" +home = "" +license = "MIT" +source = "" + +[plugins.publish_info] +published = "2023-05-07 14:00:00" +last_released = "2023-05-07 14:00:00" + "# + .to_string(); + match toml::from_str::(&text) { + Ok(manager_meta) => { + for meta in manager_meta.plugins.iter() { + if !meta + .platforms + .to_uppercase() + .contains(&PLUGIN_PLATFORM.to_uppercase()) + { + continue; + } + plugins.insert( + meta.id.clone(), + PluginInfo { + source: source.clone(), + meta: meta.clone(), + installed_version: "".to_string(), + invalid_reason: "".to_string(), + }, ); } - if let Ok(text) = resp.text() { - match toml::from_str::(&text) { - Ok(manager_meta) => { - for meta in manager_meta.plugins.iter() { - if !meta.platforms.contains(PLUGIN_PLATFORM) { - continue; - } - plugins.insert( - meta.id.clone(), - PluginInfo { - source: source.clone(), - meta: meta.clone(), - installed_version: "".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), + Err(e) => log::error!("Failed to parse plugin list from '{}', {}", url, e), } } plugins @@ -154,6 +198,71 @@ pub fn load_plugin_list() { *plugin_info_lock = plugins; } +#[cfg(target_os = "windows")] +fn elevate_install( + plugin_id: &str, + plugin_url: &str, + same_plugin_exists: bool, +) -> ResultType { + // to-do: Support args with space in quotes. 'arg 1' and "arg 2" + let args = if same_plugin_exists { + format!("--plugin-install {}", plugin_id) + } else { + format!("--plugin-install {} {}", plugin_id, plugin_url) + }; + Ok(crate::platform::elevate(&args)?) +} + +#[cfg(target_os = "linux")] +fn elevate_install( + plugin_id: &str, + plugin_url: &str, + same_plugin_exists: bool, +) -> ResultType { + let mut args = vec!["--plugin-install", plugin_id]; + if !same_plugin_exists { + args.push(&plugin_url); + } + allowed_install = match crate::platform::elevate(args) { + Ok(Some(mut child)) => match child.wait() { + Ok(status) => { + if status.success() { + true + } else { + log::error!( + "Failed to wait install process, process status: {:?}", + status + ); + false + } + } + Err(e) => { + log::error!("Failed to wait install process, error: {}", e); + false + } + }, + Ok(None) => false, + Err(e) => { + log::error!("Failed to run install process, error: {}", e); + false + } + }; + Ok(allowed_install) +} + +#[cfg(target_os = "macos")] +fn elevate_install( + plugin_id: &str, + plugin_url: &str, + same_plugin_exists: bool, +) -> ResultType { + let mut args = vec!["--plugin-install", plugin_id]; + if !same_plugin_exists { + args.push(&plugin_url); + } + Ok(crate::platform::elevate(args)?) +} + pub fn install_plugin(id: &str) -> ResultType<()> { match PLUGIN_INFO.lock().unwrap().get(id) { Some(plugin) => { @@ -163,7 +272,6 @@ pub fn install_plugin(id: &str) -> ResultType<()> { same_plugin_exists = true; } } - let plugin_url = format!( "{}/plugins/{}/{}/{}_{}.zip", plugin.source.url, @@ -172,50 +280,7 @@ pub fn install_plugin(id: &str) -> ResultType<()> { plugin.meta.id, plugin.meta.version ); - - let allowed_install; - #[cfg(windows)] - { - // to-do: Support args with space in quotes. 'arg 1' and "arg 2" - let args = if same_plugin_exists { - format!("--plugin-install {}", id) - } else { - format!("--plugin-install {} {}", id, plugin_url) - }; - allowed_install = crate::platform::elevate(&args)?; - } - #[cfg(target_os = "linux")] - { - let mut args = vec!["--plugin-install", id]; - if !same_plugin_exists { - args.push(&plugin_url); - } - allowed_install = match crate::platform::elevate(args) { - Ok(Some(mut child)) => match child.wait() { - Ok(status) => { - if status.success() { - true - } else { - log::error!( - "Failed to wait install process, process status: {:?}", - status - ); - false - } - } - Err(e) => { - log::error!("Failed to wait install process, error: {}", e); - false - } - }, - Ok(None) => false, - Err(e) => { - log::error!("Failed to run install process, error: {}", e); - false - } - }; - } - + let allowed_install = elevate_install(id, &plugin_url, same_plugin_exists)?; if allowed_install && same_plugin_exists { super::ipc::load_plugin(id)?; super::plugins::load_plugin(id)?; From bff54a61f818a17e0220345223ac529dda510783 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 12 May 2023 08:53:43 +0800 Subject: [PATCH 11/13] plugin_framework, update lang Signed-off-by: fufesou --- Cargo.lock | 2 +- src/lang/ca.rs | 7 ++- src/lang/cn.rs | 7 ++- src/lang/cs.rs | 7 ++- src/lang/da.rs | 7 ++- src/lang/de.rs | 7 ++- src/lang/el.rs | 7 ++- src/lang/eo.rs | 7 ++- src/lang/es.rs | 7 ++- src/lang/fa.rs | 7 ++- src/lang/fr.rs | 7 ++- src/lang/hu.rs | 7 ++- src/lang/id.rs | 7 ++- src/lang/it.rs | 7 ++- src/lang/ja.rs | 7 ++- src/lang/ko.rs | 7 ++- src/lang/kz.rs | 7 ++- src/lang/lt.rs | 7 ++- src/lang/nl.rs | 7 ++- src/lang/pl.rs | 7 ++- src/lang/pt_PT.rs | 7 ++- src/lang/ptbr.rs | 7 ++- src/lang/ro.rs | 7 ++- src/lang/ru.rs | 7 ++- src/lang/sk.rs | 7 ++- src/lang/sl.rs | 7 ++- src/lang/sq.rs | 7 ++- src/lang/sr.rs | 7 ++- src/lang/sv.rs | 7 ++- src/lang/template.rs | 7 ++- src/lang/th.rs | 7 ++- src/lang/tr.rs | 7 ++- src/lang/tw.rs | 7 ++- src/lang/ua.rs | 7 ++- src/lang/vn.rs | 7 ++- src/plugin/manager.rs | 102 ++++++++++++------------------------------ 36 files changed, 234 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8145032b9..d795b8a02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7406,7 +7406,7 @@ dependencies = [ "hmac", "pbkdf2", "sha1", - "time 0.3.20", + "time 0.3.21", "zstd 0.11.2+zstd.1.5.2", ] diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 252d5fbdc..0d876d6f2 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 76cbd2f2a..447460174 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "没有指纹"), ("Select a peer", "选择一个被控端"), ("Select peers", "选择被控"), - ("Plugins", "插件") + ("Plugins", "插件"), + ("Uninstall", "卸载"), + ("Update", "更新"), + ("Enable", "启用"), + ("Disable", "禁用"), + ("Options", "选项"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index e70c7d9e5..e38e7620f 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index fc27774f5..e863f534f 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 00c7122a3..bffc724bb 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "Keine Fingerabdrücke"), ("Select a peer", "Gegenstelle auswählen"), ("Select peers", "Gegenstellen auswählen"), - ("Plugins", "Plugins") + ("Plugins", "Plugins"), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 458a8c552..40c3d30d6 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index cd26b5ab5..61e57d5f5 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index d593e6838..4e82c273b 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "sin huellas digitales"), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 2f7444238..af690c1f8 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "بدون اثر انگشت"), ("Select a peer", "یک همتا را انتخاب کنید"), ("Select peers", "همتایان را انتخاب کنید"), - ("Plugins", "پلاگین ها") + ("Plugins", "پلاگین ها"), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 6d9ff9408..645a088bc 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index baeadea16..3fca393b1 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 2f197c486..e746d509d 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index b417e4d98..e95b77666 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "Nessuna firma digitale"), ("Select a peer", "Seleziona un peer"), ("Select peers", "Seelziona peer"), - ("Plugins", "Plugin") + ("Plugins", "Plugin"), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index e2391cfad..cf49377ec 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 436de89e9..eab5f189f 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 8450e1ffc..c0d90b433 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 907d26515..953d0ad43 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 0a7352bff..cc907bf73 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "geen vingerafdrukken"), ("Select a peer", "Selecteer een peer"), ("Select peers", "Selecteer peers"), - ("Plugins", "Plugins") + ("Plugins", "Plugins"), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index a9dc6b0b8..7520d13b7 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "brak sygnatur"), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 608b7bbef..07bb8a065 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 8519b75fb..0e9276f90 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "sem Impressões Digitais"), ("Select a peer", "Selecione um parceiro"), ("Select peers", "Selecione parceiros"), - ("Plugins", "Plugins") + ("Plugins", "Plugins"), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index a6e2d843a..04d9f9d22 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 3f4cb4a74..480183905 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "отпечатки отсутствуют"), ("Select a peer", "Выберите удалённый узел"), ("Select peers", "Выберите удалённые узлы"), - ("Plugins", "Плагины") + ("Plugins", "Плагины"), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 9f5e8f3ee..0e2559193 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 68a2af723..c6188aaf8 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 987eb3a01..c55a4741e 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index f34be7fc1..3f37e23c7 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 30b414f16..5a76a1677 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 9b90aed84..f61635f9a 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 2ea5a98eb..ab4ec4993 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 73da503a5..3fd57d1cd 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 2b53000b5..b9cdb60dd 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", "沒有指紋"), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 634c9b927..68580c73b 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index b55210aa5..9e64ca784 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -500,6 +500,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no fingerprints", ""), ("Select a peer", ""), ("Select peers", ""), - ("Plugins", "") + ("Plugins", ""), + ("Uninstall", ""), + ("Update", ""), + ("Enable", ""), + ("Disable", ""), + ("Options", ""), ].iter().cloned().collect(); } diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index 0c60f982f..e83a1ccbc 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -67,82 +67,38 @@ fn get_source_plugins() -> HashMap { let mut plugins = HashMap::new(); for source in get_plugin_source_list().into_iter() { let url = format!("{}/meta.toml", 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() - // ); - // } - // if let Ok(text) = resp.text() { - // match toml::from_str::(&text) { - // Ok(manager_meta) => { - // for meta in manager_meta.plugins.iter() { - // if !meta.platforms.to_uppercase().contains(&PLUGIN_PLATFORM.to_uppercase()) { - // continue; - // } - // plugins.insert( - // meta.id.clone(), - // PluginInfo { - // source: source.clone(), - // meta: meta.clone(), - // installed_version: "".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), - // } - - let text = r#" -version = "v0.1.0" -description = "" - -[[plugins]] -id = "RustDesk.c.privacy-mode" -name = "Privacy Mode" -version = "v0.1.0" -description = "This plugin can enable private mode to prevent others from seeing your operations." -platforms = "Windows,Linux,MacOS" -author = "RustDesk" -home = "" -license = "MIT" -source = "" - -[plugins.publish_info] -published = "2023-05-07 14:00:00" -last_released = "2023-05-07 14:00:00" - "# - .to_string(); - match toml::from_str::(&text) { - Ok(manager_meta) => { - for meta in manager_meta.plugins.iter() { - if !meta - .platforms - .to_uppercase() - .contains(&PLUGIN_PLATFORM.to_uppercase()) - { - continue; - } - plugins.insert( - meta.id.clone(), - PluginInfo { - source: source.clone(), - meta: meta.clone(), - installed_version: "".to_string(), - invalid_reason: "".to_string(), - }, + 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() ); } + if let Ok(text) = resp.text() { + match toml::from_str::(&text) { + Ok(manager_meta) => { + for meta in manager_meta.plugins.iter() { + if !meta.platforms.to_uppercase().contains(&PLUGIN_PLATFORM.to_uppercase()) { + continue; + } + plugins.insert( + meta.id.clone(), + PluginInfo { + source: source.clone(), + meta: meta.clone(), + installed_version: "".to_string(), + invalid_reason: "".to_string(), + }, + ); + } + } + Err(e) => log::error!("Failed to parse plugin list from '{}', {}", url, e), + } + } } - 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 From 83c110e52833dd57eabf7fb803b643dc8b2c997b Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 12 May 2023 09:10:26 +0800 Subject: [PATCH 12/13] fix build linux Signed-off-by: fufesou --- src/plugin/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/manager.rs b/src/plugin/manager.rs index e83a1ccbc..9fd0f7861 100644 --- a/src/plugin/manager.rs +++ b/src/plugin/manager.rs @@ -179,7 +179,7 @@ fn elevate_install( if !same_plugin_exists { args.push(&plugin_url); } - allowed_install = match crate::platform::elevate(args) { + let allowed_install = match crate::platform::elevate(args) { Ok(Some(mut child)) => match child.wait() { Ok(status) => { if status.success() { From 0b3fae81d8abbba5483df1a8c805630a4e280057 Mon Sep 17 00:00:00 2001 From: fufesou Date: Fri, 12 May 2023 09:59:52 +0800 Subject: [PATCH 13/13] fix build Signed-off-by: fufesou --- src/flutter_ffi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index dad4d2782..1da8cc745 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1512,8 +1512,8 @@ pub fn plugin_enable(_id: String, _v: bool) -> SyncReturn<()> { } else { crate::plugin::unload_plugin(&_id); } - SyncReturn(()) } + SyncReturn(()) } pub fn plugin_is_enabled(_id: String) -> SyncReturn {