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