收口本地工作区清理与资源投影
清理历史 Electron、Graphify、沙箱和截图等仓库跟踪残留,补充 CodeGraph 与 Convex active deploy source 协作说明。 新增 tree-first 下一阶段设计稿和 2026-05-20 清理总结,记录本地工作区、路径身份和 Zed/Lapce/VSCode 参考收口方向。 扩展 Rust Web 本地文件夹、DocumentBuffer、mindmap 资源、tree runtime 和页面聚合链路,并补充 task455 local-folder mindmap clean smoke。 验证:git diff --check 通过;pnpm store status --store-dir .pnpm-store 通过;npm ls --depth=0 --json 通过;find -L node_modules 未发现断链。cargo test -p mnote-web 当前 418 passed / 35 failed。
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::document_buffer_store::BufferStore;
|
||||
use crate::routes::{local_workspace_id_from_root_uri, refresh_local_search_index_for_path};
|
||||
use notify::event::ModifyKind;
|
||||
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
@@ -12,6 +13,7 @@ use tokio::sync::{broadcast, mpsc, oneshot};
|
||||
#[derive(Clone)]
|
||||
pub struct LocalFolderWatcherRegistry {
|
||||
inner: Arc<LocalFolderWatcherRegistryInner>,
|
||||
buffer_store: BufferStore,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for LocalFolderWatcherRegistry {
|
||||
@@ -23,11 +25,12 @@ impl std::fmt::Debug for LocalFolderWatcherRegistry {
|
||||
}
|
||||
|
||||
impl LocalFolderWatcherRegistry {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(buffer_store: BufferStore) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(LocalFolderWatcherRegistryInner {
|
||||
entries: Mutex::new(HashMap::new()),
|
||||
}),
|
||||
buffer_store,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +39,10 @@ impl LocalFolderWatcherRegistry {
|
||||
canonical_root: &Path,
|
||||
) -> Result<LocalFolderWatcherSubscription, String> {
|
||||
let key = canonical_root_uri(canonical_root);
|
||||
let channel = self.inner.get_or_create_channel(&key, canonical_root)?;
|
||||
let buffer_store = self.buffer_store.clone();
|
||||
let channel = self
|
||||
.inner
|
||||
.get_or_create_channel(&key, canonical_root, buffer_store)?;
|
||||
channel.subscriber_count.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(LocalFolderWatcherSubscription {
|
||||
receiver: channel.sender.subscribe(),
|
||||
@@ -63,6 +69,7 @@ impl LocalFolderWatcherRegistryInner {
|
||||
&self,
|
||||
key: &str,
|
||||
canonical_root: &Path,
|
||||
buffer_store: BufferStore,
|
||||
) -> Result<Arc<LocalFolderWatchChannel>, String> {
|
||||
if let Some(existing) = self
|
||||
.entries
|
||||
@@ -76,7 +83,7 @@ impl LocalFolderWatcherRegistryInner {
|
||||
|
||||
let channel = Arc::new(LocalFolderWatchChannel::new(
|
||||
key.to_string(),
|
||||
spawn_local_folder_watcher(key, canonical_root.to_path_buf())?,
|
||||
spawn_local_folder_watcher(key, canonical_root.to_path_buf(), buffer_store)?,
|
||||
));
|
||||
|
||||
let mut entries = self.entries.lock().expect("registry lock");
|
||||
@@ -157,6 +164,7 @@ impl Drop for LocalFolderWatcherSubscriptionGuard {
|
||||
fn spawn_local_folder_watcher(
|
||||
root_uri: &str,
|
||||
canonical_root: PathBuf,
|
||||
buffer_store: BufferStore,
|
||||
) -> Result<(broadcast::Sender<Value>, oneshot::Sender<()>), String> {
|
||||
let (event_sender, mut event_receiver) = mpsc::unbounded_channel::<notify::Result<Event>>();
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
@@ -174,6 +182,7 @@ fn spawn_local_folder_watcher(
|
||||
let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>();
|
||||
let sender_for_task = sender.clone();
|
||||
let root_uri_for_task = root_uri.to_string();
|
||||
let buffer_store_for_task = buffer_store.clone();
|
||||
tokio::spawn(async move {
|
||||
let _watcher = watcher;
|
||||
loop {
|
||||
@@ -206,11 +215,29 @@ fn spawn_local_folder_watcher(
|
||||
if !is_markdown_path(&path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 外部文件变更→更新 BufferStore
|
||||
let document_id =
|
||||
format!("local-md:{}", encode_local_id_segment(&relative_path));
|
||||
if let Ok(ws_id) =
|
||||
local_workspace_id_from_root_uri(&root_uri_for_task)
|
||||
{
|
||||
let ws_path =
|
||||
crate::document_buffer_store::build_local_folder_workspace_path(
|
||||
&ws_id,
|
||||
&root_uri_for_task,
|
||||
&relative_path,
|
||||
&document_id,
|
||||
);
|
||||
buffer_store_for_task
|
||||
.mark_external_modified(&ws_path, Some("external-editor".into()));
|
||||
}
|
||||
|
||||
let payload = json!({
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": root_uri_for_task,
|
||||
"relativePath": relative_path,
|
||||
"documentId": format!("local-md:{}", encode_local_id_segment(&relative_path)),
|
||||
"documentId": document_id,
|
||||
"eventKind": format!("{:?}", event.kind),
|
||||
"revision": event_revision(&path),
|
||||
});
|
||||
@@ -319,6 +346,7 @@ mod tests {
|
||||
is_local_search_index_path, refresh_local_search_index_for_event, should_emit_event_kind,
|
||||
LocalFolderWatcherRegistry,
|
||||
};
|
||||
use crate::document_buffer_store::BufferStore;
|
||||
use notify::event::{AccessKind, CreateKind, DataChange, ModifyKind};
|
||||
use notify::EventKind;
|
||||
|
||||
@@ -337,7 +365,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn same_root_subscribers_share_single_watcher() {
|
||||
let registry = LocalFolderWatcherRegistry::new();
|
||||
let registry = LocalFolderWatcherRegistry::new(BufferStore::new());
|
||||
let root = test_root("shared");
|
||||
|
||||
let first = registry.subscribe(&root).expect("first subscription");
|
||||
@@ -358,7 +386,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn different_roots_create_independent_watchers() {
|
||||
let registry = LocalFolderWatcherRegistry::new();
|
||||
let registry = LocalFolderWatcherRegistry::new(BufferStore::new());
|
||||
let first_root = test_root("first");
|
||||
let second_root = test_root("second");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user