OSDN Git Service

new repo
[bytom/vapor.git] / vendor / github.com / rjeczalik / notify / watcher_fsevents_cgo.go
1 // Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
2 // Use of this source code is governed by the MIT license that can be
3 // found in the LICENSE file.
4
5 // +build darwin,!kqueue
6
7 package notify
8
9 /*
10 #include <CoreServices/CoreServices.h>
11
12 typedef void (*CFRunLoopPerformCallBack)(void*);
13
14 void gosource(void *);
15 void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);
16
17 static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
18         context->info = (void*) info;
19         return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
20 }
21
22 #cgo LDFLAGS: -framework CoreServices
23 */
24 import "C"
25
26 import (
27         "errors"
28         "os"
29         "sync"
30         "sync/atomic"
31         "time"
32         "unsafe"
33 )
34
35 var nilstream C.FSEventStreamRef
36
37 // Default arguments for FSEventStreamCreate function.
38 var (
39         latency C.CFTimeInterval
40         flags   = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
41         since   = uint64(C.FSEventsGetCurrentEventId())
42 )
43
44 var runloop C.CFRunLoopRef // global runloop which all streams are registered with
45 var wg sync.WaitGroup      // used to wait until the runloop starts
46
47 // source is used for synchronization purposes - it signals when runloop has
48 // started and is ready via the wg. It also serves purpose of a dummy source,
49 // thanks to it the runloop does not return as it also has at least one source
50 // registered.
51 var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{
52         perform: (C.CFRunLoopPerformCallBack)(C.gosource),
53 })
54
55 // Errors returned when FSEvents functions fail.
56 var (
57         errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
58         errStart  = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
59 )
60
61 // initializes the global runloop and ensures any created stream awaits its
62 // readiness.
63 func init() {
64         wg.Add(1)
65         go func() {
66                 runloop = C.CFRunLoopGetCurrent()
67                 C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
68                 C.CFRunLoopRun()
69                 panic("runloop has just unexpectedly stopped")
70         }()
71         C.CFRunLoopSourceSignal(source)
72 }
73
74 //export gosource
75 func gosource(unsafe.Pointer) {
76         time.Sleep(time.Second)
77         wg.Done()
78 }
79
80 //export gostream
81 func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
82         const (
83                 offchar = unsafe.Sizeof((*C.char)(nil))
84                 offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
85                 offid   = unsafe.Sizeof(C.FSEventStreamEventId(0))
86         )
87         if n == 0 {
88                 return
89         }
90         ev := make([]FSEvent, 0, int(n))
91         for i := uintptr(0); i < uintptr(n); i++ {
92                 switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
93                 case flags&uint32(FSEventsEventIdsWrapped) != 0:
94                         atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
95                 default:
96                         ev = append(ev, FSEvent{
97                                 Path:  C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
98                                 Flags: flags,
99                                 ID:    *(*uint64)(unsafe.Pointer(ids + i*offid)),
100                         })
101                 }
102
103         }
104         streamFuncs.get(info)(ev)
105 }
106
107 // StreamFunc is a callback called when stream receives file events.
108 type streamFunc func([]FSEvent)
109
110 var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}
111
112 type streamFuncRegistry struct {
113         mu sync.Mutex
114         m  map[uintptr]streamFunc
115         i  uintptr
116 }
117
118 func (r *streamFuncRegistry) get(id uintptr) streamFunc {
119         r.mu.Lock()
120         defer r.mu.Unlock()
121         return r.m[id]
122 }
123
124 func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
125         r.mu.Lock()
126         defer r.mu.Unlock()
127         r.i++
128         r.m[r.i] = fn
129         return r.i
130 }
131
132 func (r *streamFuncRegistry) delete(id uintptr) {
133         r.mu.Lock()
134         defer r.mu.Unlock()
135         delete(r.m, id)
136 }
137
138 // Stream represents single watch-point which listens for events scheduled by
139 // the global runloop.
140 type stream struct {
141         path string
142         ref  C.FSEventStreamRef
143         info uintptr
144 }
145
146 // NewStream creates a stream for given path, listening for file events and
147 // calling fn upon receiving any.
148 func newStream(path string, fn streamFunc) *stream {
149         return &stream{
150                 path: path,
151                 info: streamFuncs.add(fn),
152         }
153 }
154
155 // Start creates a FSEventStream for the given path and schedules it with
156 // global runloop. It's a nop if the stream was already started.
157 func (s *stream) Start() error {
158         if s.ref != nilstream {
159                 return nil
160         }
161         wg.Wait()
162         p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil)
163         path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
164         ctx := C.FSEventStreamContext{}
165         ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
166         if ref == nilstream {
167                 return errCreate
168         }
169         C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
170         if C.FSEventStreamStart(ref) == C.Boolean(0) {
171                 C.FSEventStreamInvalidate(ref)
172                 return errStart
173         }
174         C.CFRunLoopWakeUp(runloop)
175         s.ref = ref
176         return nil
177 }
178
179 // Stop stops underlying FSEventStream and unregisters it from global runloop.
180 func (s *stream) Stop() {
181         if s.ref == nilstream {
182                 return
183         }
184         wg.Wait()
185         C.FSEventStreamStop(s.ref)
186         C.FSEventStreamInvalidate(s.ref)
187         C.CFRunLoopWakeUp(runloop)
188         s.ref = nilstream
189         streamFuncs.delete(s.info)
190 }