# 文件系统监视

<cite>
**本文引用的文件**
- [electron/main.ts](file://electron/main.ts)
- [electron/preload.ts](file://electron/preload.ts)
- [src/components/layout/right-sidebar.tsx](file://src/components/layout/right-sidebar.tsx)
- [src/types/electron.d.ts](file://src/types/electron.d.ts)
- [src/app/api/workspaces/[id]/tree/route.ts](file://src/app/api/workspaces/[id]/tree/route.ts)
- [src/lib/workspace-fs.ts](file://src/lib/workspace-fs.ts)
</cite>

## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)

## 简介
本文件系统监视功能围绕主进程的 ipcMain.handle('fs:watch') 接口展开，实现对指定目录的递归文件系统监控，并通过 IPC 将“文件变更”事件通知到渲染进程。渲染侧在右侧边栏中订阅该事件，采用二次防抖策略刷新文件树，确保界面与磁盘状态保持一致，同时避免频繁网络请求带来的性能损耗。

## 项目结构
- 主进程负责注册 IPC 处理器、启动文件系统监视器、发送变更通知。
- 预加载脚本将安全的 API 暴露给渲染进程，包括目录监视与变更回调。
- 渲染侧（右侧边栏）在挂载时调用监视接口，并监听变更事件后进行本地防抖刷新。
- 后端 API 提供文件树数据，用于在变更后拉取最新目录结构。

```mermaid
graph TB
subgraph "主进程"
M_main["electron/main.ts<br/>注册 fs:watch 处理器"]
end
subgraph "预加载"
M_preload["electron/preload.ts<br/>暴露 electronAPI"]
end
subgraph "渲染进程"
R_sidebar["src/components/layout/right-sidebar.tsx<br/>调用 watchDirectory/onFsChanged"]
end
subgraph "后端"
B_api["src/app/api/workspaces/[id]/tree/route.ts<br/>返回文件树"]
end
R_sidebar --> M_preload
M_preload --> M_main
M_main --> R_sidebar
R_sidebar --> B_api
```

**图表来源**
- [electron/main.ts:177-219](file://electron/main.ts#L177-L219)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/components/layout/right-sidebar.tsx:123-151](file://src/components/layout/right-sidebar.tsx#L123-L151)
- [src/app/api/workspaces/[id]/tree/route.ts:51-81](file://src/app/api/workspaces/[id]/tree/route.ts#L51-L81)

**章节来源**
- [electron/main.ts:177-219](file://electron/main.ts#L177-L219)
- [electron/preload.ts:5-23](file://electron/preload.ts#L5-L23)
- [src/components/layout/right-sidebar.tsx:123-151](file://src/components/layout/right-sidebar.tsx#L123-L151)
- [src/app/api/workspaces/[id]/tree/route.ts:51-81](file://src/app/api/workspaces/[id]/tree/route.ts#L51-L81)

## 核心组件
- 主进程文件系统监视器
  - 注册 IPC 处理器：ipcMain.handle('fs:watch', ...)，接收目录路径参数。
  - 创建 Node.js FSWatcher，启用递归监视。
  - 在每次文件事件触发时，先检查是否位于忽略目录集合内，再进行防抖处理，最终向渲染进程发送 'fs:changed' 通知。
  - 若发生异常，记录错误日志。
- 预加载脚本
  - 通过 contextBridge 暴露 electronAPI，包含 watchDirectory 和 onFsChanged。
- 渲染侧（右侧边栏）
  - 在组件挂载时根据当前模式选择监视路径并调用 watchDirectory。
  - 订阅 'fs:changed' 事件，使用本地防抖刷新文件树。
  - 组件卸载时清理回调与定时器，防止内存泄漏。

**章节来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)

## 架构总览
下图展示了从用户操作到文件树刷新的完整链路：渲染侧发起监视 → 主进程创建 FSWatcher → 过滤忽略目录 → 防抖合并事件 → 发送 IPC 通知 → 渲染侧刷新 UI。

```mermaid
sequenceDiagram
participant Renderer as "渲染进程<br/>右侧边栏"
participant Preload as "预加载<br/>electronAPI"
participant Main as "主进程<br/>fs : watch 处理器"
participant FS as "文件系统<br/>FSWatcher"
participant Backend as "后端<br/>/api/workspaces/ : id/tree"
Renderer->>Preload : 调用 watchDirectory(dirPath)
Preload->>Main : ipcRenderer.invoke('fs : watch', dirPath)
Main->>FS : watch(dirPath, { recursive : true }, handler)
FS-->>Main : 文件事件回调(filename)
Main->>Main : 忽略目录过滤(WATCH_IGNORED)
Main->>Main : 防抖(200ms)设置定时器
Main-->>Renderer : webContents.send('fs : changed')
Renderer->>Renderer : onFsChanged 回调触发
Renderer->>Backend : 拉取最新文件树
Backend-->>Renderer : 返回 tree 数据
Renderer-->>Renderer : 更新 UI
```

**图表来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)
- [src/app/api/workspaces/[id]/tree/route.ts:51-81](file://src/app/api/workspaces/[id]/tree/route.ts#L51-L81)

## 详细组件分析

### 主进程文件系统监视器
- 目录监视器创建
  - 使用 Node 的 fs.watch，传入 { recursive: true } 实现递归监视。
  - 每次收到新监视请求时，若存在旧监视器则先关闭并释放资源，避免重复监听导致的资源泄漏。
- 忽略目录集合与过滤逻辑
  - 定义忽略集合，包含常见的构建产物、版本控制目录、缓存目录等。
  - 对于每个文件事件，按路径分隔符拆分路径段，只要任一段命中忽略集合即跳过处理。
- 防抖机制（200ms 延迟）
  - 每当收到文件事件，先清除上一次定时器，再设置新的 200ms 定时器。
  - 定时器到期后向渲染进程发送 'fs:changed' 通知，从而将短时间内连续的事件合并为一次通知。
- 错误处理
  - 监视器创建失败或回调抛错时，记录错误日志，保证应用稳定性。
- 生命周期与资源清理
  - 应用退出前主动关闭当前监视器，确保进程退出时无残留句柄。
  - 渲染侧卸载时也需移除 IPC 监听与本地定时器，避免内存泄漏。

```mermaid
flowchart TD
Start(["事件入口"]) --> CheckPath["校验目录路径是否为空"]
CheckPath --> |为空| End(["结束"])
CheckPath --> |非空| CleanupOld["关闭旧监视器(如有)"]
CleanupOld --> CreateWatcher["创建 FSWatcher(递归)"]
CreateWatcher --> OnEvent["事件回调(filename)"]
OnEvent --> HasFilename{"是否存在文件名?"}
HasFilename --> |否| End
HasFilename --> |是| SplitParts["按路径分隔符拆分路径段"]
SplitParts --> CheckIgnore{"是否命中忽略集合?"}
CheckIgnore --> |是| End
CheckIgnore --> |否| Debounce["清除旧定时器并设置200ms定时器"]
Debounce --> SendIPC["发送 'fs:changed' 到渲染进程"]
SendIPC --> End
```

**图表来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/main.ts:34-35](file://electron/main.ts#L34-L35)

**章节来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/main.ts:34-35](file://electron/main.ts#L34-L35)
- [electron/main.ts:301-310](file://electron/main.ts#L301-L310)

### 预加载脚本与渲染侧集成
- 预加载脚本
  - 暴露 watchDirectory(dirPath) 与 onFsChanged(callback) 两个方法，分别用于启动监视与订阅变更事件。
  - onFsChanged 返回一个清理函数，用于移除监听器与取消定时器。
- 渲染侧（右侧边栏）
  - 根据当前模式选择监视路径（全局模式或项目模式），并在挂载时调用 watchDirectory。
  - 订阅 'fs:changed' 后，使用本地防抖（300ms）再次拉取文件树，避免 UI 频繁刷新。
  - 卸载时执行清理函数，确保监听器与定时器被正确移除。

```mermaid
sequenceDiagram
participant Sidebar as "右侧边栏"
participant Preload as "预加载"
participant Main as "主进程"
participant IPC as "IPC 通道"
Sidebar->>Preload : watchDirectory(watchPath)
Preload->>Main : invoke('fs : watch', watchPath)
Main-->>Preload : 监视器创建完成
Main-->>Sidebar : send('fs : changed')
Sidebar->>Sidebar : onFsChanged 回调
Sidebar->>Sidebar : 本地防抖(300ms)后刷新文件树
Sidebar->>Sidebar : 卸载时清理监听与定时器
```

**图表来源**
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)

**章节来源**
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)

### 后端文件树 API
- 在全局模式或项目模式下，后端根据工作区 ID 返回对应目录的文件树。
- 当项目目录不存在时，返回空树并标记 folderMissing，便于前端展示提示信息。
- 右侧边栏在收到变更通知后，会调用该 API 获取最新树结构并更新 UI。

**章节来源**
- [src/app/api/workspaces/[id]/tree/route.ts:51-81](file://src/app/api/workspaces/[id]/tree/route.ts#L51-L81)

## 依赖关系分析
- 主进程依赖 Node 内置 fs 模块创建 FSWatcher，并通过 BrowserWindow 的 webContents 发送 IPC 事件。
- 预加载脚本通过 contextBridge 将主进程能力暴露给渲染进程，形成安全的桥接层。
- 渲染侧依赖后端 API 获取文件树数据，实现 UI 与磁盘状态的同步。
- 工作区文件系统工具提供路径解析与默认目录结构保障，间接影响监视目标的选择与初始化。

```mermaid
graph LR
FS["Node fs.watch"] --> Main["主进程 fs:watch 处理器"]
Main --> IPC["IPC 'fs:changed'"]
IPC --> Renderer["渲染侧 onFsChanged"]
Renderer --> API["/api/workspaces/:id/tree"]
API --> Renderer
Utils["workspace-fs 工具"] --> Renderer
```

**图表来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)
- [src/app/api/workspaces/[id]/tree/route.ts:51-81](file://src/app/api/workspaces/[id]/tree/route.ts#L51-L81)
- [src/lib/workspace-fs.ts:32-53](file://src/lib/workspace-fs.ts#L32-L53)

**章节来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)
- [src/app/api/workspaces/[id]/tree/route.ts:51-81](file://src/app/api/workspaces/[id]/tree/route.ts#L51-L81)
- [src/lib/workspace-fs.ts:32-53](file://src/lib/workspace-fs.ts#L32-L53)

## 性能考量
- 递归监视的开销
  - 递归监视会对整个目录树产生事件，建议合理设置忽略目录集合，减少无效事件数量。
- 防抖策略
  - 主进程防抖（200ms）与渲染侧防抖（300ms）分层设计，降低 IPC 与网络请求频率，提升整体响应性。
- 资源管理
  - 每次切换监视目标时关闭旧监视器；应用退出时统一关闭；渲染侧卸载时清理监听与定时器，避免句柄泄露。
- I/O 与网络
  - 文件树刷新仅在变更后触发，且带本地防抖，避免高频请求后端 API。

[本节为通用性能讨论，无需特定文件来源]

## 故障排查指南
- 无法开始监视
  - 检查传入的目录路径是否有效；主进程会在路径为空时直接返回。
  - 查看主进程日志，确认监视器创建是否抛出异常。
- 事件未触发
  - 确认渲染侧已调用 watchDirectory 并成功订阅 onFsChanged。
  - 检查忽略目录集合是否误判了目标路径。
- UI 不刷新
  - 确认 'fs:changed' 事件到达后，渲染侧是否执行了本地防抖逻辑并调用后端 API。
  - 检查网络请求是否成功，以及返回的 tree 是否为空。
- 内存泄漏
  - 确保在组件卸载时调用清理函数，移除 IPC 监听并清除定时器。
  - 应用退出前检查主进程是否关闭了当前监视器。

**章节来源**
- [electron/main.ts:191-219](file://electron/main.ts#L191-L219)
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)

## 结论
该文件系统监视方案通过主进程的 FSWatcher 与渲染侧的事件订阅相结合，实现了对项目目录的实时感知。通过忽略目录集合与两级防抖（主进程 200ms + 渲染侧 300ms）有效降低了事件风暴与网络压力。配合完善的资源清理与错误处理，系统在可用性与性能之间取得了良好平衡。建议在实际使用中结合项目特性调整忽略集合，并在复杂场景下进一步细化事件过滤与批量刷新策略。

[本节为总结性内容，无需特定文件来源]

## 附录

### 使用示例与最佳实践
- 使用示例
  - 在渲染侧组件挂载时调用 watchDirectory 设置监视路径。
  - 订阅 onFsChanged，在回调中触发本地防抖刷新文件树。
  - 在组件卸载时执行清理函数，移除监听并清除定时器。
- 最佳实践
  - 明确区分全局模式与项目模式，分别设置不同的监视路径。
  - 根据项目类型维护忽略目录集合，避免无关事件干扰。
  - 控制防抖时间，兼顾响应速度与性能。
  - 在应用退出或窗口关闭时，确保主进程与渲染侧的资源清理流程执行。

**章节来源**
- [src/components/layout/right-sidebar.tsx:127-151](file://src/components/layout/right-sidebar.tsx#L127-L151)
- [electron/preload.ts:9-15](file://electron/preload.ts#L9-L15)
- [electron/main.ts:301-310](file://electron/main.ts#L301-L310)