返回 DeepSeek-Reasonix
workspace_watcher_darwin.go
根目录 / desktop / workspace_watcher_darwin.go
1 //go:build darwin && cgo
2
3 package main
4
5 /*
6 #cgo LDFLAGS: -framework CoreServices -framework CoreFoundation
7
8 #include <CoreServices/CoreServices.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11
12 typedef struct reasonix_fsevents_subscription reasonix_fsevents_subscription;
13
14 reasonix_fsevents_subscription *reasonix_fsevents_start(
15 const char *path,
16 uintptr_t token,
17 double latency,
18 int *error_code
19 );
20 void reasonix_fsevents_stop(reasonix_fsevents_subscription *subscription);
21 void reasonix_fsevents_catch_up(reasonix_fsevents_subscription *subscription);
22 */
23 import "C"
24
25 import (
26 "fmt"
27 "path/filepath"
28 "runtime/cgo"
29 "strings"
30 "sync"
31 "sync/atomic"
32 "unsafe"
33
34 "github.com/fsnotify/fsnotify"
35 )
36
37 const (
38 darwinWorkspaceEventBuffer = 1024
39 darwinWorkspaceLatency = 0.050
40
41 darwinFSEventMustScanSubDirs = uint32(C.kFSEventStreamEventFlagMustScanSubDirs)
42 darwinFSEventUserDropped = uint32(C.kFSEventStreamEventFlagUserDropped)
43 darwinFSEventKernelDropped = uint32(C.kFSEventStreamEventFlagKernelDropped)
44 darwinFSEventEventIDsWrapped = uint32(C.kFSEventStreamEventFlagEventIdsWrapped)
45 darwinFSEventHistoryDone = uint32(C.kFSEventStreamEventFlagHistoryDone)
46 darwinFSEventRootChanged = uint32(C.kFSEventStreamEventFlagRootChanged)
47 darwinFSEventMount = uint32(C.kFSEventStreamEventFlagMount)
48 darwinFSEventUnmount = uint32(C.kFSEventStreamEventFlagUnmount)
49 darwinFSEventItemCreated = uint32(C.kFSEventStreamEventFlagItemCreated)
50 darwinFSEventItemRemoved = uint32(C.kFSEventStreamEventFlagItemRemoved)
51 darwinFSEventItemInodeMeta = uint32(C.kFSEventStreamEventFlagItemInodeMetaMod)
52 darwinFSEventItemRenamed = uint32(C.kFSEventStreamEventFlagItemRenamed)
53 darwinFSEventItemModified = uint32(C.kFSEventStreamEventFlagItemModified)
54 darwinFSEventItemFinderInfo = uint32(C.kFSEventStreamEventFlagItemFinderInfoMod)
55 darwinFSEventItemChangeOwner = uint32(C.kFSEventStreamEventFlagItemChangeOwner)
56 darwinFSEventItemXattr = uint32(C.kFSEventStreamEventFlagItemXattrMod)
57 )
58
59 type darwinWorkspaceWatcher struct {
60 mu sync.Mutex
61 events chan fsnotify.Event
62 errors chan error
63 watches map[string]*darwinWorkspaceSubscription
64 isClosed bool
65 closed atomic.Bool
66 overflowed atomic.Bool
67 stopWG sync.WaitGroup
68 closeDone chan struct{}
69 }
70
71 type darwinWorkspaceSubscription struct {
72 watcher *darwinWorkspaceWatcher
73 path string
74 recursive bool
75 native *C.reasonix_fsevents_subscription
76 handle cgo.Handle
77 stopOnce sync.Once
78 stopNative func()
79 }
80
81 func newWorkspaceWatcher() (workspaceWatcher, error) {
82 return &darwinWorkspaceWatcher{
83 events: make(chan fsnotify.Event, darwinWorkspaceEventBuffer),
84 errors: make(chan error, 1),
85 watches: make(map[string]*darwinWorkspaceSubscription),
86 closeDone: make(chan struct{}),
87 }, nil
88 }
89
90 func (w *darwinWorkspaceWatcher) Events() <-chan fsnotify.Event { return w.events }
91 func (w *darwinWorkspaceWatcher) Errors() <-chan error { return w.errors }
92 func (w *darwinWorkspaceWatcher) SupportsRecursive() bool { return true }
93
94 // CatchUp delivers the daemon's pending notifications before the catalog opens
95 // its initial iterator. Callbacks only try-send into bounded channels, so this
96 // barrier never waits on the catalog or its database writer. Registration and
97 // close share mu to keep each native subscription alive across the barrier.
98 func (w *darwinWorkspaceWatcher) CatchUp() {
99 w.mu.Lock()
100 defer w.mu.Unlock()
101 if w.isClosed {
102 return
103 }
104 for _, sub := range w.watches {
105 C.reasonix_fsevents_catch_up(sub.native)
106 }
107 }
108
109 func (w *darwinWorkspaceWatcher) Add(path string, recursive bool) error {
110 path = canonicalWorkspaceRoot(path)
111 if path == "" {
112 return fmt.Errorf("start FSEvents stream: empty path")
113 }
114
115 w.mu.Lock()
116 defer w.mu.Unlock()
117 if w.isClosed {
118 return fsnotify.ErrClosed
119 }
120 if _, exists := w.watches[path]; exists {
121 return nil
122 }
123
124 sub := &darwinWorkspaceSubscription{watcher: w, path: path, recursive: recursive}
125 sub.handle = cgo.NewHandle(sub)
126 cPath := C.CString(path)
127 defer C.free(unsafe.Pointer(cPath))
128 var errorCode C.int
129 sub.native = C.reasonix_fsevents_start(cPath, C.uintptr_t(sub.handle), C.double(darwinWorkspaceLatency), &errorCode)
130 if sub.native == nil {
131 sub.handle.Delete()
132 return fmt.Errorf("start FSEvents stream for %q: %s", path, darwinFSEventsStartError(int(errorCode)))
133 }
134 w.watches[path] = sub
135 return nil
136 }
137
138 func (w *darwinWorkspaceWatcher) Remove(path string) error {
139 path = canonicalWorkspaceRoot(path)
140 w.mu.Lock()
141 sub := w.watches[path]
142 delete(w.watches, path)
143 if sub != nil {
144 w.stopWG.Add(1)
145 }
146 w.mu.Unlock()
147 if sub != nil {
148 sub.stop()
149 w.stopWG.Done()
150 }
151 return nil
152 }
153
154 func (w *darwinWorkspaceWatcher) Close() error {
155 w.mu.Lock()
156 if w.isClosed {
157 done := w.closeDone
158 w.mu.Unlock()
159 <-done
160 return nil
161 }
162 w.isClosed = true
163 w.closed.Store(true)
164 subs := make([]*darwinWorkspaceSubscription, 0, len(w.watches))
165 for _, sub := range w.watches {
166 subs = append(subs, sub)
167 }
168 w.watches = make(map[string]*darwinWorkspaceSubscription)
169 w.mu.Unlock()
170
171 for _, sub := range subs {
172 sub.stop()
173 }
174 w.stopWG.Wait()
175 close(w.events)
176 close(w.errors)
177 close(w.closeDone)
178 return nil
179 }
180
181 func (s *darwinWorkspaceSubscription) stop() {
182 s.stopOnce.Do(func() {
183 if s.stopNative != nil {
184 s.stopNative()
185 return
186 }
187 C.reasonix_fsevents_stop(s.native)
188 s.native = nil
189 s.handle.Delete()
190 })
191 }
192
193 //export reasonixFSEventsEvent
194 func reasonixFSEventsEvent(token C.uintptr_t, eventPath *C.char, eventFlags C.uint32_t) {
195 if eventPath == nil {
196 return
197 }
198 value := cgo.Handle(token).Value()
199 sub, ok := value.(*darwinWorkspaceSubscription)
200 if !ok || sub == nil {
201 return
202 }
203 sub.publish(filepath.Clean(C.GoString(eventPath)), uint32(eventFlags))
204 }
205
206 func (s *darwinWorkspaceSubscription) publish(path string, flags uint32) {
207 if path == "" || s.watcher.closed.Load() || !s.accepts(path) {
208 return
209 }
210 op, overflow := darwinWorkspaceEvent(flags)
211 if op != 0 {
212 s.watcher.sendEvent(fsnotify.Event{Name: path, Op: op})
213 }
214 if overflow {
215 s.watcher.sendOverflow()
216 }
217 }
218
219 func (s *darwinWorkspaceSubscription) accepts(path string) bool {
220 rel, err := filepath.Rel(s.path, path)
221 if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
222 return false
223 }
224 return s.recursive || rel == "." || !strings.ContainsRune(rel, filepath.Separator)
225 }
226
227 func (w *darwinWorkspaceWatcher) sendEvent(event fsnotify.Event) {
228 if w.closed.Load() {
229 return
230 }
231 select {
232 case w.events <- event:
233 w.overflowed.Store(false)
234 default:
235 w.sendOverflow()
236 }
237 }
238
239 func (w *darwinWorkspaceWatcher) sendOverflow() {
240 if w.closed.Load() || w.overflowed.Swap(true) {
241 return
242 }
243 select {
244 case w.errors <- fsnotify.ErrEventOverflow:
245 default:
246 }
247 }
248
249 func darwinWorkspaceEvent(flags uint32) (fsnotify.Op, bool) {
250 var op fsnotify.Op
251 if flags&darwinFSEventItemRemoved != 0 {
252 op |= fsnotify.Remove
253 }
254 if flags&darwinFSEventItemRenamed != 0 {
255 op |= fsnotify.Rename
256 }
257 if flags&darwinFSEventItemCreated != 0 {
258 op |= fsnotify.Create
259 }
260 const writeFlags = darwinFSEventItemModified |
261 darwinFSEventItemInodeMeta |
262 darwinFSEventItemFinderInfo |
263 darwinFSEventItemXattr |
264 darwinFSEventItemChangeOwner
265 if flags&writeFlags != 0 {
266 op |= fsnotify.Write
267 }
268
269 overflowFlags := darwinFSEventMustScanSubDirs |
270 darwinFSEventUserDropped |
271 darwinFSEventKernelDropped |
272 darwinFSEventEventIDsWrapped |
273 darwinFSEventMount |
274 darwinFSEventUnmount
275 overflow := flags&overflowFlags != 0
276 if flags&darwinFSEventRootChanged != 0 {
277 op |= fsnotify.Rename
278 overflow = true
279 }
280 return op, overflow
281 }
282
283 func darwinFSEventsStartError(code int) string {
284 switch code {
285 case 1:
286 return "invalid filesystem path"
287 case 2:
288 return "allocation failed"
289 case 3:
290 return "dispatch queue creation failed"
291 case 4:
292 return "FSEventStream creation failed"
293 case 5:
294 return "FSEventStream start failed"
295 default:
296 return fmt.Sprintf("native error %d", code)
297 }
298 }
299
299 lines GO