simplify file watchers
This commit is contained in:
parent
d86167a86e
commit
8f74566e96
@ -242,10 +242,10 @@ func initRouter() error {
|
|||||||
|
|
||||||
// Set up the file watchers.
|
// Set up the file watchers.
|
||||||
func setupWatchers() error {
|
func setupWatchers() error {
|
||||||
wmg = watchers.NewWatcherManager(watchers.DefaultPollInterval)
|
wmg = watchers.NewWatcherManager()
|
||||||
|
|
||||||
// Router watcher
|
// Router watcher
|
||||||
err := wmg.WatchDirectory(watchers.DirectoryWatcherConfig{
|
err := wmg.WatchDirectory(watchers.WatcherConfig{
|
||||||
Dir: cfg.Dirs.Routes,
|
Dir: cfg.Dirs.Routes,
|
||||||
Callback: rtr.Refresh,
|
Callback: rtr.Refresh,
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
@ -258,9 +258,9 @@ func setupWatchers() error {
|
|||||||
|
|
||||||
// Libs watchers
|
// Libs watchers
|
||||||
for _, dir := range cfg.Dirs.Libs {
|
for _, dir := range cfg.Dirs.Libs {
|
||||||
err := wmg.WatchDirectory(watchers.DirectoryWatcherConfig{
|
err := wmg.WatchDirectory(watchers.WatcherConfig{
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
EnhancedCallback: func(changes []watchers.FileChange) error {
|
Callback: func(changes []watchers.FileChange) error {
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
if !change.IsDeleted && strings.HasSuffix(change.Path, ".lua") {
|
if !change.IsDeleted && strings.HasSuffix(change.Path, ".lua") {
|
||||||
rnr.NotifyFileChanged(change.Path)
|
rnr.NotifyFileChanged(change.Path)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"Moonshark/watchers"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -457,7 +458,7 @@ func (r *Router) GetBytecode(scriptPath string) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Refresh rebuilds the router
|
// Refresh rebuilds the router
|
||||||
func (r *Router) Refresh() error {
|
func (r *Router) Refresh(changes []watchers.FileChange) error {
|
||||||
r.get = &node{}
|
r.get = &node{}
|
||||||
r.post = &node{}
|
r.post = &node{}
|
||||||
r.put = &node{}
|
r.put = &node{}
|
||||||
|
212
watchers/dir.go
212
watchers/dir.go
@ -1,212 +0,0 @@
|
|||||||
package watchers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"Moonshark/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirectoryWatcher watches a specific directory for changes
|
|
||||||
type DirectoryWatcher struct {
|
|
||||||
// Directory to watch
|
|
||||||
dir string
|
|
||||||
|
|
||||||
// Map of file paths to their metadata
|
|
||||||
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
|
|
||||||
recursive bool
|
|
||||||
|
|
||||||
// Debounce timer
|
|
||||||
debounceTimer *time.Timer
|
|
||||||
debouncing bool
|
|
||||||
debounceMu sync.Mutex
|
|
||||||
|
|
||||||
// Error tracking
|
|
||||||
consecutiveErrors int
|
|
||||||
lastError error
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
EnhancedCallback func([]FileChange) error // Enhanced callback that receives file changes
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanDirectory builds the initial file list
|
|
||||||
func (w *DirectoryWatcher) scanDirectory() error {
|
|
||||||
w.filesMu.Lock()
|
|
||||||
defer w.filesMu.Unlock()
|
|
||||||
|
|
||||||
w.files = make(map[string]FileInfo)
|
|
||||||
|
|
||||||
return filepath.Walk(w.dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return nil // Skip files with errors
|
|
||||||
}
|
|
||||||
|
|
||||||
if !w.recursive && info.IsDir() && path != w.dir {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
w.files[path] = FileInfo{
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkForChanges detects if any files have been added, modified, or deleted
|
|
||||||
func (w *DirectoryWatcher) checkForChanges() (bool, error) {
|
|
||||||
w.filesMu.RLock()
|
|
||||||
prevFileCount := len(w.files)
|
|
||||||
w.filesMu.RUnlock()
|
|
||||||
|
|
||||||
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
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !w.recursive && info.IsDir() && path != w.dir {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
currentInfo := FileInfo{
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
}
|
|
||||||
newFiles[path] = currentInfo
|
|
||||||
|
|
||||||
w.filesMu.RLock()
|
|
||||||
prevInfo, exists := w.files[path]
|
|
||||||
w.filesMu.RUnlock()
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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 // We already know changes happened
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.filesMu.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
w.filesMu.Lock()
|
|
||||||
w.files = newFiles
|
|
||||||
w.filesMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyChange triggers the callback with debouncing
|
|
||||||
func (w *DirectoryWatcher) notifyChange() {
|
|
||||||
w.debounceMu.Lock()
|
|
||||||
defer w.debounceMu.Unlock()
|
|
||||||
|
|
||||||
if w.debouncing {
|
|
||||||
if w.debounceTimer != nil {
|
|
||||||
w.debounceTimer.Stop()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.debounceMu.Lock()
|
|
||||||
w.debouncing = false
|
|
||||||
w.debounceMu.Unlock()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// logDebug logs a debug message with the watcher's directory prefix
|
|
||||||
func (w *DirectoryWatcher) logDebug(format string, args ...any) {
|
|
||||||
logger.Debugf("[Watcher] [%s] %s", w.dir, fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// logError logs an error message with the watcher's directory prefix
|
|
||||||
func (w *DirectoryWatcher) logError(format string, args ...any) {
|
|
||||||
logger.Errorf("[Watcher] [%s] %s", w.dir, fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDir gets the DirectoryWatcher's current directory
|
|
||||||
func (w *DirectoryWatcher) GetDir() string {
|
|
||||||
return w.dir
|
|
||||||
}
|
|
@ -3,62 +3,40 @@ package watchers
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"Moonshark/logger"
|
"Moonshark/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultPollInterval is the time between directory checks
|
|
||||||
const DefaultPollInterval = 1 * time.Second
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrDirectoryNotFound = errors.New("directory not found")
|
ErrDirectoryNotFound = errors.New("directory not found")
|
||||||
ErrAlreadyWatching = errors.New("already watching directory")
|
ErrAlreadyWatching = errors.New("already watching directory")
|
||||||
)
|
)
|
||||||
|
|
||||||
// WatcherManager coordinates file watching across multiple directories
|
// WatcherManager now just manages watcher lifecycle - no polling logic
|
||||||
type WatcherManager struct {
|
type WatcherManager struct {
|
||||||
watchers map[string]*DirectoryWatcher
|
watchers map[string]*Watcher
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
done chan struct{}
|
|
||||||
ticker *time.Ticker
|
|
||||||
interval time.Duration
|
|
||||||
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWatcherManager creates a new watcher manager with a specified poll interval
|
func NewWatcherManager() *WatcherManager {
|
||||||
func NewWatcherManager(pollInterval time.Duration) *WatcherManager {
|
return &WatcherManager{
|
||||||
if pollInterval <= 0 {
|
watchers: make(map[string]*Watcher),
|
||||||
pollInterval = DefaultPollInterval
|
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := &WatcherManager{
|
|
||||||
watchers: make(map[string]*DirectoryWatcher),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
interval: pollInterval,
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.ticker = time.NewTicker(pollInterval)
|
|
||||||
manager.wg.Add(1)
|
|
||||||
go manager.pollLoop()
|
|
||||||
|
|
||||||
return manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close stops all watchers and the manager
|
|
||||||
func (m *WatcherManager) Close() error {
|
func (m *WatcherManager) Close() error {
|
||||||
close(m.done)
|
m.mu.Lock()
|
||||||
if m.ticker != nil {
|
defer m.mu.Unlock()
|
||||||
m.ticker.Stop()
|
|
||||||
|
for _, watcher := range m.watchers {
|
||||||
|
watcher.Close()
|
||||||
}
|
}
|
||||||
m.wg.Wait()
|
|
||||||
|
m.watchers = make(map[string]*Watcher)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchDirectory adds a new directory to watch and returns the watcher
|
func (m *WatcherManager) WatchDirectory(config WatcherConfig) error {
|
||||||
func (m *WatcherManager) WatchDirectory(config DirectoryWatcherConfig) error {
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
@ -66,21 +44,8 @@ func (m *WatcherManager) WatchDirectory(config DirectoryWatcherConfig) error {
|
|||||||
return ErrAlreadyWatching
|
return ErrAlreadyWatching
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.DebounceTime == 0 {
|
watcher, err := NewWatcher(config)
|
||||||
config.DebounceTime = defaultDebounceTime
|
if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
watcher := &DirectoryWatcher{
|
|
||||||
dir: config.Dir,
|
|
||||||
files: make(map[string]FileInfo),
|
|
||||||
callback: config.Callback,
|
|
||||||
enhancedCallback: config.EnhancedCallback,
|
|
||||||
debounceTime: config.DebounceTime,
|
|
||||||
recursive: config.Recursive,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform initial scan
|
|
||||||
if err := watcher.scanDirectory(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,64 +55,38 @@ func (m *WatcherManager) WatchDirectory(config DirectoryWatcherConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwatchDirectory removes a directory from being watched
|
|
||||||
func (m *WatcherManager) UnwatchDirectory(dir string) error {
|
func (m *WatcherManager) UnwatchDirectory(dir string) error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
if _, exists := m.watchers[dir]; !exists {
|
watcher, exists := m.watchers[dir]
|
||||||
|
if !exists {
|
||||||
return ErrDirectoryNotFound
|
return ErrDirectoryNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watcher.Close()
|
||||||
delete(m.watchers, dir)
|
delete(m.watchers, dir)
|
||||||
logger.Debugf("WatcherManager removed watcher for %s", dir)
|
logger.Debugf("WatcherManager removed watcher for %s", dir)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pollLoop is the main polling loop that checks all watched directories
|
func (m *WatcherManager) GetWatcher(dir string) (*Watcher, bool) {
|
||||||
func (m *WatcherManager) pollLoop() {
|
|
||||||
defer m.wg.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-m.ticker.C:
|
|
||||||
m.checkAllDirectories()
|
|
||||||
case <-m.done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkAllDirectories polls all registered directories for changes
|
|
||||||
func (m *WatcherManager) checkAllDirectories() {
|
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
watchers := make([]*DirectoryWatcher, 0, len(m.watchers))
|
defer m.mu.RUnlock()
|
||||||
for _, w := range m.watchers {
|
|
||||||
watchers = append(watchers, w)
|
|
||||||
}
|
|
||||||
m.mu.RUnlock()
|
|
||||||
|
|
||||||
changesDetected := 0
|
watcher, exists := m.watchers[dir]
|
||||||
|
return watcher, exists
|
||||||
for _, watcher := range watchers {
|
}
|
||||||
if watcher.consecutiveErrors > 3 {
|
|
||||||
if watcher.consecutiveErrors == 4 {
|
func (m *WatcherManager) ListWatching() []string {
|
||||||
logger.Errorf("Temporarily skipping directory %s due to errors: %v",
|
m.mu.RLock()
|
||||||
watcher.dir, watcher.lastError)
|
defer m.mu.RUnlock()
|
||||||
watcher.consecutiveErrors++
|
|
||||||
}
|
dirs := make([]string, 0, len(m.watchers))
|
||||||
continue
|
for dir := range m.watchers {
|
||||||
}
|
dirs = append(dirs, dir)
|
||||||
|
}
|
||||||
changed, err := watcher.checkForChanges()
|
|
||||||
if err != nil {
|
return dirs
|
||||||
logger.Errorf("Error checking directory %s: %v", watcher.dir, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
changesDetected++
|
|
||||||
watcher.notifyChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user