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 }