Files
MarkLite/src/main/file-watcher.ts
T
thzxx 7f070eb11d chore: bump version to 0.3.8
Code audit fixes:
- CRITICAL: reorder Markdown pipeline (fixImages before sanitize)
- CRITICAL: fix path prefix separator check
- BLOCKING: remove duplicate useEffect in Editor
- BLOCKING: skip onChange when content unchanged
- BLOCKING: optimize Sidebar re-render with useMemo
- HIGH: cleanup FileWatcher polling intervals
- HIGH: improve validatePath segment check
- HIGH: fix isExternalUpdate race with counter
- HIGH: add will-navigate / setWindowOpenHandler
- HIGH: explicit strip in sanitize schema
- MEDIUM: random temp file suffix instead of Date.now()
- MEDIUM/LOW: add IndexedDB error boundaries
- LOW: support UTF-16 BOM detection
2026-06-18 21:28:35 +08:00

130 lines
3.4 KiB
TypeScript

import * as fs from 'fs'
import { BrowserWindow } from 'electron'
export class FileWatcher {
private watcher: fs.FSWatcher | null = null
private currentPath: string | null = null
private isSelfWriting = false
private pollTimer: ReturnType<typeof setInterval> | null = null
private pollTimeout: ReturnType<typeof setTimeout> | null = null
constructor(private getMainWindow: () => BrowserWindow | null) {}
start(filePath: string): void {
this.stop()
if (!filePath) return
try {
this.currentPath = filePath
this.watcher = fs.watch(filePath, (eventType) => {
if (eventType === 'change') {
if (this.isSelfWriting) return
const win = this.getMainWindow()
if (win && !win.isDestroyed()) {
win.webContents.send('file:externallyModified', filePath)
}
}
})
// L-02: 监听 error 事件,文件被删除时显式关闭并清理状态
this.watcher.on('error', () => {
const originalPath = this.currentPath
this.stop()
if (originalPath) {
this.pollTimer = setInterval(() => {
try {
if (fs.existsSync(originalPath)) {
this.clearPollTimers()
this.start(originalPath)
}
} catch {
this.clearPollTimers()
}
}, 2000)
// 最多轮询30秒
this.pollTimeout = setTimeout(() => this.clearPollTimers(), 30000)
}
})
} catch {
// silently ignore
}
}
stop(): void {
this.clearPollTimers()
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
this.currentPath = null
}
private clearPollTimers(): void {
if (this.pollTimer) {
clearInterval(this.pollTimer)
this.pollTimer = null
}
if (this.pollTimeout) {
clearTimeout(this.pollTimeout)
this.pollTimeout = null
}
}
getCurrentPath(): string | null {
return this.currentPath
}
setSelfWriting(value: boolean): void {
this.isSelfWriting = value
}
}
export class SidebarWatcher {
private watcher: fs.FSWatcher | null = null
private watchPath: string | null = null
private refreshTimer: NodeJS.Timeout | null = null
constructor(private getMainWindow: () => BrowserWindow | null) {}
start(dirPath: string): void {
this.stop()
if (!dirPath) return
try {
this.watchPath = dirPath
this.watcher = fs.watch(dirPath, { recursive: true }, () => {
const win = this.getMainWindow()
if (win && !win.isDestroyed()) {
if (this.refreshTimer) clearTimeout(this.refreshTimer)
this.refreshTimer = setTimeout(() => {
// M-03: 延迟后再次检查窗口状态
const w = this.getMainWindow()
if (w && !w.isDestroyed()) {
w.webContents.send('sidebar:dirChanged')
}
}, 300)
}
})
// L-02: 监听 error 事件,显式关闭
this.watcher.on('error', () => {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
this.watchPath = null
})
} catch {
// silently ignore
}
}
stop(): void {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
if (this.refreshTimer) {
clearTimeout(this.refreshTimer)
this.refreshTimer = null
}
this.watchPath = null
}
}