github.com/kaydxh/golang@v0.0.131/pkg/fsnotify/fsnotify.go (about) 1 package fsnotify 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sync" 9 "sync/atomic" 10 11 "github.com/fsnotify/fsnotify" 12 "github.com/kaydxh/golang/go/errors" 13 os_ "github.com/kaydxh/golang/go/os" 14 "github.com/sirupsen/logrus" 15 ) 16 17 type EventCallbackFunc func(ctx context.Context, path string) 18 19 type FsnotifyOptions struct { 20 CreateCallbackFunc EventCallbackFunc 21 WriteCallbackFunc EventCallbackFunc 22 RemoveCallbackFunc EventCallbackFunc 23 } 24 25 type FsnotifyService struct { 26 watcher *fsnotify.Watcher 27 paths []string 28 29 opts FsnotifyOptions 30 inShutdown atomic.Bool 31 mu sync.Mutex 32 cancel func() 33 } 34 35 // paths can also be dir or file or both of them 36 func NewFsnotifyService(paths []string, opts ...FsnotifyOption) (*FsnotifyService, error) { 37 watcher, err := fsnotify.NewWatcher() 38 if err != nil { 39 return nil, err 40 } 41 42 if len(paths) == 0 { 43 return nil, fmt.Errorf("paths is empty") 44 } 45 46 for _, path := range paths { 47 ok, err := os_.PathExist(path) 48 if err != nil { 49 return nil, err 50 } 51 if !ok { 52 return nil, fmt.Errorf("%s is not exist", path) 53 } 54 } 55 56 fs := &FsnotifyService{ 57 watcher: watcher, 58 paths: paths, 59 } 60 fs.ApplyOptions(opts...) 61 62 return fs, nil 63 } 64 65 func (srv *FsnotifyService) logger() logrus.FieldLogger { 66 return logrus.WithField("module", "FsnotifyService") 67 } 68 69 func (srv *FsnotifyService) Run(ctx context.Context) error { 70 logger := srv.logger() 71 logger.Infoln("FsnotifyService Run") 72 if srv.inShutdown.Load() { 73 logger.Infoln("FsnotifyService Shutdown") 74 return fmt.Errorf("server closed") 75 } 76 go func() { 77 errors.HandleError(srv.Serve(ctx)) 78 }() 79 return nil 80 } 81 82 func (srv *FsnotifyService) Serve(ctx context.Context) error { 83 logger := srv.logger() 84 logger.Infoln("FsnotifyService Serve") 85 86 if srv.inShutdown.Load() { 87 err := fmt.Errorf("server closed") 88 logger.WithError(err).Errorf("FsnotifyService Serve canceled") 89 return err 90 } 91 92 defer srv.inShutdown.Store(true) 93 ctx, cancel := context.WithCancel(ctx) 94 srv.mu.Lock() 95 srv.cancel = cancel 96 srv.mu.Unlock() 97 98 defer func() { 99 err := srv.watcher.Close() 100 if err != nil { 101 logger.WithError(err).Errorf("failed to close FsnotifyService watcher") 102 } 103 }() 104 105 err := srv.AddWatchPaths(false, srv.paths...) 106 if err != nil { 107 logger.WithError(err).Errorf("failed to add watcher for path: %v", srv.paths) 108 return err 109 } 110 111 for { 112 select { 113 case ev, ok := <-srv.watcher.Events: 114 if !ok { 115 continue 116 } 117 118 if ev.Op&fsnotify.Create != 0 { 119 logger.Infof("%s happen create event", ev.Name) 120 srv.AddWatchPaths(false, ev.Name) 121 if srv.opts.CreateCallbackFunc != nil { 122 srv.opts.CreateCallbackFunc(ctx, ev.Name) 123 } 124 } 125 if ev.Op&fsnotify.Write != 0 { 126 logger.Infof("%s happen write event", ev.Name) 127 srv.AddWatchPaths(false, ev.Name) 128 if srv.opts.WriteCallbackFunc != nil { 129 srv.opts.WriteCallbackFunc(ctx, ev.Name) 130 } 131 } 132 if ev.Op&fsnotify.Remove != 0 { 133 logger.Infof("%s happen remove event", ev.Name) 134 srv.AddWatchPaths(true, ev.Name) 135 if srv.opts.RemoveCallbackFunc != nil { 136 srv.opts.RemoveCallbackFunc(ctx, ev.Name) 137 } 138 } 139 if ev.Op&fsnotify.Rename != 0 { 140 logger.Infof("%s happen rename event", ev.Name) 141 } 142 if ev.Op&fsnotify.Chmod != 0 { 143 logger.Infof("%s happen chmod event", ev.Name) 144 } 145 146 case <-ctx.Done(): 147 logger.WithError(ctx.Err()).Errorf("server canceld") 148 return ctx.Err() 149 } 150 } 151 } 152 153 // Add starts watching the named directory (support recursively). 154 func (srv *FsnotifyService) AddWatchPath(unWatch bool, path string) error { 155 logger := srv.logger() 156 157 ok, err := os_.IsDir(path) 158 if err != nil { 159 return err 160 } 161 162 if ok { 163 return filepath.Walk(path, func(walkPath string, fi os.FileInfo, err error) error { 164 if err != nil { 165 logger.WithError(err).Errorf("failed to walk dir: %v", walkPath) 166 return err 167 } 168 169 if fi.IsDir() { 170 return srv.Add(unWatch, walkPath) 171 } 172 return nil 173 }) 174 } else { 175 return srv.Add(unWatch, path) 176 } 177 } 178 179 // Add starts watching the named directory (non-recursively). 180 func (srv *FsnotifyService) Add(unWatch bool, path string) (err error) { 181 logger := srv.logger() 182 if unWatch { 183 err = srv.watcher.Remove(path) 184 } else { 185 err = srv.watcher.Add(path) 186 } 187 if err != nil { 188 logger.WithError(err).Errorf("failed to add watcher for path: %v, ", path) 189 return err 190 } 191 logger.Infof("add watcher for path: %v", path) 192 193 return nil 194 } 195 196 func (srv *FsnotifyService) AddWatchPaths(unWatch bool, paths ...string) error { 197 for _, path := range paths { 198 err := srv.AddWatchPath(unWatch, path) 199 if err != nil { 200 return err 201 } 202 } 203 204 return nil 205 } 206 207 func (srv *FsnotifyService) Shutdown() { 208 srv.inShutdown.Store(true) 209 srv.mu.Lock() 210 defer srv.mu.Unlock() 211 if srv.cancel != nil { 212 srv.cancel() 213 } 214 }