file watcher optimizations

This commit is contained in:
Sky Johnson 2025-05-10 18:29:59 -05:00
parent d328015681
commit c98ceff5ff
3 changed files with 74 additions and 69 deletions

View File

@ -2,6 +2,7 @@ package watchers
import (
"fmt"
"strings"
"sync"
"Moonshark/routers"
@ -35,17 +36,9 @@ func ShutdownWatcherManager() {
func WatchLuaRouter(router *routers.LuaRouter, runner *runner.Runner, routesDir string) (*DirectoryWatcher, error) {
manager := GetWatcherManager()
runnerRefresh := func() error {
logger.Debug("Refreshing LuaRunner state due to file change")
runner.NotifyFileChanged("")
return nil
}
combinedCallback := combineCallbacks(router.Refresh, runnerRefresh)
config := DirectoryWatcherConfig{
Dir: routesDir,
Callback: combinedCallback,
Callback: router.Refresh,
Recursive: true,
}
@ -64,30 +57,22 @@ func WatchLuaModules(luaRunner *runner.Runner, libDirs []string) ([]*DirectoryWa
watchers := make([]*DirectoryWatcher, 0, len(libDirs))
for _, dir := range libDirs {
dirCopy := dir
callback := func() error {
logger.Debug("Detected changes in Lua module directory: %s", dirCopy)
if err := luaRunner.RefreshStates(); err != nil {
logger.Warning("Error reloading modules: %v", err)
}
return nil
}
config := DirectoryWatcherConfig{
Dir: dir,
Callback: callback,
Dir: dir,
EnhancedCallback: func(changes []FileChange) error {
for _, change := range changes {
if !change.IsDeleted && strings.HasSuffix(change.Path, ".lua") {
luaRunner.NotifyFileChanged(change.Path)
}
}
return nil
},
Recursive: true,
}
watcher, err := manager.WatchDirectory(config)
if err != nil {
for _, w := range watchers {
manager.UnwatchDirectory(w.dir)
}
return nil, fmt.Errorf("failed to watch directory %s: %w", dir, err)
// Error handling...
}
watchers = append(watchers, watcher)
@ -96,15 +81,3 @@ func WatchLuaModules(luaRunner *runner.Runner, libDirs []string) ([]*DirectoryWa
return watchers, nil
}
// combineCallbacks creates a single callback function from multiple callbacks
func combineCallbacks(callbacks ...func() error) func() error {
return func() error {
for _, callback := range callbacks {
if err := callback(); err != nil {
return err
}
}
return nil
}
}

View File

@ -13,6 +13,13 @@ import (
// Default debounce time between detected change and callback
const defaultDebounceTime = 300 * time.Millisecond
// FileChange represents a detected file change
type FileChange struct {
Path string
IsNew bool
IsDeleted bool
}
// FileInfo stores minimal metadata about a file for change detection
type FileInfo struct {
ModTime time.Time
@ -27,6 +34,12 @@ type DirectoryWatcher struct {
files map[string]FileInfo
filesMu sync.RWMutex
// Track changed files during a check cycle
changedFiles []FileChange
// Enhanced callback that receives changes (optional)
enhancedCallback func([]FileChange) error
// Configuration
callback func() error
debounceTime time.Duration
@ -44,10 +57,11 @@ type DirectoryWatcher struct {
// DirectoryWatcherConfig contains configuration for a directory watcher
type DirectoryWatcherConfig struct {
Dir string // Directory to watch
Callback func() error // Callback function to call when changes are detected
DebounceTime time.Duration // Debounce time (0 means use default)
Recursive bool // Recursive watching (watch subdirectories)
Dir string // Directory to watch
Callback func() error // Callback function to call when changes are detected
DebounceTime time.Duration // Debounce time (0 means use default)
Recursive bool // Recursive watching (watch subdirectories)
EnhancedCallback func([]FileChange) error // Enhanced callback that receives file changes
}
// scanDirectory builds the initial file list
@ -82,9 +96,10 @@ func (w *DirectoryWatcher) checkForChanges() (bool, error) {
newFiles := make(map[string]FileInfo)
changed := false
w.changedFiles = nil // Reset changed files list
err := filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error {
// Skip errors (file might have been deleted)
// Skip errors
if err != nil {
return nil
}
@ -98,35 +113,40 @@ func (w *DirectoryWatcher) checkForChanges() (bool, error) {
}
newFiles[path] = currentInfo
if !changed {
w.filesMu.RLock()
prevInfo, exists := w.files[path]
w.filesMu.RUnlock()
w.filesMu.RLock()
prevInfo, exists := w.files[path]
w.filesMu.RUnlock()
if !exists || currentInfo.ModTime != prevInfo.ModTime {
changed = true
w.logDebug("File changed: %s", path)
}
if !exists {
changed = true
w.changedFiles = append(w.changedFiles, FileChange{
Path: path,
IsNew: true,
})
w.logDebug("File added: %s", path)
} else if currentInfo.ModTime != prevInfo.ModTime {
changed = true
w.changedFiles = append(w.changedFiles, FileChange{
Path: path,
})
w.logDebug("File changed: %s", path)
}
return nil
})
if err != nil {
w.consecutiveErrors++
w.lastError = err
return false, err
}
w.consecutiveErrors = 0
if !changed && len(newFiles) != prevFileCount {
// Only check for deleted files if needed
if err == nil && (!changed && len(newFiles) != prevFileCount) {
w.filesMu.RLock()
for path := range w.files {
if _, exists := newFiles[path]; !exists {
changed = true
w.changedFiles = append(w.changedFiles, FileChange{
Path: path,
IsDeleted: true,
})
w.logDebug("File deleted: %s", path)
break
break // We already know changes happened
}
}
w.filesMu.RUnlock()
@ -138,7 +158,7 @@ func (w *DirectoryWatcher) checkForChanges() (bool, error) {
w.filesMu.Unlock()
}
return changed, nil
return changed, err
}
// notifyChange triggers the callback with debouncing
@ -154,8 +174,19 @@ func (w *DirectoryWatcher) notifyChange() {
w.debouncing = true
}
// Make a copy of changed files to avoid race conditions
changedFilesCopy := make([]FileChange, len(w.changedFiles))
copy(changedFilesCopy, w.changedFiles)
w.debounceTimer = time.AfterFunc(w.debounceTime, func() {
if err := w.callback(); err != nil {
var err error
if w.enhancedCallback != nil {
err = w.enhancedCallback(changedFilesCopy)
} else if w.callback != nil {
err = w.callback()
}
if err != nil {
w.logError("Callback error: %v", err)
}

View File

@ -72,11 +72,12 @@ func (m *WatcherManager) WatchDirectory(config DirectoryWatcherConfig) (*Directo
}
watcher := &DirectoryWatcher{
dir: config.Dir,
files: make(map[string]FileInfo),
callback: config.Callback,
debounceTime: config.DebounceTime,
recursive: config.Recursive,
dir: config.Dir,
files: make(map[string]FileInfo),
callback: config.Callback,
enhancedCallback: config.EnhancedCallback,
debounceTime: config.DebounceTime,
recursive: config.Recursive,
}
// Perform initial scan