修复: OSS收藏打开+连接指示器根目录+字段映射+侧边栏重构+文件监听+首屏优化
This commit is contained in:
138
internal/filewatch/watcher.go
Normal file
138
internal/filewatch/watcher.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user