remove old/new watcher implementation
This commit is contained in:
parent
9b1942903b
commit
e00a5eb17a
255
core/watchers/watcher.go
Normal file
255
core/watchers/watcher.go
Normal file
|
@ -0,0 +1,255 @@
|
|||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user