Private
Public Access
1
0

修复: OSS收藏打开+连接指示器根目录+字段映射+侧边栏重构+文件监听+首屏优化

This commit is contained in:
2026-05-16 17:55:59 +08:00
parent 316e517989
commit d17c20c579
37 changed files with 1667 additions and 1566 deletions

View 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()
}