- 文件剪贴板复制剪切粘贴,重名自动副本 - SFTP下载到本机与外部文件拖入,目录递归 - 传输进度浮动面板,transfer-progress事件链 - Drawio预览,Excel工作线程,冻结看门狗 - cmd与bat自写batch语法高亮,psm1等映射补齐
143 lines
2.7 KiB
Go
143 lines
2.7 KiB
Go
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() {
|
|
// 文件已不存在则跳过(如被删除)
|
|
info, err := os.Stat(target)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if w.emitEvent != nil {
|
|
w.emitEvent("file-changed", map[string]any{
|
|
"path": target,
|
|
"mtime": info.ModTime().UnixMilli(),
|
|
})
|
|
}
|
|
})
|
|
|
|
case _, ok := <-w.watcher.Errors:
|
|
if !ok {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close 释放资源
|
|
func (w *Watcher) Close() {
|
|
w.UnwatchFile()
|
|
}
|