feat: 实现动态插件系统 (6阶段完成)

- 阶段1: 消息类型序列化 (Serialize/Deserialize, &'static str → String)
- 阶段2: FFI 边界类型 + Plugin SDK (plugin_abi, showen-plugin-sdk crate)
- 阶段3: PluginLoader + DynamicPlugin (libloading 动态加载 .so)
- 阶段4: 版本管理 + 错误策略 (VersionManager, PluginState, 自动回退)
- 阶段5: 远程仓库客户端 (HTTP 下载 + tar.gz 安装)
- 阶段6: 示例插件 + HTTP 管理 API + 全目录 README 文档

54/54 测试通过,0 warnings。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
showen
2026-03-13 03:38:08 +08:00
parent 5dcc1ad98e
commit 7135f28545
62 changed files with 3501 additions and 299 deletions

View File

@@ -43,6 +43,8 @@ pub(crate) struct HttpState {
player_status: Mutex<crate::core::message::PlayerStatusData>,
ble_ready: AtomicBool,
ws_events: broadcast::Sender<String>,
/// 动态插件管理状态(由 Custom 消息更新)
plugin_states: Mutex<Vec<crate::core::service_manager::PluginStateInfo>>,
}
impl HttpState {
@@ -68,6 +70,7 @@ impl HttpState {
player_status: Mutex::new(player_status),
ble_ready: AtomicBool::new(false),
ws_events,
plugin_states: Mutex::new(Vec::new()),
}
}
@@ -179,6 +182,21 @@ impl HttpState {
self.publish_ws(payload);
}
}
pub(crate) fn plugin_states(&self) -> Vec<crate::core::service_manager::PluginStateInfo> {
self.plugin_states
.lock()
.map(|s| s.clone())
.unwrap_or_default()
}
fn update_plugin_states(&self, json: &str) {
if let Ok(states) = serde_json::from_str::<Vec<crate::core::service_manager::PluginStateInfo>>(json) {
if let Ok(mut current) = self.plugin_states.lock() {
*current = states;
}
}
}
}
pub struct HttpPlugin {
@@ -202,21 +220,21 @@ impl Default for HttpPlugin {
}
impl Plugin for HttpPlugin {
fn id(&self) -> &'static str {
fn id(&self) -> &str {
"http"
}
fn info(&self) -> PluginInfo {
PluginInfo {
name: "HTTP API",
version: "0.2.0",
description: "Web UI + REST API (warp)",
name: "HTTP API".to_string(),
version: "0.2.0".to_string(),
description: "Web UI + REST API (warp)".to_string(),
platform: Platform::Any,
}
}
fn dependencies(&self) -> Vec<&'static str> {
vec!["video"]
fn dependencies(&self) -> Vec<String> {
vec!["video".to_string()]
}
fn init(&mut self, ctx: PluginContext) -> Result<()> {
@@ -268,9 +286,9 @@ impl Plugin for HttpPlugin {
};
if let Err(error) = tx.send(Envelope {
from: "http",
from: "http".to_string(),
to: crate::core::message::Destination::Manager,
message: Message::PluginReady("http"),
message: Message::PluginReady("http".to_string()),
}) {
eprintln!("[HttpPlugin] failed to report ready state: {error}");
}
@@ -314,8 +332,11 @@ impl Plugin for HttpPlugin {
state.publish_ws(payload);
}
}
Message::PluginReady("ble") => state.set_ble_ready(true),
Message::PluginReady(ref id) if id == "ble" => state.set_ble_ready(true),
Message::Shutdown => state.set_ble_ready(false),
Message::Custom { ref kind, ref payload } if kind == "plugin_states" => {
state.update_plugin_states(payload);
}
_ => {}
}