plugin_framework, test plugin manager, uninstall is not fully tested
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
4ee0fd9676
commit
095ac46255
@ -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<PluginInfo>(
|
||||
builder: (context, model, child) => DesktopSettingsCard(plugin: model),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget accountAction() {
|
||||
return Obx(() => _Button(
|
||||
gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout',
|
||||
|
@ -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<String, dynamic> 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<String, dynamic> 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'] ?? '',
|
||||
);
|
||||
}
|
||||
|
@ -28,10 +28,13 @@ class DesktopSettingsCard extends StatefulWidget {
|
||||
|
||||
class _DesktopSettingsCardState extends State<DesktopSettingsCard> {
|
||||
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<DesktopSettingsCard> {
|
||||
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<DesktopSettingsCard> {
|
||||
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<DesktopSettingsCard> {
|
||||
})
|
||||
: 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<DesktopSettingsCard> {
|
||||
}
|
||||
|
||||
Widget more() {
|
||||
if (!installed) {
|
||||
if (!(installed && isEnabled)) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
@ -61,6 +61,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref IS_SERVER: bool = std::env::args().nth(1) == Some("--server".to_owned());
|
||||
static ref SERVER_RUNNING: Arc<RwLock<bool>> = Default::default();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -93,9 +94,14 @@ pub fn global_init() -> bool {
|
||||
|
||||
pub fn global_clean() {}
|
||||
|
||||
#[inline]
|
||||
pub fn set_server_running(b: bool) {
|
||||
*SERVER_RUNNING.write().unwrap() = b;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_server() -> bool {
|
||||
*IS_SERVER
|
||||
*IS_SERVER || *SERVER_RUNNING.read().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -245,7 +245,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if args[0] == "--plugin-install" {
|
||||
if args.len() == 3 {
|
||||
crate::plugin::privileged_install_plugin(&args[1], &args[2]);
|
||||
crate::plugin::install_plugin_with_url(&args[1], &args[2]);
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--plugin-uninstall" {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,16 @@ fn path_plugins(id: &str) -> PathBuf {
|
||||
HbbConfig::path("plugins").join(id)
|
||||
}
|
||||
|
||||
pub fn remove(id: &str) {
|
||||
CONFIG_SHARED.lock().unwrap().remove(id);
|
||||
CONFIG_PEERS.lock().unwrap().remove(id);
|
||||
// allow_err is Ok here.
|
||||
allow_err!(ManagerConfig::remove_plugin(id));
|
||||
if let Err(e) = fs::remove_dir_all(path_plugins(id)) {
|
||||
log::error!("Failed to remove plugin '{}' directory: {}", id, e);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SharedConfig {
|
||||
type Target = HashMap<String, String>;
|
||||
|
||||
@ -207,6 +217,7 @@ impl PeerConfig {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PluginStatus {
|
||||
pub enabled: bool,
|
||||
pub uninstalled: bool,
|
||||
}
|
||||
|
||||
const MANAGER_VERSION: &str = "0.1.0";
|
||||
@ -269,7 +280,13 @@ impl ManagerConfig {
|
||||
if let Some(status) = lock.plugins.get_mut(id) {
|
||||
status.enabled = enabled;
|
||||
} else {
|
||||
lock.plugins.insert(id.to_owned(), PluginStatus { enabled });
|
||||
lock.plugins.insert(
|
||||
id.to_owned(),
|
||||
PluginStatus {
|
||||
enabled,
|
||||
uninstalled: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
@ -292,29 +309,50 @@ impl ManagerConfig {
|
||||
#[inline]
|
||||
pub fn add_plugin(id: &str) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.plugins
|
||||
.insert(id.to_owned(), PluginStatus { enabled: true });
|
||||
lock.plugins.insert(
|
||||
id.to_owned(),
|
||||
PluginStatus {
|
||||
enabled: true,
|
||||
uninstalled: false,
|
||||
},
|
||||
);
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove_plugin(id: &str, uninstall: bool) -> ResultType<()> {
|
||||
pub fn remove_plugin(id: &str) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.plugins.remove(id);
|
||||
hbb_common::config::store_path(Self::path(), &*lock)?;
|
||||
if uninstall {
|
||||
allow_err!(fs::remove_dir_all(path_plugins(id)));
|
||||
}
|
||||
Ok(())
|
||||
hbb_common::config::store_path(Self::path(), &*lock)
|
||||
}
|
||||
|
||||
pub fn remove_plugins(uninstall: bool) {
|
||||
#[inline]
|
||||
pub fn is_uninstalled(id: &str) -> bool {
|
||||
CONFIG_MANAGER
|
||||
.lock()
|
||||
.unwrap()
|
||||
.plugins
|
||||
.get(id)
|
||||
.map(|p| p.uninstalled)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_uninstall(id: &str, uninstall: bool) -> ResultType<()> {
|
||||
let mut lock = CONFIG_MANAGER.lock().unwrap();
|
||||
lock.plugins.clear();
|
||||
allow_err!(hbb_common::config::store_path(Self::path(), &*lock));
|
||||
if uninstall {
|
||||
allow_err!(fs::remove_dir_all(HbbConfig::path("plugins")));
|
||||
if let Some(status) = lock.plugins.get_mut(id) {
|
||||
status.uninstalled = uninstall;
|
||||
} else {
|
||||
lock.plugins.insert(
|
||||
id.to_owned(),
|
||||
PluginStatus {
|
||||
enabled: true,
|
||||
uninstalled: uninstall,
|
||||
},
|
||||
);
|
||||
}
|
||||
hbb_common::config::store_path(Self::path(), &*lock)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ pub enum Plugin {
|
||||
Load(String),
|
||||
Reload(String),
|
||||
InstallStatus((String, InstallStatus)),
|
||||
Uninstall(String),
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
@ -66,6 +67,11 @@ pub async fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
reload_plugin_async(id).await
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn uninstall_plugin(id: &str) -> ResultType<()> {
|
||||
uninstall_plugin_async(id).await
|
||||
}
|
||||
|
||||
async fn get_config_async(id: &str, name: &str, ms_timeout: u64) -> ResultType<Option<String>> {
|
||||
let mut c = connect(ms_timeout, "").await?;
|
||||
c.send(&Data::Plugin(Plugin::Config(
|
||||
@ -164,6 +170,13 @@ async fn reload_plugin_async(id: &str) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn uninstall_plugin_async(id: &str) -> ResultType<()> {
|
||||
let mut c = connect(1000, "").await?;
|
||||
c.send(&Data::Plugin(Plugin::Uninstall(id.to_owned())))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
match plugin {
|
||||
Plugin::Config(id, name, value) => match value {
|
||||
@ -206,11 +219,15 @@ pub async fn handle_plugin(plugin: Plugin, stream: &mut Connection) {
|
||||
}
|
||||
},
|
||||
Plugin::Load(id) => {
|
||||
allow_err!(super::config::ManagerConfig::set_uninstall(&id, false));
|
||||
allow_err!(super::load_plugin(&id));
|
||||
}
|
||||
Plugin::Reload(id) => {
|
||||
allow_err!(super::reload_plugin(&id));
|
||||
}
|
||||
Plugin::Uninstall(id) => {
|
||||
super::manager::uninstall_plugin(&id, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,14 @@ use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
const MSG_TO_UI_PLUGIN_MANAGER_LIST: &str = "plugin_list";
|
||||
const MSG_TO_UI_PLUGIN_MANAGER_UPDATE: &str = "plugin_update";
|
||||
const MSG_TO_UI_PLUGIN_MANAGER_INSTALL: &str = "plugin_install";
|
||||
const MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL: &str = "plugin_uninstall";
|
||||
|
||||
const IPC_PLUGIN_POSTFIX: &str = "_plugin";
|
||||
|
||||
@ -40,7 +42,6 @@ pub struct PluginInfo {
|
||||
pub source: PluginSource,
|
||||
pub meta: PluginMeta,
|
||||
pub installed_version: String,
|
||||
pub install_time: String,
|
||||
pub invalid_reason: String,
|
||||
}
|
||||
|
||||
@ -78,7 +79,6 @@ fn get_source_plugins() -> HashMap<String, PluginInfo> {
|
||||
source: source.clone(),
|
||||
meta: meta.clone(),
|
||||
installed_version: "".to_string(),
|
||||
install_time: "".to_string(),
|
||||
invalid_reason: "".to_string(),
|
||||
},
|
||||
);
|
||||
@ -110,9 +110,18 @@ fn send_plugin_list_event(plugins: &HashMap<String, PluginInfo>) {
|
||||
pub fn load_plugin_list() {
|
||||
let mut plugin_info_lock = PLUGIN_INFO.lock().unwrap();
|
||||
let mut plugins = get_source_plugins();
|
||||
for (id, info) in super::plugins::get_plugin_infos().read().unwrap().iter() {
|
||||
|
||||
// A big read lock is needed to prevent race conditions.
|
||||
// Loading plugin list may be slow.
|
||||
// Users may call uninstall plugin in the middle.
|
||||
let plugin_infos = super::plugins::get_plugin_infos();
|
||||
let plugin_infos_read_lock = plugin_infos.read().unwrap();
|
||||
for (id, info) in plugin_infos_read_lock.iter() {
|
||||
if info.uninstalled {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(p) = plugins.get_mut(id) {
|
||||
p.install_time = info.install_time.clone();
|
||||
p.installed_version = info.desc.meta().version.clone();
|
||||
p.invalid_reason = "".to_string();
|
||||
} else {
|
||||
@ -126,7 +135,6 @@ pub fn load_plugin_list() {
|
||||
},
|
||||
meta: info.desc.meta().clone(),
|
||||
installed_version: info.desc.meta().version.clone(),
|
||||
install_time: info.install_time.clone(),
|
||||
invalid_reason: "".to_string(),
|
||||
},
|
||||
);
|
||||
@ -139,14 +147,34 @@ pub fn load_plugin_list() {
|
||||
pub fn install_plugin(id: &str) -> ResultType<()> {
|
||||
match PLUGIN_INFO.lock().unwrap().get(id) {
|
||||
Some(plugin) => {
|
||||
let _plugin_url = format!(
|
||||
"{}/plugins/{}/{}_{}.zip",
|
||||
plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version
|
||||
);
|
||||
// to-do: Support args with space in quotes. 'arg 1' and "arg 2"
|
||||
#[cfg(windows)]
|
||||
let _res =
|
||||
crate::platform::elevate(&format!("--plugin-install {} {}", id, _plugin_url))?;
|
||||
{
|
||||
let mut same_plugin_exists = false;
|
||||
if let Some(version) = super::plugins::get_version(id) {
|
||||
if version == plugin.meta.version {
|
||||
same_plugin_exists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// to-do: Support args with space in quotes. 'arg 1' and "arg 2"
|
||||
let plugin_url = format!(
|
||||
"{}/plugins/{}/{}_{}.zip",
|
||||
plugin.source.url, plugin.meta.id, plugin.meta.id, plugin.meta.version
|
||||
);
|
||||
let args = if same_plugin_exists {
|
||||
format!("--plugin-install {}", id)
|
||||
} else {
|
||||
format!("--plugin-install {} {}", id, plugin_url)
|
||||
};
|
||||
let allowed = crate::platform::elevate(&args)?;
|
||||
|
||||
if allowed && same_plugin_exists {
|
||||
super::ipc::load_plugin(id)?;
|
||||
super::plugins::load_plugin(id)?;
|
||||
super::plugins::mark_uninstalled(id, false);
|
||||
push_install_event(id, "finished");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
@ -155,26 +183,93 @@ pub fn install_plugin(id: &str) -> ResultType<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn remove_plugins() {}
|
||||
|
||||
// 1. Add to uninstall list.
|
||||
// 2. Try remove.
|
||||
// 2. Remove on the next start.
|
||||
pub fn uninstall_plugin(id: &str) {
|
||||
// to-do: add to uninstall list.
|
||||
super::plugins::unload_plugin(id);
|
||||
fn get_uninstalled_plugins() -> ResultType<Vec<String>> {
|
||||
let plugins_dir = super::get_plugins_dir()?;
|
||||
let mut plugins = Vec::new();
|
||||
if plugins_dir.exists() {
|
||||
for entry in std::fs::read_dir(plugins_dir)? {
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
let plugin_dir = entry.path();
|
||||
if plugin_dir.is_dir() {
|
||||
if let Some(id) = plugin_dir.file_name().and_then(|n| n.to_str()) {
|
||||
if super::config::ManagerConfig::is_uninstalled(id) {
|
||||
plugins.push(id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read plugins dir entry, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
fn push_install_event(id: &str, msg: &str) {
|
||||
pub(super) fn remove_plugins() -> ResultType<()> {
|
||||
for id in get_uninstalled_plugins()?.iter() {
|
||||
super::config::remove(id as _);
|
||||
if let Ok(dir) = super::get_plugin_dir(id as _) {
|
||||
allow_err!(fs::remove_dir_all(dir));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uninstall_plugin(id: &str, called_by_ui: bool) {
|
||||
if called_by_ui {
|
||||
match crate::platform::elevate(&format!("--plugin-uninstall {}", id)) {
|
||||
Ok(true) => {
|
||||
if let Err(e) = super::ipc::uninstall_plugin(id) {
|
||||
log::error!("Failed to uninstall plugin '{}': {}", id, e);
|
||||
push_uninstall_event(id, "failed");
|
||||
return;
|
||||
}
|
||||
super::plugins::unload_plugin(id);
|
||||
super::plugins::mark_uninstalled(id, true);
|
||||
super::config::remove(id);
|
||||
push_uninstall_event(id, "");
|
||||
}
|
||||
Ok(false) => {
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to uninstall plugin '{}': {}", id, e);
|
||||
push_uninstall_event(id, "failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_server() {
|
||||
super::plugins::unload_plugin(&id);
|
||||
// allow_err is Ok here.
|
||||
allow_err!(super::config::ManagerConfig::set_uninstall(&id, true));
|
||||
}
|
||||
}
|
||||
|
||||
fn push_event(id: &str, r#type: &str, msg: &str) {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("name", MSG_TO_UI_TYPE_PLUGIN_MANAGER);
|
||||
m.insert("id", id);
|
||||
m.insert(MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg);
|
||||
m.insert(r#type, msg);
|
||||
if let Ok(event) = serde_json::to_string(&m) {
|
||||
let _res = flutter::push_global_event(flutter::APP_TYPE_MAIN, event.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_uninstall_event(id: &str, msg: &str) {
|
||||
push_event(id, MSG_TO_UI_PLUGIN_MANAGER_UNINSTALL, msg);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_install_event(id: &str, msg: &str) {
|
||||
push_event(id, MSG_TO_UI_PLUGIN_MANAGER_INSTALL, msg);
|
||||
}
|
||||
|
||||
async fn handle_conn(mut stream: crate::ipc::Connection) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
@ -197,7 +292,7 @@ async fn handle_conn(mut stream: crate::ipc::Connection) {
|
||||
InstallStatus::Finished => {
|
||||
allow_err!(super::plugins::load_plugin(&id));
|
||||
allow_err!(super::ipc::load_plugin_async(id).await);
|
||||
load_plugin_list();
|
||||
std::thread::spawn(load_plugin_list);
|
||||
push_install_event(&id, "finished");
|
||||
}
|
||||
InstallStatus::FailedCreating => {
|
||||
@ -329,7 +424,7 @@ pub(super) mod install {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn install_plugin(id: &str, url: &str) {
|
||||
pub fn install_plugin_with_url(id: &str, url: &str) {
|
||||
let plugin_dir = match super::super::get_plugin_dir(id) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
|
@ -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]
|
||||
|
@ -3,7 +3,7 @@ use super::{desc::Desc, errno::*, *};
|
||||
use crate::common::is_server;
|
||||
use crate::flutter;
|
||||
use hbb_common::{
|
||||
allow_err, bail,
|
||||
bail,
|
||||
dlopen::symbor::Library,
|
||||
lazy_static, log,
|
||||
message_proto::{Message, Misc, PluginFailure, PluginRequest},
|
||||
@ -15,7 +15,6 @@ use std::{
|
||||
ffi::{c_char, c_void},
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
const METHOD_HANDLE_UI: &[u8; 10] = b"handle_ui\0";
|
||||
@ -29,7 +28,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
pub(super) struct PluginInfo {
|
||||
pub path: String,
|
||||
pub install_time: String,
|
||||
pub uninstalled: bool,
|
||||
pub desc: Desc,
|
||||
}
|
||||
|
||||
@ -307,7 +306,7 @@ fn load_plugin_dir(dir: &PathBuf) {
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to read '{}' dir entry, {}",
|
||||
dir.file_stem().and_then(|f| f.to_str()).unwrap_or(""),
|
||||
dir.file_name().and_then(|f| f.to_str()).unwrap_or(""),
|
||||
e
|
||||
);
|
||||
}
|
||||
@ -316,20 +315,18 @@ fn load_plugin_dir(dir: &PathBuf) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unload_plugins() {
|
||||
log::info!("Plugins unloaded");
|
||||
PLUGINS.write().unwrap().clear();
|
||||
if change_manager() {
|
||||
super::config::ManagerConfig::remove_plugins(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unload_plugin(id: &str) {
|
||||
log::info!("Plugin {} unloaded", id);
|
||||
PLUGINS.write().unwrap().remove(id);
|
||||
if change_manager() {
|
||||
allow_err!(super::config::ManagerConfig::remove_plugin(id, false));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_uninstalled(id: &str, uninstalled: bool) {
|
||||
log::info!("Plugin {} uninstall", id);
|
||||
PLUGIN_INFO
|
||||
.write()
|
||||
.unwrap()
|
||||
.get_mut(id)
|
||||
.map(|info| info.uninstalled = uninstalled);
|
||||
}
|
||||
|
||||
pub fn reload_plugin(id: &str) -> ResultType<()> {
|
||||
@ -364,7 +361,7 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
};
|
||||
plugin.init(&init_data, path)?;
|
||||
|
||||
if change_manager() {
|
||||
if is_server() {
|
||||
super::config::ManagerConfig::add_plugin(&desc.meta().id)?;
|
||||
}
|
||||
|
||||
@ -372,18 +369,11 @@ fn load_plugin_path(path: &str) -> ResultType<()> {
|
||||
// Ui may be not ready now, so we need to update again once ui is ready.
|
||||
reload_ui(&desc, None);
|
||||
|
||||
let install_time = PathBuf::from(path)
|
||||
.metadata()
|
||||
.and_then(|d| d.created())
|
||||
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||
let install_time = chrono::DateTime::<chrono::Local>::from(install_time)
|
||||
.format("%Y-%m-%d %H:%M:%S")
|
||||
.to_string();
|
||||
// add plugins
|
||||
let id = desc.meta().id.clone();
|
||||
let plugin_info = PluginInfo {
|
||||
path: path.to_string(),
|
||||
install_time,
|
||||
uninstalled: false,
|
||||
desc,
|
||||
};
|
||||
PLUGIN_INFO.write().unwrap().insert(id.clone(), plugin_info);
|
||||
@ -582,15 +572,6 @@ fn make_plugin_failure(id: &str, name: &str, msg: &str) -> Message {
|
||||
msg_out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn change_manager() -> bool {
|
||||
#[cfg(debug_assertions)]
|
||||
let change_manager = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let change_manager = is_server();
|
||||
change_manager
|
||||
}
|
||||
|
||||
fn reload_ui(desc: &Desc, sync_to: Option<&str>) {
|
||||
for (location, ui) in desc.location().ui.iter() {
|
||||
if let Ok(ui) = serde_json::to_string(&ui) {
|
||||
@ -641,3 +622,11 @@ pub(super) fn get_desc_conf(id: &str) -> Option<super::desc::Config> {
|
||||
.get(id)
|
||||
.map(|info| info.desc.config().clone())
|
||||
}
|
||||
|
||||
pub(super) fn get_version(id: &str) -> Option<String> {
|
||||
PLUGIN_INFO
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(id)
|
||||
.map(|info| info.desc.meta().version.clone())
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user