remove watcher

This commit is contained in:
Sky Johnson 2025-03-27 07:14:01 -05:00
parent e00a5eb17a
commit 50e92ccf04

View File

@ -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)
}
}