Private
Public Access
1
0
Files
u-desk/internal/filewatch/watcher.go

139 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package filewatch
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
type Watcher struct {
watcher *fsnotify.Watcher
emitEvent func(name string, data ...any)
mu sync.Mutex
watched string // 当前监听的文件绝对路径
}
func NewWatcher(emitEvent func(name string, data ...any)) *Watcher {
return &Watcher{emitEvent: emitEvent}
}
// WatchFile 开始监听指定文件的变化。切换文件时自动取消旧监听。
func (w *Watcher) WatchFile(path string) error {
w.mu.Lock()
defer w.mu.Unlock()
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("解析路径失败: %w", err)
}
// 同一文件不重复监听
if absPath == w.watched {
return nil
}
// 停止旧监听
w.stopLocked()
// 检查文件是否存在
if _, err := os.Stat(absPath); err != nil {
return fmt.Errorf("文件不存在: %s", absPath)
}
// 创建 fsnotify watcher
fw, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("创建文件监听器失败: %w", err)
}
w.watcher = fw
// fsnotify 在某些系统上不支持直接监听文件,监听其所在目录
dir := filepath.Dir(absPath)
if err := fw.Add(dir); err != nil {
fw.Close()
return fmt.Errorf("监听目录失败: %w", err)
}
w.watched = absPath
// 后台消费事件
go w.consumeEvents()
return nil
}
// UnwatchFile 停止监听
func (w *Watcher) UnwatchFile() {
w.mu.Lock()
defer w.mu.Unlock()
w.stopLocked()
}
func (w *Watcher) stopLocked() {
if w.watcher != nil {
w.watcher.Close()
w.watcher = nil
}
w.watched = ""
}
func (w *Watcher) consumeEvents() {
debounceDelay := 300 * time.Millisecond
var debounceTimer *time.Timer
for {
select {
case event, ok := <-w.watcher.Events:
if !ok {
return
}
// 只处理目标文件的 Write/Create/Rename 事件
if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) == 0 {
continue
}
w.mu.Lock()
target := w.watched
w.mu.Unlock()
if target == "" {
return // 已停止监听
}
// 路径比较忽略大小写Windows
if !strings.EqualFold(event.Name, target) {
continue
}
// 防抖
if debounceTimer != nil {
debounceTimer.Stop()
}
debounceTimer = time.AfterFunc(debounceDelay, func() {
// 文件已不存在则跳过(如被删除)
if _, err := os.Stat(target); err != nil {
return
}
if w.emitEvent != nil {
w.emitEvent("file-changed", target)
}
})
case _, ok := <-w.watcher.Errors:
if !ok {
return
}
}
}
}
// Close 释放资源
func (w *Watcher) Close() {
w.UnwatchFile()
}