rb-fsevent-legacy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+