github.com/bazelbuild/bazel-watcher@v0.25.2/internal/ibazel/fswatcher/fsevents/fsevents.go (about)

     1  // Copyright 2017 The Bazel Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build darwin
    16  // +build darwin
    17  
    18  package fsevents
    19  
    20  import (
    21  	"errors"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/fsnotify/fsevents"
    26  
    27  	"github.com/bazelbuild/bazel-watcher/internal/ibazel/fswatcher/common"
    28  )
    29  
    30  type realFSEventsWatcher struct {
    31  	es  *fsevents.EventStream
    32  	evs chan common.Event
    33  }
    34  
    35  var _ common.Watcher = &realFSEventsWatcher{}
    36  
    37  // Close implements ibazel/fswatcher/common.Watcher
    38  func (w *realFSEventsWatcher) Close() error {
    39  	w.es.Stop()
    40  	close(w.es.Events)
    41  	close(w.evs)
    42  	return nil
    43  }
    44  
    45  // UpdateAll implements ibazel/fswatcher/common.Watcher
    46  func (w *realFSEventsWatcher) UpdateAll(names []string) error {
    47  	w.es.Stop()
    48  	commonRoot, err := findCommonRoot(names)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	es := &fsevents.EventStream{
    53  		Events: make(chan []fsevents.Event),
    54  		Paths:  commonRoot,
    55  		Flags:  w.es.Flags,
    56  	}
    57  	w.es = es
    58  	go w.MapEvents()
    59  	es.Start()
    60  
    61  	return nil
    62  }
    63  
    64  // Events implements ibazel/fswatcher/common.Watcher
    65  func (w *realFSEventsWatcher) Events() chan common.Event {
    66  	return w.evs
    67  }
    68  func (s *realFSEventsWatcher) MapEvents() {
    69  	for events := range s.es.Events {
    70  		for _, event := range events {
    71  			if evt, ok := newEvent(event.Path, event.Flags); ok {
    72  				s.evs <- evt
    73  			}
    74  		}
    75  	}
    76  }
    77  
    78  func newEvent(name string, mask fsevents.EventFlags) (common.Event, bool) {
    79  	e := common.Event{}
    80  
    81  	if mask&fsevents.ItemIsFile != fsevents.ItemIsFile {
    82  		return e, false
    83  	}
    84  
    85  	if mask&fsevents.ItemRemoved == fsevents.ItemRemoved {
    86  		e.Op |= common.Remove
    87  	}
    88  	if mask&fsevents.ItemCreated == fsevents.ItemCreated {
    89  		e.Op |= common.Create
    90  	}
    91  	if mask&fsevents.ItemRenamed == fsevents.ItemRenamed {
    92  		e.Op |= common.Rename
    93  	}
    94  	if mask&fsevents.ItemModified == fsevents.ItemModified ||
    95  		mask&fsevents.ItemInodeMetaMod == fsevents.ItemInodeMetaMod {
    96  		e.Op |= common.Write
    97  	}
    98  	if mask&fsevents.ItemChangeOwner == fsevents.ItemChangeOwner ||
    99  		mask&fsevents.ItemXattrMod == fsevents.ItemXattrMod {
   100  		e.Op |= common.Chmod
   101  	}
   102  
   103  	e.Name = name
   104  	return e, true
   105  }
   106  
   107  // Find the longest common root path of all directories to watch.
   108  func findCommonRoot(names []string) ([]string, error) {
   109  	if len(names) == 0 {
   110  		return []string{}, nil
   111  	}
   112  
   113  	rootSplit := strings.Split(strings.Trim(names[0], "/"), "/")
   114  	rootLength := len(rootSplit)
   115  
   116  	for _, dir := range names {
   117  		split := strings.Split(strings.Trim(dir, "/"), "/")
   118  		commonLength := 0
   119  		for i := 0; i < rootLength && i < len(split); i++ {
   120  			if rootSplit[i] != split[i] {
   121  				break
   122  			}
   123  			commonLength = i + 1
   124  		}
   125  		rootLength = commonLength
   126  	}
   127  
   128  	if rootLength == 0 {
   129  		return nil, errors.New("could not find common root of directories")
   130  	}
   131  
   132  	return []string{"/" + filepath.Join(rootSplit[:rootLength]...) + "/"}, nil
   133  }
   134  
   135  func NewWatcher() (common.Watcher, error) {
   136  	es := &fsevents.EventStream{
   137  		Events: make(chan []fsevents.Event),
   138  		Paths:  []string{},
   139  		Flags:  fsevents.FileEvents,
   140  	}
   141  	watcher := &realFSEventsWatcher{
   142  		es:  es,
   143  		evs: make(chan common.Event),
   144  	}
   145  	return watcher, nil
   146  }