refactor DirectoryWatcher to Watcher
This commit is contained in:
parent
8f74566e96
commit
c53c54a5d9
236
watchers/watcher.go
Normal file
236
watchers/watcher.go
Normal file
@ -0,0 +1,236 @@
|
||||
package watchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"Moonshark/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDebounceTime = 300 * time.Millisecond
|
||||
defaultPollInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
type FileChange struct {
|
||||
Path string
|
||||
IsNew bool
|
||||
IsDeleted bool
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
// Watcher is now self-contained and manages its own polling
|
||||
type Watcher struct {
|
||||
dir string
|
||||
files map[string]FileInfo
|
||||
filesMu sync.RWMutex
|
||||
callback func([]FileChange) error
|
||||
debounceTime time.Duration
|
||||
pollInterval time.Duration
|
||||
recursive bool
|
||||
|
||||
// Self-management
|
||||
done chan struct{}
|
||||
debounceTimer *time.Timer
|
||||
debouncing bool
|
||||
debounceMu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
type WatcherConfig struct {
|
||||
Dir string
|
||||
Callback func([]FileChange) error
|
||||
DebounceTime time.Duration
|
||||
PollInterval time.Duration
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func NewWatcher(config WatcherConfig) (*Watcher, error) {
|
||||
if config.DebounceTime == 0 {
|
||||
config.DebounceTime = defaultDebounceTime
|
||||
}
|
||||
if config.PollInterval == 0 {
|
||||
config.PollInterval = defaultPollInterval
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
dir: config.Dir,
|
||||
files: make(map[string]FileInfo),
|
||||
callback: config.Callback,
|
||||
debounceTime: config.DebounceTime,
|
||||
pollInterval: config.PollInterval,
|
||||
recursive: config.Recursive,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
if err := w.scanDirectory(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.wg.Add(1)
|
||||
go w.watchLoop()
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *Watcher) Close() {
|
||||
close(w.done)
|
||||
w.wg.Wait()
|
||||
|
||||
w.debounceMu.Lock()
|
||||
if w.debounceTimer != nil {
|
||||
w.debounceTimer.Stop()
|
||||
}
|
||||
w.debounceMu.Unlock()
|
||||
}
|
||||
|
||||
func (w *Watcher) GetDir() string {
|
||||
return w.dir
|
||||
}
|
||||
|
||||
// watchLoop is the main polling loop for this watcher
|
||||
func (w *Watcher) watchLoop() {
|
||||
defer w.wg.Done()
|
||||
ticker := time.NewTicker(w.pollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
w.checkAndNotify()
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkAndNotify combines change detection and notification
|
||||
func (w *Watcher) checkAndNotify() {
|
||||
changed, changedFiles := w.detectChanges()
|
||||
if changed {
|
||||
w.notifyChange(changedFiles)
|
||||
}
|
||||
}
|
||||
|
||||
// detectChanges scans directory and returns changes
|
||||
func (w *Watcher) detectChanges() (bool, []FileChange) {
|
||||
newFiles := make(map[string]FileInfo)
|
||||
var changedFiles []FileChange
|
||||
changed := false
|
||||
|
||||
err := filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !w.recursive && info.IsDir() && path != w.dir {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
currentInfo := FileInfo{ModTime: info.ModTime()}
|
||||
newFiles[path] = currentInfo
|
||||
|
||||
w.filesMu.RLock()
|
||||
prevInfo, exists := w.files[path]
|
||||
w.filesMu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
changed = true
|
||||
changedFiles = append(changedFiles, FileChange{Path: path, IsNew: true})
|
||||
w.logDebug("File added: %s", path)
|
||||
} else if currentInfo.ModTime != prevInfo.ModTime {
|
||||
changed = true
|
||||
changedFiles = append(changedFiles, FileChange{Path: path})
|
||||
w.logDebug("File changed: %s", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
w.logError("Error scanning directory: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check for deletions
|
||||
w.filesMu.RLock()
|
||||
for path := range w.files {
|
||||
if _, exists := newFiles[path]; !exists {
|
||||
changed = true
|
||||
changedFiles = append(changedFiles, FileChange{Path: path, IsDeleted: true})
|
||||
w.logDebug("File deleted: %s", path)
|
||||
}
|
||||
}
|
||||
w.filesMu.RUnlock()
|
||||
|
||||
if changed {
|
||||
w.filesMu.Lock()
|
||||
w.files = newFiles
|
||||
w.filesMu.Unlock()
|
||||
}
|
||||
|
||||
return changed, changedFiles
|
||||
}
|
||||
|
||||
func (w *Watcher) scanDirectory() error {
|
||||
w.filesMu.Lock()
|
||||
defer w.filesMu.Unlock()
|
||||
|
||||
w.files = make(map[string]FileInfo)
|
||||
|
||||
return filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !w.recursive && info.IsDir() && path != w.dir {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
w.files[path] = FileInfo{ModTime: info.ModTime()}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Watcher) notifyChange(changedFiles []FileChange) {
|
||||
w.debounceMu.Lock()
|
||||
defer w.debounceMu.Unlock()
|
||||
|
||||
if w.debouncing && w.debounceTimer != nil {
|
||||
w.debounceTimer.Stop()
|
||||
}
|
||||
w.debouncing = true
|
||||
|
||||
// Copy to avoid race conditions
|
||||
filesCopy := make([]FileChange, len(changedFiles))
|
||||
copy(filesCopy, changedFiles)
|
||||
|
||||
w.debounceTimer = time.AfterFunc(w.debounceTime, func() {
|
||||
var err error
|
||||
if w.callback != nil {
|
||||
err = w.callback(filesCopy)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
w.logError("Callback error: %v", err)
|
||||
}
|
||||
|
||||
w.debounceMu.Lock()
|
||||
w.debouncing = false
|
||||
w.debounceMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Watcher) logDebug(format string, args ...any) {
|
||||
logger.Debugf("[Watcher] [%s] %s", w.dir, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (w *Watcher) logError(format string, args ...any) {
|
||||
logger.Errorf("[Watcher] [%s] %s", w.dir, fmt.Sprintf(format, args...))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user