refactor: 合并为单 crate,前端改为内嵌 React 控制台
合并 stash-cli / stash-core 为根目录单 crate(lib + bin),新增 React + Vite + TS 控制台替换 Askama 模板。 build.rs 用 walkdir 把 stash-web/dist 同步到 $OUT_DIR/web,由 include_dir! 编译期内嵌,产出单文件二进制。 路由改为 / 与 /assets/** 走内嵌前端,其余交给 ServeDir,缺失路径返回内置 404。同步更新依赖、.gitignore 与 README。
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
use crate::{AppProject, error::StashError, scan_projects};
|
||||
use notify::{Event, EventKind, RecursiveMode, Watcher};
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::warn;
|
||||
|
||||
const EVENT_DEBOUNCE: Duration = Duration::from_millis(150);
|
||||
|
||||
pub fn start(root: PathBuf, updates: broadcast::Sender<Vec<AppProject>>) -> Result<(), StashError> {
|
||||
let mut previous_projects = scan_projects(&root);
|
||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel();
|
||||
let mut watcher = notify::recommended_watcher(move |result| {
|
||||
let _ = event_tx.send(result);
|
||||
})?;
|
||||
watcher.watch(&root, RecursiveMode::Recursive)?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _watcher = watcher;
|
||||
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
if !should_rescan(&event) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Editors and build tools often emit a burst of file events for one change.
|
||||
tokio::time::sleep(EVENT_DEBOUNCE).await;
|
||||
while event_rx.try_recv().is_ok() {}
|
||||
|
||||
let projects = scan_projects(&root);
|
||||
if projects == previous_projects {
|
||||
continue;
|
||||
}
|
||||
|
||||
previous_projects = projects.clone();
|
||||
let _ = updates.send(projects);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_rescan(event: ¬ify::Result<Event>) -> bool {
|
||||
match event {
|
||||
Ok(event) => !matches!(event.kind, EventKind::Access(_)),
|
||||
Err(error) => {
|
||||
warn!("文件监听事件出错,将重新扫描项目: {error}");
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use notify::{
|
||||
Event,
|
||||
event::{AccessKind, CreateKind, EventKind},
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct TestDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl TestDir {
|
||||
fn new() -> Self {
|
||||
let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
|
||||
let path = std::env::temp_dir()
|
||||
.join(format!("stash-watcher-test-{}-{id}", std::process::id()));
|
||||
fs::create_dir_all(&path).expect("应能创建测试目录");
|
||||
Self { path }
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_events_do_not_trigger_project_scans() {
|
||||
let access = Event::new(EventKind::Access(AccessKind::Read));
|
||||
let create = Event::new(EventKind::Create(CreateKind::Folder));
|
||||
|
||||
assert!(!should_rescan(&Ok(access)));
|
||||
assert!(should_rescan(&Ok(create)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn broadcasts_when_a_project_is_added() {
|
||||
let root = TestDir::new();
|
||||
let (updates, _) = broadcast::channel(8);
|
||||
let mut receiver = updates.subscribe();
|
||||
|
||||
start(root.path().to_path_buf(), updates).expect("应能启动文件监听");
|
||||
|
||||
let project = root.path().join("new-project");
|
||||
fs::create_dir_all(&project).expect("应能创建项目目录");
|
||||
fs::write(project.join("index.html"), "<!doctype html>").expect("应能创建入口文件");
|
||||
|
||||
let projects = tokio::time::timeout(Duration::from_secs(5), receiver.recv())
|
||||
.await
|
||||
.expect("应在超时前收到项目更新")
|
||||
.expect("项目更新通道不应关闭");
|
||||
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].name, "new-project");
|
||||
assert_eq!(projects[0].route_path, "/new-project/");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user