rb-fsevent-legacy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Thibaud Guillaume-Gentil
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = rb-fsevent
2
+
3
+ Very simple & usable Mac OSX FSEvents API
4
+
5
+ - RubyCocoa not required!
6
+ - Signals are working (really) -- I don't know if they are or not.
7
+ - Tested on MRI 1.8.7 & 1.9.2, JRuby 1.6.3
8
+ - Tested on 10.5.8
9
+ - Works on PowerPC
10
+
11
+ == Install
12
+
13
+ gem install rb-fsevent-legacy
14
+
15
+ == Usage
16
+
17
+ === Singular path
18
+
19
+ require 'rb-fsevent-legacy'
20
+
21
+ fsevent = FSEvent.new(Dir.pwd)
22
+
23
+ #some changes happen here, other program code, etcj
24
+
25
+ fsevent.events do |e|
26
+ puts "Detected change inside: #{e.event_path}"
27
+ end
28
+
29
+ === Multiple paths
30
+
31
+ require 'rb-fsevent'
32
+
33
+ paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd]
34
+
35
+ fsevent = FSEvent.new
36
+ fsevent.add_paths(paths)
37
+ fsevent.watch do |e|
38
+ puts "Detected change inside: #{e.event_path}"
39
+ end
40
+
41
+ #some things happen
42
+
43
+ fsevent.events.collect { |e| e.event_path }
44
+
45
+ === Threads
46
+
47
+ The native stream runs in it's own (isolated) CoreFoundation thread, which is started as soon as the gem
48
+ is loaded and it terminated when the program stops. You may check for changes as often as you like in
49
+ ruby loop, or from an additional Ruby thread.
50
+
51
+
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake/clean'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec'
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ task :default => :spec
9
+
10
+ file "lib/rb-fsevent-legacy/fs_native_stream.bundle" => Dir.glob("ext/rb-fsevent-legacy/*{.rb,.c}") do
11
+ Dir.chdir("ext/rb-fsevent-legacy") do
12
+ ruby "extconf.rb"
13
+ sh "make"
14
+ end
15
+ cp "ext/rb-fsevent-legacy/fs_native_stream.bundle", "lib/rb-fsevent-legacy/fs_native_stream.bundle"
16
+ end
17
+
18
+ task :spec => "lib/rb-fsevent-legacy/fs_native_stream.bundle"
19
+
20
+ CLEAN.include('ext/**/*{.o,.log,.bundle}')
21
+ CLEAN.include('ext/**/Makefile')
22
+ CLOBBER.include('lib/**/*.bundle')
23
+
24
+ namespace(:spec) do
25
+ desc "Run all specs on multiple ruby versions (requires rbenv)"
26
+ task(:portability) do
27
+ %w[1.8.6 1.8.7 1.9.2 jruby-head].each do |version|
28
+ system <<-BASH
29
+ bash -c 'source ~/.rvm/scripts/rvm;
30
+ rvm #{version};
31
+ echo "--------- version #{version} ----------\n";
32
+ bundle install;
33
+ rake spec'
34
+ BASH
35
+ end
36
+ end
37
+ desc "run all specs with rcov"
38
+ RSpec::Core::RakeTask.new(:coverage) do |t|
39
+ t.rcov = true
40
+ t.rcov_opts = %q[--exclude "spec"]
41
+ t.verbose = true
42
+ end
43
+ end
@@ -0,0 +1,12 @@
1
+ require 'mkmf'
2
+
3
+ if RUBY_PLATFORM.downcase.include?("darwin")
4
+ with_ldflags($LDFLAGS + ' -framework Foundation') { true }
5
+ with_ldflags($LDFLAGS + ' -framework CoreServices') { true }
6
+ else
7
+ raise "Compiling this for something other than OSX doesn't make sense."
8
+ end
9
+
10
+ $CFLAGS="-g -O0 -pipe -fno-common"
11
+
12
+ create_makefile("rb-fsevent-legacy/fs_native_stream")
@@ -0,0 +1,442 @@
1
+ #include <CoreFoundation/CoreFoundation.h>
2
+ #include <CoreServices/CoreServices.h>
3
+
4
+ #include <ruby.h>
5
+ #include <pthread.h>
6
+ #include <stdlib.h>
7
+
8
+ #define COMPILED_AT __DATE__ " " __TIME__
9
+
10
+ static VALUE cFSEventSystemStream;
11
+
12
+ typedef struct cFSEventEventType {
13
+ char * path;
14
+ long eventFlags;
15
+ unsigned int long eventId;
16
+ } cFSEventEvent;
17
+
18
+ typedef struct cFSEventSystemStreamType {
19
+ cFSEventEvent *events; // pointer to an arry of events
20
+ int numEvents;
21
+ double latency;
22
+ VALUE path;
23
+ VALUE running;
24
+ FSEventStreamRef native_stream;
25
+ } cFSEventSystemStreamType;
26
+
27
+ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
28
+ pthread_t FSEventThread;
29
+ int semaphore = 0;
30
+
31
+ CFRunLoopRef FSEventRunLoop;
32
+ int timer_passes;
33
+ VALUE last_events;
34
+ VALUE rbFSEventModule;
35
+
36
+ void *fs_rb_cf_allocate(CFIndex size, CFOptionFlags hint, void *info);
37
+ void *fs_rb_cf_reallocate(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info);
38
+ void fs_rb_cf_deallocate(void *ptr, void *info);
39
+ void fs_callback( const FSEventStreamRef streamRef, void *callbackInfo, size_t numEvents, void *eventPaths,
40
+ const FSEventStreamEventFlags eventFlags[],
41
+ const FSEventStreamEventId eventIds[]);
42
+ static VALUE fs_init(VALUE self, VALUE directory, VALUE latency);
43
+ void *fs_runloop();
44
+
45
+ // We need to create a custom allocator so that we place nice with
46
+ // ruby's GC and allocation practices
47
+ static CFAllocatorRef rb_CFAllocator(void) {
48
+ static CFAllocatorRef allocator = NULL;
49
+ if(!allocator) {
50
+ CFAllocatorContext context = {
51
+ 0, // CFIndex Version - always 0
52
+ NULL, // void * to context info
53
+ NULL, // retain callback insn't necessary
54
+ NULL, // release callback isn't necessary
55
+ NULL, // a callback which returns a description based on info, so NULL here too.
56
+ fs_rb_cf_allocate, // core foundation allocation functions which play nice
57
+ fs_rb_cf_reallocate, // with the ruby gc
58
+ fs_rb_cf_deallocate,
59
+ NULL // the preferred size allocater
60
+ };
61
+ allocator = CFAllocatorCreate(NULL, &context);
62
+ }
63
+ return allocator;
64
+ }
65
+
66
+ void * fs_rb_cf_allocate(CFIndex size, CFOptionFlags hint, void *info){
67
+ // hint and info are always going to bu null in our case.
68
+ // okay, so the size here is an int of BYTES. ruby's ALLOC_N macro
69
+ // requires that we allocate by pointer type. I think this
70
+ // is fairly cross platform - but I'm not sure.
71
+ ALLOC_N(char,size);
72
+ }
73
+ void * fs_rb_cf_reallocate(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info){
74
+ // hint and info are always going to bu null in our case.
75
+ REALLOC_N(ptr, char, newsize);
76
+ }
77
+
78
+ void fs_rb_cf_deallocate(void *ptr, void *info){
79
+ xfree(ptr);
80
+ }
81
+
82
+ //function prototypes
83
+ //
84
+
85
+ // AND GO.
86
+ //
87
+ void fs_timer(){
88
+ #ifdef DEBUG
89
+ printf("Timer Fire\n");
90
+ #endif
91
+ pthread_mutex_lock(&mutex);
92
+ timer_passes++;
93
+ pthread_mutex_unlock(&mutex);
94
+ }
95
+
96
+ // so OSX doesn't actually have posix semaphores, and i can't make
97
+ // named sempahore work, so implement our own, rather stupid
98
+ // version of them.
99
+ void fs_wait_for_unlock(){
100
+ int deadlock = 0;
101
+ // really, it should never wait that long
102
+ while(semaphore < 0 && deadlock < 32767) { deadlock++; }
103
+ pthread_mutex_lock(&mutex);
104
+ semaphore = -1;
105
+ pthread_mutex_unlock(&mutex);
106
+ }
107
+
108
+ void fs_unlock(){
109
+ pthread_mutex_lock(&mutex);
110
+ semaphore = 0;
111
+ pthread_mutex_unlock(&mutex);
112
+ }
113
+
114
+ void fs_callback(
115
+ __attribute__((unused)) const FSEventStreamRef streamRef,
116
+ void *callbackInfo,
117
+ size_t numEvents,
118
+ void *eventPaths,
119
+ const FSEventStreamEventFlags eventFlags[],
120
+ const FSEventStreamEventId eventIds[]){
121
+
122
+ // this is actually running on our pthread.
123
+ // ruby is not NOT pthread aware or safe, we we
124
+ // lockout everything that could happen from
125
+ // calling ruby in this thread.
126
+
127
+ // this is only going to do anything
128
+ // if there's data corruption, in which case we probably
129
+ // want to bail out anyway.
130
+
131
+ // the situation is shutting down, so we don't really care
132
+ if(FSEventRunLoop == false){
133
+ #ifdef DEBUG
134
+ printf("CoreFoundation callback stopped\n");
135
+ #endif
136
+ return;
137
+ }
138
+
139
+ pthread_mutex_lock(&mutex);
140
+
141
+ #ifdef DEBUG
142
+ printf("CoreFoundation callback enter for stream <#FSEvent::NativeStream 0x%x>\n", (VALUE)callbackInfo * 2);fflush(stdout);
143
+ #endif
144
+ // we do object allocation here in another thread
145
+ // so mutex it incase the GC is running
146
+ rb_gc_disable();
147
+
148
+ int start_dex;
149
+ char **paths = eventPaths;
150
+ // #define REALLOC_N(var,type,n) (var)=(type*)xrealloc((char*)(var),sizeof(type)*(n))
151
+ // make a new FSEvent ruby object
152
+ cFSEventSystemStreamType *obj;
153
+ Check_Type((VALUE)callbackInfo, T_DATA);
154
+ obj = (cFSEventSystemStreamType *)DATA_PTR((VALUE)callbackInfo);
155
+
156
+ if(obj->numEvents > 0){
157
+ #ifdef DEBUG
158
+ printf("Adding %d to %d existing events\n", numEvents, obj->numEvents);
159
+ #endif
160
+ start_dex = obj->numEvents;
161
+ obj->numEvents = obj->numEvents + numEvents;
162
+ } else {
163
+ start_dex = 0;
164
+ obj->numEvents = numEvents;
165
+ }
166
+
167
+ // just always do realloc - since it will act as an malloc
168
+ // if we've already nulled the pointer
169
+ #ifdef DEBUG
170
+ printf("Allocating for %d events\n", obj->numEvents);
171
+ #endif
172
+ cFSEventEvent *old = obj->events; // yes, i want to store the address.
173
+ obj->events = (cFSEventEvent *)realloc(obj->events, sizeof(cFSEventEvent) * (int)obj->numEvents);
174
+
175
+
176
+ if(obj->events == NULL){
177
+ rb_bug("Couldn't malloc events from secondary watch thread");
178
+ }
179
+
180
+ int i,j;
181
+
182
+ for(i = start_dex, j = 0; j < numEvents; i++, j++){
183
+ obj->events[i].path = malloc(strlen(paths[j])+1);
184
+ if(obj->events[i].path == NULL){
185
+ rb_bug("Couldn't malloc path from secondary watch thread");
186
+ }
187
+ strcpy(obj->events[i].path,paths[j]);
188
+ obj->events[i].eventFlags = eventFlags[i];
189
+ obj->events[i].eventId = eventIds[i];
190
+ }
191
+ rb_gc_enable();
192
+
193
+ #ifdef DEBUG
194
+ printf("CoreFoundation callback exited\n");
195
+ #endif
196
+
197
+ pthread_mutex_unlock(&mutex);
198
+ }
199
+
200
+ static VALUE fs_timer_events(VALUE self){
201
+ #ifdef DEBUG
202
+ printf("Timer value retrieved\n"); fflush(stdout);
203
+ #endif
204
+
205
+ VALUE timer;
206
+ pthread_mutex_lock(&mutex);
207
+ timer = INT2FIX(timer_passes);
208
+ pthread_mutex_unlock(&mutex);
209
+ return timer;
210
+ }
211
+
212
+ static VALUE fs_events(VALUE self) {
213
+ // this should be running on the main ruby thread
214
+ pthread_mutex_lock(&mutex);
215
+ #ifdef DEBUG
216
+ printf("Ruby thread event enter for <#FSEvent::NativeStream 0x%x>\n",(VALUE)self * 2); fflush(stdout);
217
+ #endif
218
+
219
+ cFSEventSystemStreamType *obj;
220
+ // DataGetStruct(self,cFSEventSystemStreamType,obj);
221
+ Check_Type(self, T_DATA);
222
+ obj = (cFSEventSystemStreamType *)DATA_PTR(self);
223
+
224
+ if(obj->numEvents == 0){
225
+ #ifdef DEBUG
226
+ printf("Ruby thread event break\n"); fflush(stdout);
227
+ #endif
228
+ pthread_mutex_unlock(&mutex);
229
+ return Qnil;
230
+ }
231
+
232
+ volatile VALUE FSEvent = rb_const_get(rbFSEventModule,rb_intern("Event"));
233
+
234
+ if(rb_block_given_p()) {
235
+ int len = obj->numEvents;
236
+ int i;
237
+ #ifdef DEBUG
238
+ printf("Yielding %d values\n",obj->numEvents);
239
+ #endif
240
+ for(i=0;i<len;i++){
241
+ volatile VALUE fs_event = rb_funcall(FSEvent,rb_intern("new"),0);
242
+ rb_funcall(fs_event,rb_intern("event_id="),1,ULL2NUM(obj->events[i].eventId));
243
+ rb_funcall(fs_event,rb_intern("event_path="),1,rb_str_new2(obj->events[i].path));
244
+ rb_funcall(fs_event,rb_intern("event_flags="),1,LONG2NUM(obj->events[i].eventFlags));
245
+
246
+ #ifdef DEBUG
247
+ printf("Ruby thread event yield\n"); fflush(stdout);
248
+ #endif
249
+
250
+ rb_yield(fs_event);
251
+ }
252
+
253
+ // delete all of the events
254
+ obj->numEvents = 0;
255
+ free(obj->events);
256
+ obj->events = NULL;
257
+ }
258
+ #ifdef DEBUG
259
+ printf("Ruby thread event exit\n"); fflush(stdout);
260
+ #endif
261
+
262
+ pthread_mutex_unlock(&mutex);
263
+ return Qnil;
264
+ }
265
+
266
+ static VALUE fs_running(VALUE self){
267
+ cFSEventSystemStreamType *obj;
268
+ Check_Type(self, T_DATA);
269
+ obj = (cFSEventSystemStreamType *)DATA_PTR(self);
270
+ return (VALUE)obj->running;
271
+ }
272
+
273
+ static VALUE fs_path(VALUE self){
274
+ cFSEventSystemStreamType *obj;
275
+ Check_Type(self, T_DATA);
276
+ obj = (cFSEventSystemStreamType *)DATA_PTR(self);
277
+ return (VALUE)obj->path;
278
+ }
279
+
280
+ // create a new FS Event Stream
281
+ FSEventStreamRef fs_new_stream(char *dir, VALUE *self) {
282
+ // always runs on the ruby thread.
283
+ //
284
+ cFSEventSystemStreamType *obj;
285
+ Check_Type(self, T_DATA);
286
+ obj = (cFSEventSystemStreamType *)DATA_PTR(self);
287
+
288
+ CFStringRef mypath = CFStringCreateWithCString(NULL,dir,CFStringGetSystemEncoding());
289
+ CFArrayRef paths = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL);
290
+ FSEventStreamContext context = {0,self,NULL,NULL,NULL};
291
+ FSEventStreamRef stream;
292
+ CFAbsoluteTime latency = obj->latency;
293
+
294
+ stream = FSEventStreamCreate(
295
+ rb_CFAllocator(), // use the customer corefoundation allocator...
296
+ (FSEventStreamCallback)&fs_callback,
297
+ &context,
298
+ paths,
299
+ kFSEventStreamEventIdSinceNow, //allow to replace with a meaningufl "since"
300
+ latency,
301
+ kFSEventStreamCreateFlagNone //look this up
302
+ );
303
+ return stream;
304
+ }
305
+
306
+ static void fs_free(cFSEventSystemStreamType *me){
307
+ //unconditionally free unprocessed events
308
+ #ifdef DEBUG
309
+ printf("GC Free for native stream\n"); fflush(stdout);
310
+ #endif
311
+
312
+ pthread_mutex_lock(&mutex);
313
+ if(me->running == Qtrue){
314
+
315
+ #ifdef DEBUG
316
+ printf("Discarding %d events.\n",me->numEvents); fflush(stdout);
317
+ #endif
318
+ // we only get called from ruby's GC - but it's possible
319
+ // for this object to be GCd after the runloop itself has been
320
+ // so check for that situation
321
+ FSEventStreamFlushSync(me->native_stream);
322
+ FSEventStreamStop(me->native_stream);
323
+ FSEventStreamInvalidate(me->native_stream);
324
+ }
325
+ free(me->events);
326
+ FSEventStreamRelease(me->native_stream);
327
+ xfree(me);
328
+ pthread_mutex_unlock(&mutex);
329
+ }
330
+
331
+ static void fs_mark(cFSEventSystemStreamType *me){
332
+ #ifdef DEBUG
333
+ printf("GC Mark for native stream\n"); fflush(stdout);
334
+ #endif
335
+
336
+ rb_gc_mark(me->path);
337
+ rb_gc_mark(me->running); // i think that's extraneous - but mark it just to be safe.
338
+ }
339
+
340
+ static VALUE fs_stop(VALUE self){
341
+ #ifdef DEBUG
342
+ printf("Native stream stopping #<FSEvent::NativeStream 0x%x>\n",self * 2); fflush(stdout);
343
+ #endif
344
+
345
+ cFSEventSystemStreamType *obj;
346
+ Check_Type(self, T_DATA);
347
+ obj = (cFSEventSystemStreamType *)DATA_PTR(self);
348
+
349
+ if(obj->running == Qfalse){
350
+ VALUE excp = rb_const_get(rb_cObject,rb_intern("RuntimeError"));
351
+ rb_raise(excp, "native stream watcher was stopped twice");
352
+ }
353
+
354
+ FSEventStreamFlushSync(obj->native_stream);
355
+ FSEventStreamStop(obj->native_stream);
356
+ FSEventStreamInvalidate(obj->native_stream);
357
+ obj->running = Qfalse;
358
+
359
+ return obj->running;
360
+ }
361
+
362
+ static VALUE fs_alloc(VALUE klass){
363
+ // create and assign an empty eventstream.
364
+ #ifdef DEBUG
365
+ printf("GC Alloc for native stream\n"); fflush(stdout);
366
+ #endif
367
+ cFSEventSystemStreamType *obj;
368
+ return Data_Make_Struct(klass,cFSEventSystemStreamType,fs_mark,fs_free,obj);
369
+ }
370
+
371
+ static VALUE fs_init(VALUE self, VALUE directory, VALUE latency) {
372
+ char *dir;
373
+ cFSEventSystemStreamType *obj;
374
+ /* obj, type, sval */
375
+ //DataGetStruct(self,cFSEventSystemStreamType,obj);
376
+ Check_Type(self, T_DATA);
377
+ obj = (cFSEventSystemStreamType *)DATA_PTR(self);
378
+
379
+ dir = RSTRING_PTR(StringValue(directory));
380
+ obj->latency = NUM2DBL(latency);
381
+ obj->native_stream = fs_new_stream(dir,(VALUE *)self);
382
+ obj->events = NULL;
383
+ obj->path = directory;
384
+ obj->numEvents = 0;
385
+
386
+
387
+ FSEventStreamScheduleWithRunLoop(obj->native_stream, FSEventRunLoop, kCFRunLoopDefaultMode);
388
+ FSEventStreamStart(obj->native_stream);
389
+
390
+ obj->running = Qtrue;
391
+
392
+ return self;
393
+ }
394
+
395
+ void *fs_runloop() {
396
+ FSEventRunLoop = CFRunLoopGetCurrent();
397
+ // create a one second timer to keep the event loop running even
398
+ // if we haven't actually added an FSEventStream yet.
399
+ CFRunLoopTimerRef timer = CFRunLoopTimerCreate(
400
+ NULL,
401
+ (CFAbsoluteTime)0,
402
+ (CFTimeInterval)1.0, //why not.
403
+ 0,
404
+ 0,
405
+ &fs_timer,
406
+ NULL
407
+ );
408
+ CFRunLoopAddTimer(FSEventRunLoop,timer,kCFRunLoopCommonModes);
409
+ #ifdef DEBUG
410
+ printf("Starting CFRunLoop\n"); fflush(stdout);
411
+ #endif
412
+ // this will not get set until the runloop terminates
413
+ // when it will show the reason for termination
414
+ CFRunLoopRun();
415
+ FSEventRunLoop = false;
416
+
417
+ // do we need to do any cleanup from our thread?
418
+ }
419
+
420
+ static void fs_stoploop(VALUE dummy) {
421
+ #ifdef DEBUG
422
+ printf("Stopping CFRunLoop\n"); fflush(stdout);
423
+ #endif
424
+ CFRunLoopStop(FSEventRunLoop);
425
+ }
426
+
427
+ void Init_fs_native_stream(){
428
+ rbFSEventModule = rb_const_get(rb_cObject,rb_intern("FSEvent"));
429
+ cFSEventSystemStream = rb_define_class_under(rbFSEventModule, "NativeStream", rb_cObject);
430
+ rb_define_alloc_func(cFSEventSystemStream,fs_alloc);
431
+ rb_define_method(cFSEventSystemStream,"initialize",fs_init,2);
432
+ rb_define_method(cFSEventSystemStream,"events",fs_events,0);
433
+ rb_define_method(cFSEventSystemStream,"path",fs_path,0);
434
+ rb_define_method(cFSEventSystemStream,"running?",fs_running,0);
435
+ rb_define_method(cFSEventSystemStream,"stop",fs_stop,0);
436
+
437
+ rb_define_singleton_method(cFSEventSystemStream,"timer_events",fs_timer_events,0);
438
+
439
+ // we create a new pthread that we manage
440
+ pthread_create(&FSEventThread,NULL,&fs_runloop,NULL);
441
+ rb_set_end_proc(fs_stoploop,0);
442
+ }
@@ -0,0 +1,17 @@
1
+ module FSEvent
2
+ class Event
3
+ #an ACTUAL filesystem event
4
+ attr_accessor :event_path, :event_flags, :event_id
5
+
6
+ def to_s
7
+ "#<%s:0x%x event_path='%s' event_flags='%s' event_id='%s'>" \
8
+ % [self.class, self.object_id.abs * 2,self.event_path, self.event_flags, self.event_id]
9
+ end
10
+
11
+ def event_flag
12
+ # honestly, since this is the legacy system i'm not sure this is
13
+ # ever anything but zero. so lazily do nothing until i know otherwise
14
+ # break out the event flags into the something meaningful
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,108 @@
1
+ module FSEvent
2
+ class Stream
3
+ class << self
4
+ def running?
5
+ if NativeStream.timer_events > 0 then true end
6
+ end
7
+ end
8
+
9
+ #the callback recieves FSEvent objects which describe the events
10
+ def initialize(path = nil, latency = 1.0)
11
+ unless path.nil?
12
+ @streams = [NativeStream.new(path,Float(latency))]
13
+ else
14
+ @streams = []
15
+ end
16
+ @run = false;
17
+ end
18
+
19
+ def latency
20
+ @latency || 1.0
21
+ end
22
+
23
+ def latency= val
24
+ @latency = Float(val)
25
+ end
26
+
27
+ def watch path = nil, &block
28
+ @streams.push(NativeStream.new(path,self.latency)) unless path.nil?
29
+ @call_block = block
30
+ self
31
+ end
32
+
33
+ def empty
34
+ @streams.each { |s| s.stop }.delete_if { |s| not s.running? }
35
+ end
36
+
37
+ def paths
38
+ @streams.collect { |s| s.path }
39
+ end
40
+
41
+ def add_paths paths
42
+ paths.each do |p|
43
+ self.add_path p
44
+ end
45
+ end
46
+
47
+ def add_path path
48
+ @streams.push(NativeStream.new(path,self.latency))
49
+ end
50
+
51
+ def status
52
+ status = @streams.collect do |s|
53
+ s.running?
54
+ end
55
+ end
56
+
57
+ def running?
58
+ status = @streams.collect { |s| s.running? }
59
+ if not status.include? true
60
+ false
61
+ elsif not status.include? false
62
+ true
63
+ else
64
+ raise StandardError, "native streams are in mixed states"
65
+ end
66
+ end
67
+
68
+ def killall
69
+ @streams.each do |s|
70
+ s.stop
71
+ end
72
+ end
73
+
74
+ def streams
75
+ @streams
76
+ end
77
+
78
+ def events
79
+ events = []
80
+ @streams.each do |s|
81
+ s.events do |e|
82
+ if block_given?
83
+ events << (yield e)
84
+ else
85
+ events << e
86
+ end
87
+ end
88
+ end
89
+ events.compact
90
+ end
91
+
92
+ def run
93
+ @run = true
94
+ while @run do
95
+ self.events do |e|
96
+ if @call_block
97
+ @call_block.call(e)
98
+ end
99
+ end
100
+ sleep 1
101
+ end
102
+ end
103
+
104
+ def stop
105
+ @run = false
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,3 @@
1
+ module FSEvent
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "rb-fsevent-legacy/version"
2
+
3
+ module FSEvent
4
+ require "rb-fsevent-legacy/stream"
5
+ require "rb-fsevent-legacy/event"
6
+ require "rb-fsevent-legacy/fs_native_stream"
7
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rb-fsevent-legacy/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rb-fsevent-legacy"
7
+ s.version = FSEvent::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Stephen Prater']
10
+ s.email = ['me@stephenprater.com']
11
+ s.homepage = "http://github.com/stephenprater/rb-fsevents-legacy"
12
+ s.summary = "Very simple & usable FSEvents API - it works on PowerPCs and 10.5.8"
13
+ s.description = "A legacy compatible version of an FSEvents API for Darwin."
14
+
15
+ s.rubyforge_project = "rb-fsevent-legacy"
16
+
17
+ s.add_development_dependency 'bundler', '~> 1.0.10'
18
+ s.add_development_dependency 'rspec', '~> 2.5.0'
19
+ s.add_development_dependency 'rcov'
20
+ s.add_development_dependency 'pry'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.extensions = ['ext/rb-fsevent-legacy/extconf.rb']
24
+ s.require_paths = ['lib','ext']
25
+ end
26
+
File without changes
File without changes
File without changes
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe FSEvent do
4
+
5
+ it "should automatically start the FSEvent native thread" do
6
+ fsevent = FSEvent::Stream.new(@fixture_path, 0.1)
7
+ fsevent.running?.should be_true
8
+ fsevent.streams.length.should == 1
9
+ fsevent.killall
10
+ end
11
+
12
+ it "can start and stop native FSEvent watchers" do
13
+ fsevent = FSEvent::Stream.new(@fixture_path, 0.1)
14
+ fsevent.running?.should be_true
15
+ fsevent.streams.length.should == 1
16
+ fsevent.killall
17
+ fsevent.running?.should be_false
18
+
19
+ file = @fixture_path.join("newfile#{rand(10).to_s}.rb")
20
+ FileUtils.touch file
21
+ sleep 0.2
22
+
23
+ # that event should NOT show up because watcher is stopped
24
+ fsevent.events do |e|
25
+ raise "event block called even though events should return an empty array"
26
+ end.should == []
27
+
28
+ event = FSEvent::Stream.new(@fixture_path,0.1)
29
+ event.running?.should be_true
30
+
31
+ event.events.should == []
32
+
33
+ event.killall
34
+ end
35
+
36
+ it "can only stop it once" do
37
+ fsevent = FSEvent::Stream.new(@fixture_path,0.1)
38
+ fsevent.killall
39
+ lambda { fsevent.killall }.should raise_error RuntimeError
40
+ end
41
+
42
+ it "has a long running thread timer" do
43
+ FSEvent::Stream.running?.should be_true
44
+ sleep 1
45
+ FSEvent::NativeStream.timer_events.should > 1
46
+ end
47
+
48
+ it "shouldn't throw an error when it gets GC'd" do
49
+ #basically, if this doesn't throw a bus error it probably worked.
50
+ #you can ifdef the debugging output in the c-extension if you want
51
+ #to futz about with it.
52
+ local_event = FSEvent::Stream.new(@fixture_path,0.1)
53
+ local_event.running?.should be_true
54
+ local_event = nil
55
+ GC.start
56
+ end
57
+
58
+ it "should work with path with an apostrophe" do
59
+ fsevent = FSEvent::Stream.new(@fixture_path, 0.1)
60
+ custom_path = @fixture_path.join("custom 'path")
61
+ file = custom_path.join("newfile#{rand(10)}.rb").to_s
62
+
63
+ fsevent.add_path custom_path
64
+ fsevent.paths.should include("#{custom_path}")
65
+ FileUtils.touch file
66
+
67
+ sleep 0.2
68
+
69
+ fsevent.events.collect do |e|
70
+ e.event_path
71
+ end.should include(custom_path.to_s + '/')
72
+
73
+ File.delete file
74
+ fsevent.killall
75
+ end
76
+
77
+ it "should catch new file" do
78
+ fsevent = FSEvent::Stream.new(@fixture_path, 0.1)
79
+ file = @fixture_path.join("newfile#{rand(10).to_s}.rb")
80
+
81
+ FileUtils.touch file
82
+ sleep 0.2
83
+ File.delete file
84
+
85
+ fsevent.events.collect do |e|
86
+ e.event_path
87
+ end.should include(@fixture_path.to_s + '/')
88
+ fsevent.killall
89
+ end
90
+
91
+ it "should catch file update" do
92
+ fsevent = FSEvent::Stream.new(@fixture_path, 0.1)
93
+ file = @fixture_path.join("folder1/file1.txt")
94
+ File.exists?(file).should be_true
95
+ FileUtils.touch file
96
+ sleep 0.2
97
+
98
+ fsevent.events.collect do |e|
99
+ e.event_path
100
+ end.should include(@fixture_path.join("folder1/").to_s)
101
+ fsevent.killall
102
+ end
103
+
104
+ it "should store events until called" do
105
+ fsevent = FSEvent::Stream.new(@fixture_path, 0.1)
106
+ file1 = @fixture_path.join("folder1/file3.txt")
107
+ file2 = @fixture_path.join("folder1/folder2/file2.txt")
108
+ File.exists?(file1).should be_true
109
+ File.exists?(file2).should be_true
110
+ FileUtils.touch file1
111
+ sleep 0.5
112
+ FileUtils.touch file2
113
+ sleep 0.2
114
+
115
+ fsevent.events.collect do |e|
116
+ e.event_path
117
+ end.should == [@fixture_path.join("folder1/").to_s, @fixture_path.join("folder1/folder2/").to_s]
118
+ fsevent.killall
119
+ end
120
+
121
+ it "will run in a separate thread" do
122
+ fsevent = nil
123
+ t = Thread.new do
124
+ fsevent = FSEvent::Stream.new(@fixture_path,0.1)
125
+ fsevent.run
126
+ end
127
+
128
+
129
+ file1 = @fixture_path.join("folder1/file1.txt")
130
+ file2 = @fixture_path.join("folder1/folder2/file2.txt")
131
+
132
+ FileUtils.touch file1
133
+
134
+ sleep 0.2
135
+
136
+ fsevent.events.collect do |e|
137
+ e.event_path
138
+ end.should include(@fixture_path.join("folder1/").to_s)
139
+
140
+ FileUtils.touch file2
141
+
142
+ sleep 0.2
143
+
144
+ fsevent.events.collect do |e|
145
+ e.event_path
146
+ end.should include(@fixture_path.join("folder1/folder2/").to_s)
147
+ end
148
+ end
@@ -0,0 +1,16 @@
1
+ require 'rspec'
2
+ require 'rb-fsevent-legacy'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+
7
+ config.before(:each) do
8
+ @fixture_path = Pathname.new(File.expand_path('../fixtures/', __FILE__))
9
+ end
10
+
11
+ config.after(:all) do
12
+ gem_root = Pathname.new(File.expand_path('../../', __FILE__))
13
+ system "rm -rf #{gem_root.join('bin')}"
14
+ end
15
+
16
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rb-fsevent-legacy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Stephen Prater
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-27 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 1
31
+ - 0
32
+ - 10
33
+ version: 1.0.10
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 27
45
+ segments:
46
+ - 2
47
+ - 5
48
+ - 0
49
+ version: 2.5.0
50
+ type: :development
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rcov
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: pry
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ description: A legacy compatible version of an FSEvents API for Darwin.
81
+ email:
82
+ - me@stephenprater.com
83
+ executables: []
84
+
85
+ extensions:
86
+ - ext/rb-fsevent-legacy/extconf.rb
87
+ extra_rdoc_files: []
88
+
89
+ files:
90
+ - Gemfile
91
+ - LICENSE
92
+ - README.rdoc
93
+ - Rakefile
94
+ - ext/rb-fsevent-legacy/extconf.rb
95
+ - ext/rb-fsevent-legacy/fs_native_stream.c
96
+ - lib/rb-fsevent-legacy.rb
97
+ - lib/rb-fsevent-legacy/event.rb
98
+ - lib/rb-fsevent-legacy/stream.rb
99
+ - lib/rb-fsevent-legacy/version.rb
100
+ - rb-fsevent-legacy.gemspec
101
+ - spec/fixtures/folder1/file1.txt
102
+ - spec/fixtures/folder1/file3.txt
103
+ - spec/fixtures/folder1/folder2/file2.txt
104
+ - spec/rb-fsevent/fsevent_spec.rb
105
+ - spec/spec_helper.rb
106
+ homepage: http://github.com/stephenprater/rb-fsevents-legacy
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ - ext
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ hash: 3
121
+ segments:
122
+ - 0
123
+ version: "0"
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ hash: 3
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ requirements: []
134
+
135
+ rubyforge_project: rb-fsevent-legacy
136
+ rubygems_version: 1.8.16
137
+ signing_key:
138
+ specification_version: 3
139
+ summary: Very simple & usable FSEvents API - it works on PowerPCs and 10.5.8
140
+ test_files: []
141
+