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 }