github.com/argoproj/argo-events@v1.9.1/eventsources/common/naivewatcher/watcher.go (about) 1 package naivewatcher 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "sync" 8 "time" 9 10 "github.com/argoproj/argo-events/eventsources/common/fsevent" 11 ) 12 13 const ( 14 eventsQueueSize = 100 15 errorsQueueSize = 100 16 ) 17 18 // Watchable is an interface of FS which can be watched by this watcher 19 type Watchable interface { 20 Walk(root string, walkFn filepath.WalkFunc) error 21 GetFileID(fi os.FileInfo) interface{} 22 } 23 24 // filePathAndInfo is an internal struct to manage path and file info 25 type filePathAndInfo struct { 26 path string 27 info os.FileInfo 28 found bool 29 } 30 31 // Watcher is a file watcher 32 type Watcher struct { 33 watchable Watchable 34 35 // manage target directories 36 watchList map[string]map[interface{}]*filePathAndInfo 37 mWatchList *sync.RWMutex 38 39 // internal use 40 mCheck *Mutex 41 mutexRunning *Mutex 42 Events chan fsevent.Event 43 Errors chan error 44 stop chan struct{} 45 stopResp chan struct{} 46 } 47 48 // NewWatcher is the initializer of watcher struct 49 func NewWatcher(watchable Watchable) (*Watcher, error) { 50 watcher := &Watcher{ 51 watchable: watchable, 52 mWatchList: new(sync.RWMutex), 53 watchList: map[string]map[interface{}]*filePathAndInfo{}, 54 mCheck: new(Mutex), 55 mutexRunning: new(Mutex), 56 Events: make(chan fsevent.Event, eventsQueueSize), 57 Errors: make(chan error, errorsQueueSize), 58 stop: make(chan struct{}), 59 stopResp: make(chan struct{}), 60 } 61 return watcher, nil 62 } 63 64 // Add adds a directory into the watch list 65 func (w *Watcher) Add(dir string) error { 66 w.mWatchList.Lock() 67 defer w.mWatchList.Unlock() 68 w.watchList[dir] = nil 69 return nil 70 } 71 72 // Remove removes a directory from the watch list 73 func (w *Watcher) Remove(dir string) error { 74 w.mWatchList.Lock() 75 defer w.mWatchList.Unlock() 76 delete(w.watchList, dir) 77 return nil 78 } 79 80 // WatchList returns target directories 81 func (w *Watcher) WatchList() []string { 82 w.mWatchList.RLock() 83 defer w.mWatchList.RUnlock() 84 dirs := []string{} 85 for dir := range w.watchList { 86 dirs = append(dirs, dir) 87 } 88 return dirs 89 } 90 91 // Close cleans up the watcher 92 func (w *Watcher) Close() error { 93 _ = w.Stop() 94 close(w.Events) 95 close(w.Errors) 96 return nil 97 } 98 99 // Start starts the watcher 100 func (w *Watcher) Start(interval time.Duration) error { 101 if !w.mutexRunning.TryLock() { 102 return fmt.Errorf("watcher has already started") 103 } 104 // run initial check 105 err := w.Check() 106 if err != nil { 107 return err 108 } 109 go func() { 110 defer w.mutexRunning.Unlock() 111 defer close(w.stopResp) 112 for { 113 select { 114 case <-time.After(interval): 115 err := w.Check() 116 if err != nil { 117 w.Errors <- err 118 } 119 case <-w.stop: 120 return 121 } 122 } 123 }() 124 return nil 125 } 126 127 // Stop stops the watcher 128 func (w *Watcher) Stop() error { 129 if !w.mutexRunning.IsLocked() { 130 return fmt.Errorf("watcher is not started") 131 } 132 select { 133 case <-w.stop: 134 default: 135 close(w.stop) 136 } 137 <-w.stopResp 138 return nil 139 } 140 141 // Check checks the state of target directories 142 func (w *Watcher) Check() error { 143 if !w.mCheck.TryLock() { 144 return fmt.Errorf("another check is still running") 145 } 146 defer w.mCheck.Unlock() 147 148 w.mWatchList.Lock() 149 defer w.mWatchList.Unlock() 150 151 for dir, walkedFiles := range w.watchList { 152 if walkedFiles == nil { 153 walkedFiles = make(map[interface{}]*filePathAndInfo) 154 w.watchList[dir] = walkedFiles 155 } 156 for fileID := range walkedFiles { 157 walkedFiles[fileID].found = false 158 } 159 walkFn := func(path string, info os.FileInfo, err error) error { 160 if err != nil { 161 w.Errors <- err 162 return nil 163 } 164 // Ignore walk root 165 if dir == path { 166 return nil 167 } 168 fileID := w.watchable.GetFileID(info) 169 lastPathAndInfo := walkedFiles[fileID] 170 if lastPathAndInfo == nil { 171 w.Events <- fsevent.Event{Op: fsevent.Create, Name: path} 172 walkedFiles[fileID] = &filePathAndInfo{path, info, true} 173 } else { 174 lastInfo := lastPathAndInfo.info 175 var op fsevent.Op 176 if path != lastPathAndInfo.path { 177 op |= fsevent.Rename 178 } 179 if !info.ModTime().Equal(lastInfo.ModTime()) || info.Size() != lastInfo.Size() { 180 op |= fsevent.Write 181 } 182 if info.Mode() != lastInfo.Mode() { 183 op |= fsevent.Chmod 184 } 185 if op != 0 { 186 w.Events <- fsevent.Event{Op: op, Name: path} 187 } 188 walkedFiles[fileID].path = path 189 walkedFiles[fileID].info = info 190 walkedFiles[fileID].found = true 191 } 192 return nil 193 } 194 195 err := w.watchable.Walk(dir, walkFn) 196 if err != nil { 197 return err 198 } 199 200 for fileID, pathAndInfo := range walkedFiles { 201 if !pathAndInfo.found { 202 w.Events <- fsevent.Event{Op: fsevent.Remove, Name: pathAndInfo.path} 203 delete(walkedFiles, fileID) 204 } 205 } 206 } 207 208 return nil 209 }