github.com/DiversionCompany/notify@v0.9.9/watcher_fsevents.go (about) 1 // Copyright (c) 2014-2015 The Notify Authors. All rights reserved. 2 // Use of this source code is governed by the MIT license that can be 3 // found in the LICENSE file. 4 5 //go:build darwin && !kqueue && cgo 6 // +build darwin,!kqueue,cgo 7 8 package notify 9 10 import ( 11 "errors" 12 "strings" 13 "sync/atomic" 14 ) 15 16 const ( 17 failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped) 18 filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed | 19 FSEventsModified | FSEventsInodeMetaMod) 20 ) 21 22 // FSEvent represents single file event. It is created out of values passed by 23 // FSEvents to FSEventStreamCallback function. 24 type FSEvent struct { 25 Path string // real path of the file or directory 26 ID uint64 // ID of the event (FSEventStreamEventId) 27 Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags) 28 } 29 30 // splitflags separates event flags from single set into slice of flags. 31 func splitflags(set uint32) (e []uint32) { 32 for i := uint32(1); set != 0; i, set = i<<1, set>>1 { 33 if (set & 1) != 0 { 34 e = append(e, i) 35 } 36 } 37 return 38 } 39 40 // watch represents a filesystem watchpoint. It is a higher level abstraction 41 // over FSEvents' stream, which implements filtering of file events based 42 // on path and event set. It emulates non-recursive watch-point by filtering out 43 // events which paths are more than 1 level deeper than the watched path. 44 type watch struct { 45 c chan<- EventInfo 46 stream *stream 47 path string 48 events uint32 49 isrec int32 50 flushed bool 51 } 52 53 // Dispatch is a stream function which forwards given file events for the watched 54 // path to underlying FileInfo channel. 55 func (w *watch) Dispatch(ev []FSEvent) { 56 events := atomic.LoadUint32(&w.events) 57 isrec := (atomic.LoadInt32(&w.isrec) == 1) 58 for i := range ev { 59 if ev[i].Flags&FSEventsHistoryDone != 0 { 60 w.flushed = true 61 continue 62 } 63 if !w.flushed { 64 continue 65 } 66 dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags), 67 ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev)) 68 if ev[i].Flags&failure != 0 && failure&events == 0 { 69 // TODO(rjeczalik): missing error handling 70 continue 71 } 72 if !strings.HasPrefix(ev[i].Path, w.path) { 73 continue 74 } 75 n := len(w.path) 76 base := "" 77 if len(ev[i].Path) > n { 78 if ev[i].Path[n] != '/' { 79 continue 80 } 81 base = ev[i].Path[n+1:] 82 if !isrec && strings.IndexByte(base, '/') != -1 { 83 continue 84 } 85 } 86 // TODO(rjeczalik): get diff only from filtered events? 87 e := ev[i].Flags & events 88 if e == 0 { 89 continue 90 } 91 for _, e := range splitflags(e) { 92 dbgprintf("%d: single event: %v", ev[i].ID, Event(e)) 93 w.c <- &event{ 94 fse: ev[i], 95 event: Event(e), 96 } 97 } 98 } 99 } 100 101 // Stop closes underlying FSEvents stream and stops dispatching events. 102 func (w *watch) Stop() { 103 w.stream.Stop() 104 // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events, 105 // so the following hack can be removed. It should flush all the streams 106 // concurrently as we care not to block too much here. 107 atomic.StoreUint32(&w.events, 0) 108 atomic.StoreInt32(&w.isrec, 0) 109 } 110 111 // fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents 112 // framework. 113 type fsevents struct { 114 watches map[string]*watch 115 c chan<- EventInfo 116 } 117 118 func newWatcher(c chan<- EventInfo) watcher { 119 return &fsevents{ 120 watches: make(map[string]*watch), 121 c: c, 122 } 123 } 124 125 func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) { 126 if _, ok := fse.watches[path]; ok { 127 return errAlreadyWatched 128 } 129 w := &watch{ 130 c: fse.c, 131 path: path, 132 events: uint32(event), 133 isrec: isrec, 134 } 135 w.stream = newStream(path, w.Dispatch) 136 if err = w.stream.Start(); err != nil { 137 return err 138 } 139 fse.watches[path] = w 140 return nil 141 } 142 143 func (fse *fsevents) unwatch(path string) (err error) { 144 w, ok := fse.watches[path] 145 if !ok { 146 return errNotWatched 147 } 148 w.stream.Stop() 149 delete(fse.watches, path) 150 return nil 151 } 152 153 // Watch implements Watcher interface. It fails with non-nil error when setting 154 // the watch-point by FSEvents fails or with errAlreadyWatched error when 155 // the given path is already watched. 156 func (fse *fsevents) Watch(path string, event Event) error { 157 return fse.watch(path, event, 0) 158 } 159 160 // Unwatch implements Watcher interface. It fails with errNotWatched when 161 // the given path is not being watched. 162 func (fse *fsevents) Unwatch(path string) error { 163 return fse.unwatch(path) 164 } 165 166 // Rewatch implements Watcher interface. It fails with errNotWatched when 167 // the given path is not being watched or with errInvalidEventSet when oldevent 168 // does not match event set the watch-point currently holds. 169 func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error { 170 w, ok := fse.watches[path] 171 if !ok { 172 return errNotWatched 173 } 174 if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) { 175 return errInvalidEventSet 176 } 177 atomic.StoreInt32(&w.isrec, 0) 178 return nil 179 } 180 181 // RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil 182 // error when setting the watch-point by FSEvents fails or with errAlreadyWatched 183 // error when the given path is already watched. 184 func (fse *fsevents) RecursiveWatch(path string, event Event) error { 185 return fse.watch(path, event, 1) 186 } 187 188 // RecursiveUnwatch implements RecursiveWatcher interface. It fails with 189 // errNotWatched when the given path is not being watched. 190 // 191 // TODO(rjeczalik): fail if w.isrec == 0? 192 func (fse *fsevents) RecursiveUnwatch(path string) error { 193 return fse.unwatch(path) 194 } 195 196 // RecursiveRewatch implements RecursiveWatcher interface. It fails: 197 // 198 // - with errNotWatched when the given path is not being watched 199 // - with errInvalidEventSet when oldevent does not match the current event set 200 // - with errAlreadyWatched when watch-point given by the oldpath was meant to 201 // be relocated to newpath, but the newpath is already watched 202 // - a non-nil error when setting the watch-point with FSEvents fails 203 // 204 // TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs 205 // that follows. 206 func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error { 207 switch [2]bool{oldpath == newpath, oldevent == newevent} { 208 case [2]bool{true, true}: 209 w, ok := fse.watches[oldpath] 210 if !ok { 211 return errNotWatched 212 } 213 atomic.StoreInt32(&w.isrec, 1) 214 return nil 215 case [2]bool{true, false}: 216 w, ok := fse.watches[oldpath] 217 if !ok { 218 return errNotWatched 219 } 220 if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) { 221 return errors.New("invalid event state diff") 222 } 223 atomic.StoreInt32(&w.isrec, 1) 224 return nil 225 default: 226 // TODO(rjeczalik): rewatch newpath only if exists? 227 // TODO(rjeczalik): migrate w.prev to new watch? 228 if _, ok := fse.watches[newpath]; ok { 229 return errAlreadyWatched 230 } 231 if err := fse.Unwatch(oldpath); err != nil { 232 return err 233 } 234 // TODO(rjeczalik): revert unwatch if watch fails? 235 return fse.watch(newpath, newevent, 1) 236 } 237 } 238 239 // Close unwatches all watch-points. 240 func (fse *fsevents) Close() error { 241 for _, w := range fse.watches { 242 w.Stop() 243 } 244 fse.watches = nil 245 return nil 246 }