router/pkg/watcher/watcher.go (59 lines of code) (raw):
package watcher
import (
"context"
"errors"
"os"
"time"
"go.uber.org/zap"
)
type Options struct {
Interval time.Duration
Logger *zap.Logger
Path string
Callback func()
}
func New(options Options) (func(ctx context.Context) error, error) {
if options.Interval <= 0 {
return nil, errors.New("interval must be greater than zero")
}
if options.Logger == nil {
return nil, errors.New("logger must be provided")
}
if options.Path == "" {
return nil, errors.New("path must be provided")
}
if options.Callback == nil {
return nil, errors.New("callback must be provided")
}
ll := options.Logger.With(zap.String("component", "file_watcher"), zap.String("path", options.Path))
return func(ctx context.Context) error {
ticker := time.NewTicker(options.Interval)
defer ticker.Stop()
var prevModTime time.Time
stat, err := os.Stat(options.Path)
if err != nil {
ll.Debug("Target file cannot be statted", zap.Error(err))
} else {
prevModTime = stat.ModTime()
}
ll.Debug("Watching file for changes", zap.Time("initial_mod_time", prevModTime))
for {
select {
case <-ticker.C:
stat, err := os.Stat(options.Path)
if err != nil {
ll.Debug("Target file cannot be statted", zap.Error(err))
// Reset the mod time so we catch any new file at the target path
prevModTime = time.Time{}
continue
}
ll.Debug("Checking file for changes", zap.Time("prev_mod_time", prevModTime), zap.Time("current_mod_time", stat.ModTime()))
if stat.ModTime().After(prevModTime) {
prevModTime = stat.ModTime()
options.Callback()
}
case <-ctx.Done():
return ctx.Err()
}
}
}, nil
}