github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/filewatch/fsevent/fake_watcher.go (about) 1 package fsevent 2 3 import ( 4 "path/filepath" 5 "sync" 6 "sync/atomic" 7 8 "github.com/tilt-dev/tilt/internal/ospath" 9 "github.com/tilt-dev/tilt/internal/watch" 10 "github.com/tilt-dev/tilt/pkg/logger" 11 ) 12 13 type FakeMultiWatcher struct { 14 Events chan watch.FileEvent 15 Errors chan error 16 17 mu sync.Mutex 18 watchers []*FakeWatcher 19 subs []chan watch.FileEvent 20 subsErrors []chan error 21 } 22 23 func NewFakeMultiWatcher() *FakeMultiWatcher { 24 r := &FakeMultiWatcher{ 25 Events: make(chan watch.FileEvent, 20), 26 Errors: make(chan error, 20), 27 } 28 go r.loop() 29 return r 30 } 31 32 func (w *FakeMultiWatcher) NewSub(paths []string, ignore watch.PathMatcher, _ logger.Logger) (watch.Notify, error) { 33 subCh := make(chan watch.FileEvent) 34 errorCh := make(chan error) 35 w.mu.Lock() 36 defer w.mu.Unlock() 37 38 watcher := NewFakeWatcher(subCh, errorCh, paths, ignore) 39 w.watchers = append(w.watchers, watcher) 40 w.subs = append(w.subs, subCh) 41 w.subsErrors = append(w.subsErrors, errorCh) 42 return watcher, nil 43 } 44 45 func (w *FakeMultiWatcher) getSubs() []chan watch.FileEvent { 46 w.mu.Lock() 47 defer w.mu.Unlock() 48 return append([]chan watch.FileEvent{}, w.subs...) 49 } 50 51 func (w *FakeMultiWatcher) getSubErrors() []chan error { 52 w.mu.Lock() 53 defer w.mu.Unlock() 54 return append([]chan error{}, w.subsErrors...) 55 } 56 57 func (w *FakeMultiWatcher) loop() { 58 defer func() { 59 for _, sub := range w.getSubs() { 60 close(sub) 61 } 62 63 for _, sub := range w.getSubErrors() { 64 close(sub) 65 } 66 }() 67 68 for { 69 select { 70 case e, ok := <-w.Events: 71 if !ok { 72 return 73 } 74 w.mu.Lock() 75 for _, watcher := range w.watchers { 76 if watcher.Running && watcher.matches(e.Path()) { 77 watcher.inboundCh <- e 78 } 79 } 80 w.mu.Unlock() 81 case e, ok := <-w.Errors: 82 if !ok { 83 return 84 } 85 for _, sub := range w.getSubErrors() { 86 sub <- e 87 } 88 } 89 } 90 } 91 92 type FakeWatcher struct { 93 inboundCh chan watch.FileEvent 94 outboundCh chan watch.FileEvent 95 errorCh chan error 96 closeCh chan bool 97 98 eventCount uint64 99 100 paths []string 101 ignore watch.PathMatcher 102 103 Running bool 104 StartErr error 105 } 106 107 func NewFakeWatcher(inboundCh chan watch.FileEvent, errorCh chan error, paths []string, ignore watch.PathMatcher) *FakeWatcher { 108 for i, path := range paths { 109 paths[i], _ = filepath.Abs(path) 110 } 111 112 return &FakeWatcher{ 113 inboundCh: inboundCh, 114 outboundCh: make(chan watch.FileEvent, 20), 115 errorCh: errorCh, 116 paths: paths, 117 ignore: ignore, 118 closeCh: make(chan bool), 119 } 120 } 121 122 func (w *FakeWatcher) matches(path string) bool { 123 ignore, _ := w.ignore.Matches(path) 124 if ignore { 125 return false 126 } 127 128 for _, watched := range w.paths { 129 if ospath.IsChild(watched, path) { 130 return true 131 } 132 } 133 return false 134 } 135 136 func (w *FakeWatcher) Start() error { 137 w.Running = true 138 go w.loop() 139 if w.StartErr != nil { 140 return w.StartErr 141 } 142 return nil 143 } 144 145 func (w *FakeWatcher) Close() error { 146 close(w.closeCh) 147 <-w.outboundCh 148 return nil 149 } 150 151 func (w *FakeWatcher) Errors() chan error { 152 return w.errorCh 153 } 154 155 func (w *FakeWatcher) Events() chan watch.FileEvent { 156 return w.outboundCh 157 } 158 159 func (w *FakeWatcher) TotalEventCount() uint64 { 160 return atomic.LoadUint64(&w.eventCount) 161 } 162 163 func (w *FakeWatcher) QueuedCount() int { 164 return len(w.outboundCh) 165 } 166 167 func (w *FakeWatcher) loop() { 168 defer func() { 169 w.Running = false 170 close(w.outboundCh) 171 }() 172 173 var q []watch.FileEvent 174 for { 175 if len(q) == 0 { 176 select { 177 case e, ok := <-w.inboundCh: 178 if !ok { 179 return 180 } 181 q = append(q, e) 182 case <-w.closeCh: 183 return 184 } 185 } else { 186 e := q[0] 187 w.outboundCh <- e 188 atomic.AddUint64(&w.eventCount, 1) 189 q = q[1:] 190 } 191 } 192 } 193 194 var _ watch.Notify = &FakeWatcher{}