github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/cache/watcher.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"path/filepath"
     7  	"time"
     8  
     9  	"github.com/fsnotify/fsnotify"
    10  
    11  	"github.com/datawire/dlib/dlog"
    12  	"github.com/telepresenceio/telepresence/v2/pkg/dos"
    13  	"github.com/telepresenceio/telepresence/v2/pkg/filelocation"
    14  )
    15  
    16  // WatchUserCache uses a file system watcher that receives events when the file changes
    17  // and calls the given function when that happens.
    18  func WatchUserCache(ctx context.Context, subdir string, onChange func(context.Context) error, files ...string) error {
    19  	dir := filepath.Join(filelocation.AppUserCacheDir(ctx), subdir)
    20  
    21  	// Ensure that the user cache directory exists.
    22  	if err := dos.MkdirAll(ctx, dir, 0o755); err != nil {
    23  		return err
    24  	}
    25  	watcher, err := fsnotify.NewWatcher()
    26  	if err != nil {
    27  		return err
    28  	}
    29  	defer watcher.Close()
    30  
    31  	// The directory containing the files must be watched because editing a
    32  	// file will typically end with renaming the original and then creating
    33  	// a new file. A watcher that follows the inode will not see when the new
    34  	// file is created.
    35  	if err = watcher.Add(dir); err != nil {
    36  		return err
    37  	}
    38  
    39  	// The delay timer will initially sleep forever. It's reset to a very short
    40  	// delay when the file is modified.
    41  	delay := time.AfterFunc(time.Duration(math.MaxInt64), func() {
    42  		if err := onChange(ctx); err != nil {
    43  			dlog.Error(ctx, err)
    44  		}
    45  	})
    46  	defer delay.Stop()
    47  
    48  	for i := range files {
    49  		files[i] = filepath.Join(dir, files[i])
    50  	}
    51  	isOfInterest := func(s string) bool {
    52  		for _, file := range files {
    53  			if s == file {
    54  				return true
    55  			}
    56  		}
    57  		return false
    58  	}
    59  	for {
    60  		select {
    61  		case <-ctx.Done():
    62  			return nil
    63  		case err = <-watcher.Errors:
    64  			dlog.Error(ctx, err)
    65  		case event := <-watcher.Events:
    66  			if event.Op&(fsnotify.Remove|fsnotify.Write|fsnotify.Create) != 0 && isOfInterest(event.Name) {
    67  				// The file was created, modified, or removed. Let's defer the call to onChange just
    68  				// a little bit in case there are more modifications to it.
    69  				delay.Reset(5 * time.Millisecond)
    70  			}
    71  		}
    72  	}
    73  }