198 lines
3.6 KiB
Go
198 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type FileWatcher struct {
|
|
files map[string]time.Time
|
|
dirs map[string]bool // Track watched directories
|
|
mu sync.RWMutex
|
|
restartCh chan bool
|
|
stopCh chan bool
|
|
debounceMs int
|
|
lastEvent time.Time
|
|
pollMs int
|
|
}
|
|
|
|
func NewFileWatcher(debounceMs int) (*FileWatcher, error) {
|
|
return &FileWatcher{
|
|
files: make(map[string]time.Time),
|
|
dirs: make(map[string]bool),
|
|
restartCh: make(chan bool, 1),
|
|
stopCh: make(chan bool, 1),
|
|
debounceMs: debounceMs,
|
|
pollMs: 250, // Poll every 250ms
|
|
}, nil
|
|
}
|
|
|
|
func (fw *FileWatcher) AddFile(path string) error {
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
info, err := os.Stat(absPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fw.mu.Lock()
|
|
fw.files[absPath] = info.ModTime()
|
|
fw.mu.Unlock()
|
|
|
|
fmt.Printf("Watching: %s\n", absPath)
|
|
return nil
|
|
}
|
|
|
|
func (fw *FileWatcher) AddDirectory(dir string) error {
|
|
absDir, err := filepath.Abs(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fw.mu.Lock()
|
|
fw.dirs[absDir] = true
|
|
fw.mu.Unlock()
|
|
|
|
return filepath.Walk(absDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !info.IsDir() && strings.HasSuffix(path, ".lua") {
|
|
return fw.AddFile(path)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (fw *FileWatcher) Start() <-chan bool {
|
|
go fw.pollLoop()
|
|
return fw.restartCh
|
|
}
|
|
|
|
func (fw *FileWatcher) pollLoop() {
|
|
ticker := time.NewTicker(time.Duration(fw.pollMs) * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-fw.stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
fw.checkFiles()
|
|
fw.scanForNewFiles()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (fw *FileWatcher) checkFiles() {
|
|
fw.mu.RLock()
|
|
files := make(map[string]time.Time, len(fw.files))
|
|
maps.Copy(files, fw.files)
|
|
fw.mu.RUnlock()
|
|
|
|
changed := false
|
|
for path, lastMod := range files {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if info.ModTime().After(lastMod) {
|
|
fw.mu.Lock()
|
|
fw.files[path] = info.ModTime()
|
|
fw.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
if now.Sub(fw.lastEvent) > time.Duration(fw.debounceMs)*time.Millisecond {
|
|
fw.lastEvent = now
|
|
// log.Printf("File changed: %s", path)
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if changed {
|
|
select {
|
|
case fw.restartCh <- true:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (fw *FileWatcher) scanForNewFiles() {
|
|
fw.mu.RLock()
|
|
dirs := make(map[string]bool, len(fw.dirs))
|
|
maps.Copy(dirs, fw.dirs)
|
|
existingFiles := make(map[string]bool, len(fw.files))
|
|
for path := range fw.files {
|
|
existingFiles[path] = true
|
|
}
|
|
fw.mu.RUnlock()
|
|
|
|
newFilesFound := false
|
|
for dir := range dirs {
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !info.IsDir() && strings.HasSuffix(path, ".lua") {
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !existingFiles[absPath] {
|
|
fw.mu.Lock()
|
|
fw.files[absPath] = info.ModTime()
|
|
fw.mu.Unlock()
|
|
|
|
fmt.Printf("New file detected: %s\n", absPath)
|
|
newFilesFound = true
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if newFilesFound {
|
|
now := time.Now()
|
|
if now.Sub(fw.lastEvent) > time.Duration(fw.debounceMs)*time.Millisecond {
|
|
fw.lastEvent = now
|
|
select {
|
|
case fw.restartCh <- true:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (fw *FileWatcher) Close() error {
|
|
select {
|
|
case fw.stopCh <- true:
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (fw *FileWatcher) DiscoverRequiredFiles(scriptPath string) error {
|
|
if err := fw.AddFile(scriptPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
scriptDir := filepath.Dir(scriptPath)
|
|
return fw.AddDirectory(scriptDir)
|
|
}
|