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  }