github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/filenotifywatcher/worker.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package filenotifywatcher 5 6 import ( 7 "github.com/juju/clock" 8 "github.com/juju/errors" 9 "github.com/juju/worker/v3" 10 "github.com/juju/worker/v3/catacomb" 11 ) 12 13 // FileNotifyWatcher represents a way to watch for changes in a namespace folder 14 // directory. 15 type FileNotifyWatcher interface { 16 // Changes returns a channel for the given file name that will contain 17 // if a file was created or deleted. 18 // TODO (stickupkid): We could further advance this to return a channel 19 // of ints that represents the number of changes we want to advance per 20 // step. 21 Changes(fileName string) (<-chan bool, error) 22 } 23 24 // WorkerConfig encapsulates the configuration options for the 25 // changestream worker. 26 type WorkerConfig struct { 27 Clock clock.Clock 28 Logger Logger 29 NewWatcher WatcherFn 30 NewINotifyWatcher func() (INotifyWatcher, error) 31 } 32 33 // Validate ensures that the config values are valid. 34 func (c *WorkerConfig) Validate() error { 35 if c.Clock == nil { 36 return errors.NotValidf("missing Clock") 37 } 38 if c.Logger == nil { 39 return errors.NotValidf("missing Logger") 40 } 41 if c.NewWatcher == nil { 42 return errors.NotValidf("missing NewWatcher") 43 } 44 45 return nil 46 } 47 48 type fileNotifyWorker struct { 49 cfg WorkerConfig 50 catacomb catacomb.Catacomb 51 runner *worker.Runner 52 } 53 54 func newWorker(cfg WorkerConfig) (*fileNotifyWorker, error) { 55 var err error 56 if err = cfg.Validate(); err != nil { 57 return nil, errors.Trace(err) 58 } 59 60 w := &fileNotifyWorker{ 61 cfg: cfg, 62 runner: worker.NewRunner(worker.RunnerParams{ 63 // Prevent the runner from restarting the worker, if one of the 64 // workers dies, we want to stop the whole thing. 65 IsFatal: func(err error) bool { return false }, 66 Clock: cfg.Clock, 67 }), 68 } 69 70 if err = catacomb.Invoke(catacomb.Plan{ 71 Site: &w.catacomb, 72 Work: w.loop, 73 Init: []worker.Worker{ 74 w.runner, 75 }, 76 }); err != nil { 77 return nil, errors.Trace(err) 78 } 79 80 return w, nil 81 } 82 83 func (w *fileNotifyWorker) loop() (err error) { 84 defer w.runner.Kill() 85 86 for { 87 select { 88 case <-w.catacomb.Dying(): 89 return w.catacomb.ErrDying() 90 } 91 } 92 } 93 94 // Kill is part of the worker.Worker interface. 95 func (w *fileNotifyWorker) Kill() { 96 w.catacomb.Kill(nil) 97 } 98 99 // Wait is part of the worker.Worker interface. 100 func (w *fileNotifyWorker) Wait() error { 101 return w.catacomb.Wait() 102 } 103 104 // Changes returns a channel containing all the change events for the given 105 // fileName. 106 func (w *fileNotifyWorker) Changes(fileName string) (<-chan bool, error) { 107 if fw, err := w.runner.Worker(fileName, w.catacomb.Dying()); err == nil { 108 return fw.(FileWatcher).Changes(), nil 109 } 110 111 watcher, err := w.cfg.NewWatcher(fileName, WithLogger(w.cfg.Logger), WithINotifyWatcherFn(w.cfg.NewINotifyWatcher)) 112 if err != nil { 113 return nil, errors.Trace(err) 114 } 115 116 if err := w.runner.StartWorker(fileName, func() (worker.Worker, error) { 117 return watcher, nil 118 }); err != nil { 119 return nil, errors.Annotatef(err, "starting worker for fileName %q", fileName) 120 } 121 122 return watcher.Changes(), nil 123 }