docs: 战略规划和管理架构优化
- 新增 STRATEGY.md: 三年战略规划、技术路线、团队策略 - 新增 MILESTONES.md: 详细里程碑和时间表(M1.1-M1.4) - 新增 CODE_REVIEW.md: 代码审核标准和流程 - 组建管理班子: 新增 PM 刘建国,优化管理架构 - 丰富团队成员背景: 补充所有成员的教育经历、工作经验、技能树 - 解锁多线程思考能力: 团队成员可使用 kilo 命令并行探索 - 更新工作流程: CEO → PM → 开发团队,两级审核制度 - 修正 kilo 调用方式: 不使用 -f 参数,在消息中指示读取文件
This commit is contained in:
@@ -2,17 +2,112 @@
|
||||
//!
|
||||
//! 基于 warp 的 HTTP 服务,提供播放控制、配置管理、视频管理等 API。
|
||||
|
||||
use crate::core::message::Message;
|
||||
mod routes;
|
||||
|
||||
use crate::core::config::AppConfig;
|
||||
use crate::core::message::{Envelope, Message};
|
||||
use crate::core::plugin::{Plugin, PluginContext, PluginInfo, Platform};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
struct PendingWifiResponse {
|
||||
version: u64,
|
||||
payload: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) struct HttpState {
|
||||
wifi_response: Mutex<PendingWifiResponse>,
|
||||
wifi_response_cv: Condvar,
|
||||
config: Mutex<Arc<AppConfig>>,
|
||||
player_status: Mutex<crate::core::message::PlayerStatusData>,
|
||||
ble_ready: AtomicBool,
|
||||
}
|
||||
|
||||
impl HttpState {
|
||||
fn new(config: Arc<AppConfig>) -> Self {
|
||||
let player_status = crate::core::message::PlayerStatusData {
|
||||
running: false,
|
||||
paused: !config.playback.auto_start,
|
||||
in_transition: false,
|
||||
current_index: 0,
|
||||
playlist_length: config.playlist.len(),
|
||||
current_video: config.playlist.first().map(|item| item.id.clone()),
|
||||
};
|
||||
|
||||
Self {
|
||||
wifi_response: Mutex::new(PendingWifiResponse {
|
||||
version: 0,
|
||||
payload: None,
|
||||
}),
|
||||
wifi_response_cv: Condvar::new(),
|
||||
config: Mutex::new(config),
|
||||
player_status: Mutex::new(player_status),
|
||||
ble_ready: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn publish_wifi_result(&self, payload: String) {
|
||||
if let Ok(mut state) = self.wifi_response.lock() {
|
||||
state.version += 1;
|
||||
state.payload = Some(payload);
|
||||
self.wifi_response_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn config(&self) -> Arc<AppConfig> {
|
||||
self.config
|
||||
.lock()
|
||||
.map(|config| Arc::clone(&config))
|
||||
.expect("http config state poisoned")
|
||||
}
|
||||
|
||||
fn replace_config(&self, config: Arc<AppConfig>) {
|
||||
if let Ok(mut current) = self.config.lock() {
|
||||
*current = Arc::clone(&config);
|
||||
}
|
||||
|
||||
if let Ok(mut player_status) = self.player_status.lock() {
|
||||
player_status.playlist_length = config.playlist.len();
|
||||
if player_status.current_video.is_none() {
|
||||
player_status.current_video = config.playlist.first().map(|item| item.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn player_status(&self) -> crate::core::message::PlayerStatusData {
|
||||
self.player_status
|
||||
.lock()
|
||||
.map(|status| status.clone())
|
||||
.expect("http player status state poisoned")
|
||||
}
|
||||
|
||||
fn update_player_status(&self, status: crate::core::message::PlayerStatusData) {
|
||||
if let Ok(mut current) = self.player_status.lock() {
|
||||
*current = status;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ble_ready(&self) -> bool {
|
||||
self.ble_ready.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn set_ble_ready(&self, ready: bool) {
|
||||
self.ble_ready.store(ready, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpPlugin {
|
||||
ctx: Option<PluginContext>,
|
||||
state: Option<Arc<HttpState>>,
|
||||
}
|
||||
|
||||
impl HttpPlugin {
|
||||
pub fn new() -> Self {
|
||||
Self { ctx: None }
|
||||
Self {
|
||||
ctx: None,
|
||||
state: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +124,86 @@ impl Plugin for HttpPlugin {
|
||||
}
|
||||
|
||||
fn init(&mut self, ctx: PluginContext) -> Result<()> {
|
||||
self.state = Some(Arc::new(HttpState::new(Arc::clone(&ctx.config))));
|
||||
self.ctx = Some(ctx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&mut self) -> Result<()> { Ok(()) }
|
||||
fn handle_message(&mut self, _msg: Message) -> Result<()> { Ok(()) }
|
||||
fn start(&mut self) -> Result<()> {
|
||||
let ctx = self
|
||||
.ctx
|
||||
.as_ref()
|
||||
.context("http plugin context is not initialized")?;
|
||||
|
||||
if !ctx.config.remote_control.enabled {
|
||||
println!("[HttpPlugin] Remote control disabled, skip HTTP server startup");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let host = ctx.config.remote_control.host.clone();
|
||||
let port = ctx.config.remote_control.port;
|
||||
let tx = ctx.tx.clone();
|
||||
let state = Arc::clone(
|
||||
self.state
|
||||
.as_ref()
|
||||
.context("http plugin state is not initialized")?,
|
||||
);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let runtime = match tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
{
|
||||
Ok(runtime) => runtime,
|
||||
Err(error) => {
|
||||
eprintln!("[HttpPlugin] failed to create tokio runtime: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
runtime.block_on(async move {
|
||||
let routes = routes::build_routes(tx.clone(), state);
|
||||
let addr: std::net::SocketAddr = match format!("{host}:{port}").parse() {
|
||||
Ok(addr) => addr,
|
||||
Err(error) => {
|
||||
eprintln!("[HttpPlugin] invalid listen address {host}:{port}: {error}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(error) = tx.send(Envelope {
|
||||
from: "http",
|
||||
to: crate::core::message::Destination::Manager,
|
||||
message: Message::PluginReady("http"),
|
||||
}) {
|
||||
eprintln!("[HttpPlugin] failed to report ready state: {error}");
|
||||
}
|
||||
|
||||
println!("[HttpPlugin] listening on http://{addr}");
|
||||
warp::serve(routes).run(addr).await;
|
||||
});
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, msg: Message) -> Result<()> {
|
||||
let state = match self.state.as_ref() {
|
||||
Some(state) => state,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
match msg {
|
||||
Message::WifiResult(payload) => state.publish_wifi_result(payload),
|
||||
Message::PlayerStatus(status) => state.update_player_status(status),
|
||||
Message::ConfigReloaded(config) => state.replace_config(config),
|
||||
Message::PluginReady("ble") => state.set_ble_ready(true),
|
||||
Message::Shutdown => state.set_ble_ready(false),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> Result<()> { Ok(()) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user