diff --git a/watchers/api.go b/watchers/api.go index 19ff086..26252f0 100644 --- a/watchers/api.go +++ b/watchers/api.go @@ -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 - } -} diff --git a/watchers/dir.go b/watchers/dir.go index 182d6b5..e4f2d96 100644 --- a/watchers/dir.go +++ b/watchers/dir.go @@ -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) } diff --git a/watchers/manager.go b/watchers/manager.go index 635008c..919d524 100644 --- a/watchers/manager.go +++ b/watchers/manager.go @@ -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