github.com/nycdavid/zeus@v0.0.0-20201208104106-9ba439429e03/go/filemonitor/filemonitor_darwin.go (about)

     1  // +build darwin
     2  
     3  package filemonitor
     4  
     5  import (
     6  	"os"
     7  	"path/filepath"
     8  	"time"
     9  
    10  	"github.com/fsnotify/fsevents"
    11  )
    12  
    13  const flagsWorthReloadingFor = fsevents.ItemRemoved | fsevents.ItemModified | fsevents.ItemRenamed
    14  
    15  type fsEventsMonitor struct {
    16  	fileMonitor
    17  	stream *fsevents.EventStream
    18  	add    chan string
    19  	stop   chan struct{}
    20  }
    21  
    22  func NewFileMonitor(fileChangeDelay time.Duration) (FileMonitor, error) {
    23  	f := fsEventsMonitor{
    24  		stream: &fsevents.EventStream{
    25  			Paths:   []string{},
    26  			Latency: fileChangeDelay,
    27  			Flags:   fsevents.FileEvents,
    28  			EventID: fsevents.EventIDSinceNow,
    29  		},
    30  		// Restarting FSEvents can take ~100ms so buffer adds
    31  		// in the channel so they can be grouped together.
    32  		add:  make(chan string, 5000),
    33  		stop: make(chan struct{}),
    34  	}
    35  
    36  	go f.handleAdd()
    37  
    38  	return &f, nil
    39  }
    40  
    41  func (f *fsEventsMonitor) Add(file string) error {
    42  	f.add <- file
    43  	return nil
    44  }
    45  
    46  func (f *fsEventsMonitor) Close() error {
    47  	select {
    48  	case <-f.stop:
    49  		return nil // Already stopped
    50  	default:
    51  		close(f.stop)
    52  		close(f.add)
    53  	}
    54  
    55  	return nil
    56  }
    57  
    58  func (f *fsEventsMonitor) watch() {
    59  	for {
    60  		select {
    61  		case events := <-f.stream.Events:
    62  			paths := make([]string, 0, len(events))
    63  			for _, event := range events {
    64  				if (event.Flags & (fsevents.ItemIsFile | flagsWorthReloadingFor)) == 0 {
    65  					continue
    66  				}
    67  
    68  				paths = append(paths, event.Path)
    69  			}
    70  
    71  			if len(paths) == 0 {
    72  				continue
    73  			}
    74  
    75  			for _, l := range f.listeners {
    76  				l <- paths
    77  			}
    78  		case <-f.stop:
    79  			return
    80  		}
    81  	}
    82  }
    83  
    84  func (f *fsEventsMonitor) handleAdd() {
    85  	watched := make(map[string]bool)
    86  	started := false
    87  	// We don't want to add individual files to watch here but figure out the
    88  	// directory to watch and watch it instead.
    89  
    90  	for file := range f.add {
    91  		path, err := pathToMonitor(file)
    92  		if err != nil {
    93  			// can't access the file for some reason, best to ignore, probably should
    94  			// log something here
    95  			continue
    96  		}
    97  
    98  		if watched[path] {
    99  			continue
   100  		}
   101  
   102  		allFiles := []string{path}
   103  
   104  		// Read all messages waiting in the channel so we can batch restarts
   105  		done := false
   106  		for !done {
   107  			select {
   108  			case file := <-f.add:
   109  				path, err := pathToMonitor(file)
   110  				if err != nil {
   111  					continue
   112  				}
   113  
   114  				if watched[path] {
   115  					continue
   116  				}
   117  
   118  				allFiles = append(allFiles, path)
   119  			default:
   120  				done = true
   121  			}
   122  		}
   123  
   124  		for _, file := range allFiles {
   125  			watched[file] = true
   126  			if contains(f.stream.Paths, file) {
   127  				continue
   128  			}
   129  
   130  			f.stream.Paths = append(f.stream.Paths, file)
   131  		}
   132  
   133  		if started {
   134  			f.stream.Restart()
   135  		} else {
   136  			f.stream.Start()
   137  			go f.watch()
   138  			started = true
   139  		}
   140  	}
   141  
   142  	if started {
   143  		f.stream.Stop()
   144  	}
   145  }
   146  
   147  func pathToMonitor(rawPath string) (path string, err error) {
   148  	stat, err := os.Stat(rawPath)
   149  	if err != nil {
   150  		return
   151  	}
   152  
   153  	if stat.IsDir() {
   154  		path = rawPath
   155  	} else {
   156  		path = filepath.Dir(rawPath)
   157  	}
   158  
   159  	return
   160  }
   161  
   162  func contains(s []string, e string) bool {
   163  	for _, a := range s {
   164  		if a == e {
   165  			return true
   166  		}
   167  	}
   168  
   169  	return false
   170  }