github.com/gofunct/common@v0.0.0-20190131174352-fd058c7fbf22/pkg/fs/watcher/watcher.go (about) 1 package watcher 2 3 import ( 4 //"fmt" 5 6 "github.com/gofunct/common/pkg/fs/watcher/fswatch" 7 "github.com/mgutz/str" 8 "os" 9 "path/filepath" 10 "strings" 11 "sync" 12 "time" 13 ) 14 15 const ( 16 // IgnoreThresholdRange is the amount of time in ns to ignore when 17 // receiving watch events for the same file 18 IgnoreThresholdRange = 50 * 1000000 // convert to ms 19 ) 20 21 // SetWatchDelay sets the watch delay 22 func SetWatchDelay(delay time.Duration) { 23 fswatch.WatchDelay = delay 24 } 25 26 // Watcher is a wrapper around which adds some additional features: 27 // 28 // - recursive directory watch 29 // - buffer to even chan 30 // - even time 31 // 32 // Original work from https://github.com/bronze1man/kmg 33 type Watcher struct { 34 *fswatch.Watcher 35 Event chan *FileEvent 36 Error chan error 37 //default ignore all file start with "." 38 IgnorePathFn func(path string) bool 39 //default is nil,if is nil ,error send through Error chan,if is not nil,error handle by this func 40 ErrorHandler func(err error) 41 isClosed bool 42 quit chan bool 43 cache map[string]*os.FileInfo 44 mu sync.Mutex 45 } 46 47 // NewWatcher creates an instance of watcher. 48 func NewWatcher(bufferSize int) (watcher *Watcher, err error) { 49 50 fswatcher := fswatch.NewAutoWatcher() 51 52 if err != nil { 53 return nil, err 54 } 55 watcher = &Watcher{ 56 Watcher: fswatcher, 57 Error: make(chan error, 10), 58 Event: make(chan *FileEvent, bufferSize), 59 IgnorePathFn: DefaultIgnorePathFn, 60 cache: map[string]*os.FileInfo{}, 61 } 62 return 63 } 64 65 // Close closes the watcher channels. 66 func (w *Watcher) Close() error { 67 if w.isClosed { 68 return nil 69 } 70 w.Watcher.Stop() 71 w.quit <- true 72 w.isClosed = true 73 return nil 74 } 75 76 func (w *Watcher) eventLoop() { 77 // cache := map[string]*os.FileInfo{} 78 // mu := &sync.Mutex{} 79 80 coutput := w.Watcher.Start() 81 for { 82 event, ok := <-coutput 83 if !ok { 84 return 85 } 86 87 // fmt.Printf("event %+v\n", event) 88 if w.IgnorePathFn(event.Path) { 89 continue 90 } 91 92 // you can not stat a delete file... 93 if event.Event == fswatch.DELETED || event.Event == fswatch.NOEXIST { 94 // adjust with arbitrary value because it was deleted 95 // before it got here 96 //fmt.Println("sending fi wevent", event) 97 w.Event <- newFileEvent(event.Event, event.Path, time.Now().UnixNano()-10) 98 continue 99 } 100 101 fi, err := os.Stat(event.Path) 102 if os.IsNotExist(err) { 103 //fmt.Println("not exists", event) 104 continue 105 } 106 107 // fsnotify is sending multiple MODIFY events for the same 108 // file which is likely OS related. The solution here is to 109 // compare the current stats of a file against its last stats 110 // (if any) and if it falls within a nanoseconds threshold, 111 // ignore it. 112 w.mu.Lock() 113 oldFI := w.cache[event.Path] 114 w.cache[event.Path] = &fi 115 116 // if oldFI != nil { 117 // fmt.Println("new FI", fi.ModTime().UnixNano()) 118 // fmt.Println("old FI", (*oldFI).ModTime().UnixNano()+IgnoreThresholdRange) 119 // } 120 121 if oldFI != nil && fi.ModTime().UnixNano() < (*oldFI).ModTime().UnixNano()+IgnoreThresholdRange { 122 w.mu.Unlock() 123 continue 124 } 125 w.mu.Unlock() 126 127 //fmt.Println("sending Event", fi.Name()) 128 129 //fmt.Println("sending fi wevent", event) 130 w.Event <- newFileEvent(event.Event, event.Path, fi.ModTime().UnixNano()) 131 132 if err != nil { 133 //rename send two events,one old file,one new file,here ignore old one 134 if os.IsNotExist(err) { 135 continue 136 } 137 w.errorHandle(err) 138 continue 139 } 140 // case err := <-w.Watcher.Errors: 141 // w.errorHandle(err) 142 // case _ = <-w.quit: 143 // break 144 // } 145 } 146 } 147 func (w *Watcher) errorHandle(err error) { 148 if w.ErrorHandler == nil { 149 w.Error <- err 150 return 151 } 152 w.ErrorHandler(err) 153 } 154 155 // GetErrorChan gets error chan. 156 func (w *Watcher) GetErrorChan() chan error { 157 return w.Error 158 } 159 160 // GetEventChan gets event chan. 161 func (w *Watcher) GetEventChan() chan *FileEvent { 162 return w.Event 163 } 164 165 // WatchRecursive watches a directory recursively. If a dir is created 166 // within directory it is also watched. 167 func (w *Watcher) WatchRecursive(path string) error { 168 path, err := filepath.Abs(path) 169 if err != nil { 170 return err 171 } 172 173 _, err = os.Stat(path) 174 if err != nil { 175 return err 176 } 177 178 w.Watcher.Add(path) 179 180 //util.Debug("watcher", "watching %s %s\n", path, time.Now()) 181 return nil 182 } 183 184 // Start starts the watcher 185 func (w *Watcher) Start() { 186 go w.eventLoop() 187 } 188 189 // func (w *Watcher) getSubFolders(path string) (paths []string, err error) { 190 // err = filepath.Walk(path, func(newPath string, info os.FileInfo, err error) error { 191 // if err != nil { 192 // return err 193 // } 194 195 // if !info.IsDir() { 196 // return nil 197 // } 198 // if w.IgnorePathFn(newPath) { 199 // return filepath.SkipDir 200 // } 201 // paths = append(paths, newPath) 202 // return nil 203 // }) 204 // return paths, err 205 // } 206 207 // DefaultIgnorePathFn checks whether a path is ignored. Currently defaults 208 // to hidden files on *nix systems, ie they start with a ".". 209 func DefaultIgnorePathFn(path string) bool { 210 if strings.HasPrefix(path, ".") || strings.Contains(path, "/.") { 211 return true 212 } 213 214 // ignore node 215 if strings.HasPrefix(path, "node_modules") || strings.Contains(path, "/node_modules") { 216 return true 217 } 218 219 // vim creates random numeric files 220 base := filepath.Base(path) 221 if str.IsNumeric(base) { 222 return true 223 } 224 return false 225 } 226 227 // SetIgnorePathFn sets the function which determines if a path should be 228 // skipped when watching. 229 func (w *Watcher) SetIgnorePathFn(fn func(string) bool) { 230 w.Watcher.IgnorePathFn = fn 231 }