tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/watchfs/fs.go (about) 1 package watchfs 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "log" 8 "strings" 9 "sync" 10 "time" 11 12 "tractor.dev/toolkit-go/engine/fs/watchfs/watcher" 13 ) 14 15 var Interval time.Duration = 500 * time.Millisecond 16 17 type EventType uint 18 19 const ( 20 EventError EventType = 1 << iota 21 EventCreate 22 EventWrite 23 EventRemove 24 EventRename 25 EventChmod 26 EventMove 27 ) 28 29 type Event struct { 30 Type EventType 31 Path string 32 OldPath string 33 Err error 34 fs.FileInfo 35 } 36 37 func (e Event) String() string { 38 if e.Type == EventError { 39 return fmt.Sprintf("{Error %s}", e.Err.Error()) 40 } 41 return fmt.Sprintf("{%s %s %s}", map[EventType]string{ 42 EventCreate: "create", 43 EventWrite: "write", 44 EventRemove: "remove", 45 EventRename: "rename", 46 EventChmod: "chmod", 47 EventMove: "move", 48 }[e.Type], e.Path, e.OldPath) 49 } 50 51 type Config struct { 52 Recursive bool 53 EventMask uint 54 Ignores []string 55 Handler func(Event) 56 } 57 58 func Join(watches ...*Watch) <-chan Event { 59 joined := make(chan Event) 60 var wg sync.WaitGroup 61 wg.Add(len(watches)) 62 for _, w := range watches { 63 go func(ch <-chan Event) { 64 for v := range ch { 65 joined <- v 66 } 67 wg.Done() 68 }(w.Iter()) 69 } 70 go func() { 71 wg.Wait() 72 close(joined) 73 }() 74 return joined 75 } 76 77 type Watch struct { 78 path string 79 cfg Config 80 inbox chan Event 81 unwatch func(*Watch) 82 } 83 84 // NewWatch is for building your own WatchFS implementation. 85 // Use the returned chan Event for sending events and the 86 // chan bool for waiting for a close. 87 func NewWatch(path string, cfg Config) (*Watch, chan Event, chan bool) { 88 inbox := make(chan Event) 89 closer := make(chan bool) 90 unwatch := func(*Watch) { 91 close(closer) 92 } 93 return &Watch{ 94 path: path, 95 cfg: cfg, 96 inbox: inbox, 97 unwatch: unwatch, 98 }, inbox, closer 99 } 100 101 func (w *Watch) Iter() <-chan Event { 102 return w.inbox 103 } 104 105 func (w *Watch) Close() { 106 w.unwatch(w) 107 } 108 109 type FS struct { 110 fs.FS 111 watcher *watcher.Watcher 112 watches map[*Watch]struct{} 113 mu sync.Mutex 114 } 115 116 func New(fsys fs.FS) *FS { 117 _, ok := fsys.(fs.StatFS) 118 if !ok { 119 panic("stat needed on fs") 120 } 121 return &FS{ 122 FS: fsys, 123 watcher: watcher.New(fsys), 124 watches: make(map[*Watch]struct{}), 125 } 126 } 127 128 func (f *FS) matchWatches(path string) (watches []*Watch) { 129 f.mu.Lock() 130 defer f.mu.Unlock() 131 for w := range f.watches { 132 if strings.HasPrefix(path, w.path) { 133 watches = append(watches, w) 134 } 135 } 136 return 137 } 138 139 func (f *FS) startWatcher() { 140 go f.watcher.Start(Interval) 141 for { 142 select { 143 case event := <-f.watcher.Event: 144 // log.Println(event) 145 for _, w := range f.matchWatches(event.Path) { 146 // TODO: eventmask + ignores 147 e := Event{ 148 FileInfo: event.FileInfo, 149 Path: event.Path, 150 OldPath: event.OldPath, 151 Type: map[watcher.Op]EventType{ 152 watcher.Create: EventCreate, 153 watcher.Write: EventWrite, 154 watcher.Move: EventMove, 155 watcher.Remove: EventRemove, 156 watcher.Rename: EventRename, 157 watcher.Chmod: EventChmod, 158 }[event.Op], 159 } 160 if w.cfg.Handler != nil { 161 w.cfg.Handler(e) 162 } else { 163 w.inbox <- e 164 } 165 } 166 case err := <-f.watcher.Error: 167 if err == watcher.ErrWatchedFileDeleted { 168 log.Println(err) 169 } else { 170 panic(err) 171 } 172 case <-f.watcher.Closed: 173 return 174 } 175 } 176 } 177 178 func (f *FS) unwatch(w *Watch) { 179 180 if w.cfg.Recursive { 181 f.watcher.RemoveRecursive(w.path) 182 } else { 183 f.watcher.Remove(w.path) 184 } 185 186 f.mu.Lock() 187 delete(f.watches, w) 188 f.mu.Unlock() 189 } 190 191 func (f *FS) Watch(name string, cfg *Config) (*Watch, error) { 192 if wfs, ok := f.FS.(interface { 193 Watch(name string, cfg *Config) (*Watch, error) 194 }); ok { 195 w, err := wfs.Watch(name, cfg) 196 if err == nil || !errors.Is(err, errors.ErrUnsupported) { 197 return w, err 198 } 199 } 200 201 if !f.watcher.IsRunning() { 202 go f.startWatcher() 203 } 204 205 if cfg == nil { 206 cfg = &Config{} 207 } 208 209 if cfg.Recursive { 210 if err := f.watcher.AddRecursive(name); err != nil { 211 return nil, err 212 } 213 } else { 214 if err := f.watcher.Add(name); err != nil { 215 return nil, err 216 } 217 } 218 219 w := &Watch{ 220 path: name, 221 cfg: *cfg, 222 inbox: make(chan Event), 223 unwatch: f.unwatch, 224 } 225 226 f.mu.Lock() 227 f.watches[w] = struct{}{} 228 f.mu.Unlock() 229 230 return w, nil 231 }