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  }