code.gitea.io/gitea@v1.19.3/modules/watcher/watcher.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package watcher
     5  
     6  import (
     7  	"context"
     8  	"io/fs"
     9  	"os"
    10  
    11  	"code.gitea.io/gitea/modules/log"
    12  	"code.gitea.io/gitea/modules/process"
    13  
    14  	"github.com/fsnotify/fsnotify"
    15  )
    16  
    17  // CreateWatcherOpts are options to configure the watcher
    18  type CreateWatcherOpts struct {
    19  	// PathsCallback is used to set the required paths to watch
    20  	PathsCallback func(func(path, name string, d fs.DirEntry, err error) error) error
    21  
    22  	// BeforeCallback is called before any files are watched
    23  	BeforeCallback func()
    24  
    25  	// Between Callback is called between after a watched event has occurred
    26  	BetweenCallback func()
    27  
    28  	// AfterCallback is called as this watcher ends
    29  	AfterCallback func()
    30  }
    31  
    32  // CreateWatcher creates a watcher labelled with the provided description and running with the provided options.
    33  // The created watcher will create a subcontext from the provided ctx and register it with the process manager.
    34  func CreateWatcher(ctx context.Context, desc string, opts *CreateWatcherOpts) {
    35  	go run(ctx, desc, opts)
    36  }
    37  
    38  func run(ctx context.Context, desc string, opts *CreateWatcherOpts) {
    39  	if opts.BeforeCallback != nil {
    40  		opts.BeforeCallback()
    41  	}
    42  	if opts.AfterCallback != nil {
    43  		defer opts.AfterCallback()
    44  	}
    45  	ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Watcher: "+desc, process.SystemProcessType, true)
    46  	defer finished()
    47  
    48  	log.Trace("Watcher loop starting for %s", desc)
    49  	defer log.Trace("Watcher loop ended for %s", desc)
    50  
    51  	watcher, err := fsnotify.NewWatcher()
    52  	if err != nil {
    53  		log.Error("Unable to create watcher for %s: %v", desc, err)
    54  		return
    55  	}
    56  	if err := opts.PathsCallback(func(path, _ string, d fs.DirEntry, err error) error {
    57  		if err != nil && !os.IsNotExist(err) {
    58  			return err
    59  		}
    60  		log.Trace("Watcher: %s watching %q", desc, path)
    61  		_ = watcher.Add(path)
    62  		return nil
    63  	}); err != nil {
    64  		log.Error("Unable to create watcher for %s: %v", desc, err)
    65  		_ = watcher.Close()
    66  		return
    67  	}
    68  
    69  	// Note we don't call the BetweenCallback here
    70  
    71  	for {
    72  		select {
    73  		case event, ok := <-watcher.Events:
    74  			if !ok {
    75  				_ = watcher.Close()
    76  				return
    77  			}
    78  			log.Debug("Watched file for %s had event: %v", desc, event)
    79  		case err, ok := <-watcher.Errors:
    80  			if !ok {
    81  				_ = watcher.Close()
    82  				return
    83  			}
    84  			log.Error("Error whilst watching files for %s: %v", desc, err)
    85  		case <-ctx.Done():
    86  			_ = watcher.Close()
    87  			return
    88  		}
    89  
    90  		// Recreate the watcher - only call the BetweenCallback after the new watcher is set-up
    91  		_ = watcher.Close()
    92  		watcher, err = fsnotify.NewWatcher()
    93  		if err != nil {
    94  			log.Error("Unable to create watcher for %s: %v", desc, err)
    95  			return
    96  		}
    97  		if err := opts.PathsCallback(func(path, _ string, _ fs.DirEntry, err error) error {
    98  			if err != nil {
    99  				return err
   100  			}
   101  			_ = watcher.Add(path)
   102  			return nil
   103  		}); err != nil {
   104  			log.Error("Unable to create watcher for %s: %v", desc, err)
   105  			_ = watcher.Close()
   106  			return
   107  		}
   108  
   109  		// Inform our BetweenCallback that there has been an event
   110  		if opts.BetweenCallback != nil {
   111  			opts.BetweenCallback()
   112  		}
   113  	}
   114  }