diff --git a/core/watchers/watcher.go b/core/watchers/watcher.go deleted file mode 100644 index 206c02f..0000000 --- a/core/watchers/watcher.go +++ /dev/null @@ -1,255 +0,0 @@ -package watchers - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "git.sharkk.net/Sky/Moonshark/core/logger" - "git.sharkk.net/Sky/Moonshark/core/runner" -) - -const ( - defaultPollInterval = 1 * time.Second - defaultDebounceTime = 300 * time.Millisecond -) - -// FileInfo stores metadata about a file for change detection -type FileInfo struct { - ModTime time.Time - Size int64 - IsDir bool -} - -// FileWatcher watches a single directory for changes -type FileWatcher struct { - dir string - callback func() error - log *logger.Logger - files map[string]FileInfo - done chan struct{} - ticker *time.Ticker - debounceTime time.Duration - debouncing bool - debounceTimer *time.Timer - debounceMu sync.Mutex - filesMu sync.RWMutex - wg sync.WaitGroup -} - -// Registry for Lua runners to notify about .lua file changes -var ( - luaRunners = make([]*runner.LuaRunner, 0) - runnersMutex sync.RWMutex -) - -// NewFileWatcher creates a new file watcher for a directory -func NewFileWatcher(dir string, callback func() error, log *logger.Logger) (*FileWatcher, error) { - w := &FileWatcher{ - dir: dir, - callback: callback, - log: log, - files: make(map[string]FileInfo), - done: make(chan struct{}), - debounceTime: defaultDebounceTime, - } - - // Perform initial scan - if err := w.scanDirectory(); err != nil { - return nil, err - } - - // Start the watcher - w.ticker = time.NewTicker(defaultPollInterval) - w.wg.Add(1) - go w.watchLoop() - - log.Info("Started watching directory: %s", dir) - return w, nil -} - -// scanDirectory builds the initial file list -func (w *FileWatcher) scanDirectory() error { - w.filesMu.Lock() - defer w.filesMu.Unlock() - - // Clear existing files - w.files = make(map[string]FileInfo) - - return filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - w.log.Warning("[Watcher] [%s] Error accessing path %s: %v", w.dir, path, err) - return nil // Continue with other files - } - - w.files[path] = FileInfo{ - ModTime: info.ModTime(), - Size: info.Size(), - IsDir: info.IsDir(), - } - - return nil - }) -} - -// watchLoop is the polling loop that checks for changes -func (w *FileWatcher) watchLoop() { - defer w.wg.Done() - - w.log.Debug("[Watcher] [%s] Starting watch loop", w.dir) - - for { - select { - case <-w.ticker.C: - changedFiles, err := w.checkForChanges() - if err != nil { - w.log.Error("[Watcher] [%s] Error checking for changes: %v", w.dir, err) - continue - } - - if len(changedFiles) > 0 { - w.log.Debug("[Watcher] [%s] Detected %d changed files", w.dir, len(changedFiles)) - - // Notify Lua runners about .lua file changes - for _, file := range changedFiles { - if strings.HasSuffix(file, ".lua") { - w.notifyLuaRunners(file) - } - } - - // Trigger the callback with debouncing - w.triggerCallback() - } - - case <-w.done: - w.log.Debug("[Watcher] [%s] Watch loop stopped", w.dir) - return - } - } -} - -// checkForChanges detects file changes and returns a list of changed files -func (w *FileWatcher) checkForChanges() ([]string, error) { - changedFiles := []string{} - - // Get current state - currentFiles := make(map[string]FileInfo) - - err := filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - // File might have been deleted between directory read and stat - return nil - } - - currentFiles[path] = FileInfo{ - ModTime: info.ModTime(), - Size: info.Size(), - IsDir: info.IsDir(), - } - - return nil - }) - - if err != nil { - return changedFiles, fmt.Errorf("error walking directory: %w", err) - } - - // Compare with previous state - w.filesMu.RLock() - previousFiles := w.files - w.filesMu.RUnlock() - - // Check for new or modified files - for path, currentInfo := range currentFiles { - prevInfo, exists := previousFiles[path] - - if !exists { - // New file - w.log.Debug("[Watcher] [%s] New file: %s", w.dir, path) - changedFiles = append(changedFiles, path) - } else if currentInfo.ModTime != prevInfo.ModTime || currentInfo.Size != prevInfo.Size { - // Modified file - w.log.Debug("[Watcher] [%s] Modified file: %s", w.dir, path) - changedFiles = append(changedFiles, path) - } - } - - // Check for deleted files - for path, info := range previousFiles { - if _, exists := currentFiles[path]; !exists { - // File deleted - w.log.Debug("[Watcher] [%s] Deleted file: %s", w.dir, path) - if !info.IsDir { // Only add files, not directories - changedFiles = append(changedFiles, path) - } - } - } - - // Update internal file list if changes were found - if len(changedFiles) > 0 { - w.filesMu.Lock() - w.files = currentFiles - w.filesMu.Unlock() - } - - return changedFiles, nil -} - -// triggerCallback executes the callback with debouncing -func (w *FileWatcher) triggerCallback() { - w.debounceMu.Lock() - defer w.debounceMu.Unlock() - - w.log.Debug("[Watcher] [%s] Preparing to trigger callback", w.dir) - - if w.debouncing { - // Reset timer if already debouncing - if w.debounceTimer != nil { - w.log.Debug("[Watcher] [%s] Resetting existing debounce timer", w.dir) - w.debounceTimer.Stop() - } - } else { - w.log.Debug("[Watcher] [%s] Starting debounce timer (%v)", w.dir, w.debounceTime) - w.debouncing = true - } - - w.debounceTimer = time.AfterFunc(w.debounceTime, func() { - w.log.Debug("[Watcher] [%s] Executing callback", w.dir) - - if err := w.callback(); err != nil { - w.log.Error("[Watcher] [%s] Callback error: %v", w.dir, err) - } else { - w.log.Debug("[Watcher] [%s] Callback executed successfully", w.dir) - } - - // Reset debouncing state - w.debounceMu.Lock() - w.debouncing = false - w.debounceMu.Unlock() - }) -} - -// Close stops the file watcher -func (w *FileWatcher) Close() error { - w.log.Debug("[Watcher] [%s] Stopping watcher", w.dir) - close(w.done) - if w.ticker != nil { - w.ticker.Stop() - } - w.wg.Wait() - return nil -} - -// notifyLuaRunners notifies all registered Lua runners about a .lua file change -func (w *FileWatcher) notifyLuaRunners(file string) { - runnersMutex.RLock() - defer runnersMutex.RUnlock() - - for _, r := range luaRunners { - w.log.Debug("[Watcher] [%s] Notifying Lua runner about change to %s", w.dir, file) - r.NotifyFileChanged(file) - } -}