github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/cli/run/watcher.go (about) 1 package runtime 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "strings" 9 "time" 10 11 "github.com/fsnotify/fsnotify" 12 13 "github.com/tickoalcantara12/micro/v3/service/logger" 14 ) 15 16 type CallbackFunc func() error 17 18 type watcher struct { 19 root string 20 watchDelay time.Duration 21 fileWatcher *fsnotify.Watcher 22 23 eventsChan chan string 24 callbackFunc CallbackFunc 25 stopChan chan struct{} 26 } 27 28 func NewWatcher(root string, delay time.Duration, fn CallbackFunc) (*watcher, error) { 29 fileWatcher, err := fsnotify.NewWatcher() 30 if err != nil { 31 return nil, err 32 } 33 34 return &watcher{ 35 root: root, 36 watchDelay: delay, 37 fileWatcher: fileWatcher, 38 eventsChan: make(chan string, 1000), 39 callbackFunc: fn, 40 stopChan: make(chan struct{}), 41 }, nil 42 } 43 44 // Watch the file changes in specific directories 45 func (w *watcher) Watch() error { 46 err := filepath.WalkDir(w.root, func(path string, info fs.DirEntry, err error) error { 47 if err != nil { 48 return err 49 } 50 51 if info != nil && !info.IsDir() { 52 return nil 53 } 54 55 return w.watchDirectory(path) 56 }) 57 58 if err != nil { 59 return err 60 } 61 62 w.start() 63 64 return nil 65 } 66 67 // start the watching process 68 func (w *watcher) start() { 69 for { 70 select { 71 case <-w.stopChan: 72 logger.Infof("Watcher is exiting...") 73 return 74 case <-w.eventsChan: 75 76 time.Sleep(w.watchDelay) 77 w.flushEvents() 78 79 if err := w.callbackFunc(); err != nil { 80 logger.Errorf("Watcher callback function execute error: %v", err) 81 break 82 } 83 84 } 85 } 86 } 87 88 // flushEvents flush the events in buffer channel 89 func (w *watcher) flushEvents() { 90 for { 91 select { 92 case ev := <-w.eventsChan: 93 logger.Debugf("Watcher flush event: %v", ev) 94 default: 95 return 96 } 97 } 98 } 99 100 // validExtension checks the extension of file is valid to be watched 101 func (w *watcher) validExtension(filepath string) bool { 102 if strings.HasSuffix(filepath, ".go") || strings.HasSuffix(filepath, ".proto") { 103 return true 104 } 105 106 return false 107 } 108 109 // watchDirectory watch all the files in the dir, recurse all the subdirectories 110 func (w *watcher) watchDirectory(dir string) error { 111 logger.Infof("Watcher is watching path: %+v", dir) 112 113 err := w.fileWatcher.Add(dir) 114 if err != nil { 115 logger.Fatal(err) 116 } 117 118 go func() { 119 for { 120 select { 121 case <-w.stopChan: 122 return 123 case event, ok := <-w.fileWatcher.Events: 124 if !ok { 125 return 126 } 127 if event.Op&fsnotify.Write != fsnotify.Write { 128 break 129 } 130 131 eventPath := event.Name 132 133 if !w.validExtension(eventPath) { 134 break 135 } 136 137 f, err := os.Stat(eventPath) 138 if err != nil { 139 logger.Errorf("File get file info: %v", err) 140 break 141 } 142 143 if f.IsDir() { 144 err := w.watchDirectory(eventPath) 145 if err != nil { 146 logger.Errorf("Watching dir error: %v", err) 147 } 148 break 149 } 150 151 logger.Infof("%v has changed", eventPath) 152 153 w.eventsChan <- event.Name 154 155 case err, ok := <-w.fileWatcher.Errors: 156 if !ok { 157 return 158 } 159 fmt.Println("error:", err) 160 } 161 } 162 }() 163 164 return nil 165 } 166 167 // Stop the watching process 168 func (w *watcher) Stop() { 169 close(w.stopChan) 170 }