Files
mediawatcher/internal/watcher/watcher.go
2025-12-08 07:56:46 -05:00

127 lines
2.7 KiB
Go

package watcher
import (
"log"
"os"
"path/filepath"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"mediawatcher/internal/config"
"mediawatcher/internal/util"
)
type FileHandler func(path string)
type Watcher struct {
cfg *config.Config
watcher *fsnotify.Watcher
pending map[string]struct{}
mu sync.Mutex
}
func New(cfg *config.Config) (*Watcher, error) {
w, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
return &Watcher{
cfg: cfg,
watcher: w,
pending: make(map[string]struct{}),
}, nil
}
func (w *Watcher) Close() error {
return w.watcher.Close()
}
func (w *Watcher) Start(handler FileHandler) error {
for _, dir := range w.cfg.Watch.Dirs {
if err := w.addDir(dir); err != nil {
return err
}
}
go w.loop(handler)
return nil
}
func (w *Watcher) addDir(dir string) error {
info, err := os.Stat(dir)
if err != nil {
return err
}
if !info.IsDir() {
return nil
}
log.Printf("[watch] watching %s", dir)
return w.watcher.Add(dir)
}
func (w *Watcher) loop(handler FileHandler) {
for {
select {
case event, ok := <-w.watcher.Events:
if !ok {
return
}
if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Rename) == 0 {
continue
}
path := event.Name
info, err := os.Stat(path)
if err != nil || info.IsDir() {
continue
}
extIncl := util.HasExt(path, w.cfg.Watch.IncludeExt)
extExcl := util.HasExt(path, w.cfg.Watch.ExcludeExt)
if !extIncl || extExcl {
continue
}
w.mu.Lock()
if _, exists := w.pending[path]; exists {
w.mu.Unlock()
continue
}
w.pending[path] = struct{}{}
w.mu.Unlock()
go w.handleFile(path, handler)
case err, ok := <-w.watcher.Errors:
if !ok {
return
}
log.Printf("[watch] error: %v", err)
}
}
}
func (w *Watcher) handleFile(path string, handler FileHandler) {
defer func() {
w.mu.Lock()
delete(w.pending, path)
w.mu.Unlock()
}()
stableFor := time.Duration(w.cfg.Watch.StableSeconds) * time.Second
if err := util.WaitForStable(path, stableFor, 2*time.Hour); err != nil {
log.Printf("[watch] file %s not stable: %v", path, err)
return
}
abs, err := filepath.Abs(path)
if err != nil {
abs = path
}
handler(abs)
}