rb-fsevent 0.3.10 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,8 @@ Very simple & usable Mac OSX FSEvents API
4
4
 
5
5
  - RubyCocoa not required!
6
6
  - Signals are working
7
- - Tested on Ruby 1.8.6, 1.8.7 & 1.9.2
7
+ - Tested on MRI 1.8.6, 1.8.7 & 1.9.2
8
+ - Tested on JRuby 1.6.0 RC3, with experimental (but sufficient) extconf/mkmf support
8
9
 
9
10
  == Install
10
11
 
@@ -12,14 +13,221 @@ Very simple & usable Mac OSX FSEvents API
12
13
 
13
14
  == Usage
14
15
 
16
+ === Singular path
17
+
15
18
  require 'rb-fsevent'
16
-
19
+
17
20
  fsevent = FSEvent.new
18
21
  fsevent.watch Dir.pwd do |directories|
19
22
  puts "Detected change inside: #{directories.inspect}"
20
23
  end
21
24
  fsevent.run
22
25
 
26
+ === Multiple paths
27
+
28
+ require 'rb-fsevent'
29
+
30
+ paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd]
31
+
32
+ fsevent = FSEvent.new
33
+ fsevent.watch paths do |directories|
34
+ puts "Detected change inside: #{directories.inspect}"
35
+ end
36
+ fsevent.run
37
+
38
+ === Multiple paths and additional options as a Hash
39
+
40
+ require 'rb-fsevent'
41
+
42
+ paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd]
43
+ options = {:latency => 1.5, :no_defer => true }
44
+
45
+ fsevent = FSEvent.new
46
+ fsevent.watch paths, options do |directories|
47
+ puts "Detected change inside: #{directories.inspect}"
48
+ end
49
+ fsevent.run
50
+
51
+ === Multiple paths and additional options as an Array
52
+
53
+ require 'rb-fsevent'
54
+
55
+ paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd]
56
+ options = ['--latency', 1.5, '--no-defer']
57
+
58
+ fsevent = FSEvent.new
59
+ fsevent.watch paths, options do |directories|
60
+ puts "Detected change inside: #{directories.inspect}"
61
+ end
62
+ fsevent.run
63
+
64
+ == Options
65
+
66
+ When defining options using a hash or hash-like object, it gets checked for validity and converted to the appropriate fsevent_watch commandline arguments array when the FSEvent class is instantiated. This is obviously the safest and preferred method of passing in options.
67
+
68
+ You may, however, choose to pass in an array of commandline arguments as your options value and it will be passed on, unmodified, to the fsevent_watch binary when called.
69
+
70
+ So far, the following options are supported...
71
+
72
+ - :latency => 0.5 # in seconds
73
+ - :no_defer => true
74
+ - :watch_root => true
75
+ - :since_when => 18446744073709551615 # an FSEventStreamEventId
76
+
77
+ === Latency
78
+
79
+ The :latency parameter determines how long the service should wait after the first event before passing that information along to the client. If your latency is set to 4 seconds, and 300 changes occur in the first three, then the callback will be fired only once. If latency is set to 0.1 in the exact same scenario, you will see that callback fire somewhere closer to between 25 and 30 times.
80
+
81
+ Setting a higher latency value allows for more effective temporal coalescing, resulting in fewer callbacks and greater overall efficiency... at the cost of apparent responsiveness. Setting this to a reasonably high value (and NOT setting :no_defer) is particularly well suited for background, daemon, or batch processing applications.
82
+
83
+ === NoDefer
84
+
85
+ The :no_defer option changes the behavior of the latency parameter completely. Rather than waiting for $latency period of time before sending along events in an attempt to coalesce a potential deluge ahead of time, that first event is sent along to the client immediately and is followed by a $latency period of silence before sending along any additional events that occurred within that period.
86
+
87
+ This behavior is particularly useful for interactive applications where that feeling of apparent responsiveness is most important, but you still don't want to get overwhelmed by a series of events that occur in rapid succession.
88
+
89
+ === WatchRoot
90
+
91
+ The :watch_root option allows for catching the scenario where you start watching "~/src/demo_project" and either it is later renamed to "~/src/awesome_sauce_3000" or the path changes in such a manner that the original directory is now at "~/clients/foo/iteration4/demo_project".
92
+
93
+ Unfortunately, while this behavior is somewhat supported in the fsevent_watch binary built as part of this project, support for passing across detailed metadata is not (yet). As a result, you would not receive the appropriate RootChanged event and be able to react appropriately. Also, since the C code doesn't open watched directories and retain that file descriptor as part of path-specific callback metadata, we are unable to to issue an F_GETPATH fcntl() to determine the directory's new path.
94
+
95
+ Please do not use this option until proper support is added in an upcoming (planned) release.
96
+
97
+ === SinceWhen
98
+
99
+ The FSEventStreamEventId passed in to :since_when is used as a base for reacting to historic events. Unfortunately, not only is the metadata for transitioning from historic to live events not currently passed along, but it is incorrectly passed as a change event on the root path, and only per-host event streams are currently supported. When using per-host event streams, the event IDs are not guaranteed to be unique or contiguous when shared volumes (firewire/USB/net/etc) are used on multiple macs.
100
+
101
+ Please do not use this option until proper support is added in an upcoming (planned) release, unless it's acceptable for you to receive that one fake event that's handled incorrectly when events transition from historical to live. Even in that scenario, there's no metadata available for determining the FSEventStreamEventId of the last received event.
102
+
103
+ WARNING: passing in 0 as the parameter to :since_when will return events for every directory modified since "the beginning of time".
104
+
105
+ == Debugging output
106
+
107
+ If the gem is installed with the environment variable FWDEBUG set to the string "true", then fsevent_watch will be built with its various DEBUG sections defined, and the output to STDERR is truly verbose (and hopefully helpful debugging your application and not just fsevent_watch itself):
108
+
109
+ append_path called for: /tmp/moo/cow/
110
+ resolved path to: /private/tmp/moo/cow
111
+
112
+ config.sinceWhen 18446744073709551615
113
+ config.latency 0.300000
114
+ config.flags 00000000
115
+ config.paths
116
+ /private/tmp/moo/cow
117
+
118
+ FSEventStreamRef @ 0x100108540:
119
+ allocator = 0x7fff705a4ee0
120
+ callback = 0x10000151e
121
+ context = {0, 0x0, 0x0, 0x0, 0x0}
122
+ numPathsToWatch = 1
123
+ pathsToWatch = 0x7fff705a4ee0
124
+ pathsToWatch[0] = '/private/tmp/moo/cow'
125
+ latestEventId = -1
126
+ latency = 300000 (microseconds)
127
+ flags = 0x00000000
128
+ runLoop = 0x0
129
+ runLoopMode = 0x0
130
+
131
+
132
+ FSEventStreamCallback fired!
133
+ numEvents: 32
134
+ event path: /private/tmp/moo/cow/1/a/
135
+ event flags: 00000000
136
+ event ID: 1023767
137
+ event path: /private/tmp/moo/cow/1/b/
138
+ event flags: 00000000
139
+ event ID: 1023782
140
+ event path: /private/tmp/moo/cow/1/c/
141
+ event flags: 00000000
142
+ event ID: 1023797
143
+ event path: /private/tmp/moo/cow/1/d/
144
+ event flags: 00000000
145
+ event ID: 1023812
146
+ event path: /private/tmp/moo/cow/1/e/
147
+ event flags: 00000000
148
+ event ID: 1023827
149
+ event path: /private/tmp/moo/cow/1/f/
150
+ event flags: 00000000
151
+ event ID: 1023842
152
+ event path: /private/tmp/moo/cow/1/g/
153
+ event flags: 00000000
154
+ event ID: 1023857
155
+ event path: /private/tmp/moo/cow/1/h/
156
+ event flags: 00000000
157
+ event ID: 1023872
158
+ event path: /private/tmp/moo/cow/1/i/
159
+ event flags: 00000000
160
+ event ID: 1023887
161
+ event path: /private/tmp/moo/cow/1/j/
162
+ event flags: 00000000
163
+ event ID: 1023902
164
+ event path: /private/tmp/moo/cow/1/k/
165
+ event flags: 00000000
166
+ event ID: 1023917
167
+ event path: /private/tmp/moo/cow/1/l/
168
+ event flags: 00000000
169
+ event ID: 1023932
170
+ event path: /private/tmp/moo/cow/1/m/
171
+ event flags: 00000000
172
+ event ID: 1023947
173
+ event path: /private/tmp/moo/cow/1/n/
174
+ event flags: 00000000
175
+ event ID: 1023962
176
+ event path: /private/tmp/moo/cow/1/o/
177
+ event flags: 00000000
178
+ event ID: 1023977
179
+ event path: /private/tmp/moo/cow/1/p/
180
+ event flags: 00000000
181
+ event ID: 1023992
182
+ event path: /private/tmp/moo/cow/1/q/
183
+ event flags: 00000000
184
+ event ID: 1024007
185
+ event path: /private/tmp/moo/cow/1/r/
186
+ event flags: 00000000
187
+ event ID: 1024022
188
+ event path: /private/tmp/moo/cow/1/s/
189
+ event flags: 00000000
190
+ event ID: 1024037
191
+ event path: /private/tmp/moo/cow/1/t/
192
+ event flags: 00000000
193
+ event ID: 1024052
194
+ event path: /private/tmp/moo/cow/1/u/
195
+ event flags: 00000000
196
+ event ID: 1024067
197
+ event path: /private/tmp/moo/cow/1/v/
198
+ event flags: 00000000
199
+ event ID: 1024082
200
+ event path: /private/tmp/moo/cow/1/w/
201
+ event flags: 00000000
202
+ event ID: 1024097
203
+ event path: /private/tmp/moo/cow/1/x/
204
+ event flags: 00000000
205
+ event ID: 1024112
206
+ event path: /private/tmp/moo/cow/1/y/
207
+ event flags: 00000000
208
+ event ID: 1024127
209
+ event path: /private/tmp/moo/cow/1/z/
210
+ event flags: 00000000
211
+ event ID: 1024142
212
+ event path: /private/tmp/moo/cow/1/
213
+ event flags: 00000000
214
+ event ID: 1024145
215
+ event path: /private/tmp/moo/cow/2/a/
216
+ event flags: 00000000
217
+ event ID: 1024160
218
+ event path: /private/tmp/moo/cow/2/b/
219
+ event flags: 00000000
220
+ event ID: 1024175
221
+ event path: /private/tmp/moo/cow/2/c/
222
+ event flags: 00000000
223
+ event ID: 1024190
224
+ event path: /private/tmp/moo/cow/2/d/
225
+ event flags: 00000000
226
+ event ID: 1024205
227
+ event path: /private/tmp/moo/cow/2/e/
228
+ event flags: 00000000
229
+ event ID: 1024220
230
+
23
231
  == Note about FFI
24
232
 
25
233
  rb-fsevent doesn't use {ruby-ffi}[http://github.com/ffi/ffi] anymore because it sadly doesn't allow to catch Signals, you can see the code in {ffi branch}[http://github.com/thibaudgg/rb-fsevent/tree/ffi].
@@ -34,4 +242,5 @@ you make.
34
242
 
35
243
  == Authors
36
244
 
37
- {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
245
+ - {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
246
+ - {Travis Tilley}[http://github.com/ttilley]
@@ -5,16 +5,45 @@ create_makefile('none')
5
5
  if `uname -s`.chomp != 'Darwin'
6
6
  puts "Warning! Only Darwin (Mac OS X) systems are supported, nothing will be compiled"
7
7
  else
8
- gem_root = File.expand_path(File.join('..'))
9
- darwin_verion = `uname -r`.to_i
10
- sdk_verion = { 9 => '10.5', 10 => '10.6', 11 => '10.7' }[darwin_verion]
11
-
12
- raise "Only Darwin systems greather than 8 (Mac OS X 10.5+) are supported" unless sdk_verion
13
-
8
+ gem_root = File.expand_path(File.join('..'))
9
+ darwin_version = `uname -r`.to_i
10
+ sdk_version = { 9 => '10.5', 10 => '10.6', 11 => '10.7' }[darwin_version]
11
+
12
+ raise "Only Darwin systems greater than 8 (Mac OS X 10.5+) are supported" unless sdk_version
13
+
14
+ core_flags = %W{
15
+ -isysroot /Developer/SDKs/MacOSX#{sdk_version}.sdk
16
+ -mmacosx-version-min=#{sdk_version} -mdynamic-no-pic -std=gnu99
17
+ }
18
+
19
+ cflags = core_flags + %w{-Os -pipe}
20
+
21
+ wflags = %w{
22
+ -Wmissing-prototypes -Wreturn-type -Wmissing-braces -Wparentheses -Wswitch
23
+ -Wunused-function -Wunused-label -Wunused-parameter -Wunused-variable
24
+ -Wunused-value -Wuninitialized -Wunknown-pragmas -Wshadow
25
+ -Wfour-char-constants -Wsign-compare -Wnewline-eof -Wconversion
26
+ -Wshorten-64-to-32 -Wglobal-constructors -pedantic
27
+ }
28
+
29
+ ldflags = %w{
30
+ -dead_strip -framework CoreServices
31
+ }
32
+
33
+ gcc_opts = core_flags + ldflags
34
+
35
+ gcc_opts += %w{
36
+ -D DEBUG=true
37
+ } if ENV['FWDEBUG'] == "true"
38
+
39
+ compile_command = "CFLAGS='#{cflags.join(' ')} #{wflags.join(' ')}' /usr/bin/gcc #{gcc_opts.join(' ')} -o '#{gem_root}/bin/fsevent_watch' fsevent/fsevent_watch.c"
40
+
41
+ STDERR.puts(compile_command)
42
+
14
43
  # Compile the actual fsevent_watch binary
15
44
  system "mkdir -p #{File.join(gem_root, 'bin')}"
16
- system "CFLAGS='-isysroot /Developer/SDKs/MacOSX#{sdk_verion}.sdk -mmacosx-version-min=#{sdk_verion}' /usr/bin/gcc -framework CoreServices -o '#{gem_root}/bin/fsevent_watch' fsevent/fsevent_watch.c"
17
-
45
+ system compile_command
46
+
18
47
  unless File.executable?("#{gem_root}/bin/fsevent_watch")
19
48
  raise "Compilation of fsevent_watch failed (see README)"
20
49
  end
@@ -1,49 +1,197 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <unistd.h>
4
+
1
5
  #include <CoreServices/CoreServices.h>
2
6
 
3
- void callback(ConstFSEventStreamRef streamRef,
4
- void *clientCallBackInfo,
5
- size_t numEvents,
6
- void *eventPaths,
7
- const FSEventStreamEventFlags eventFlags[],
8
- const FSEventStreamEventId eventIds[]
9
- ) {
10
- // Print modified dirs
11
- int i;
7
+
8
+ // Structure for storing metadata parsed from the commandline
9
+ static struct {
10
+ FSEventStreamEventId sinceWhen;
11
+ CFTimeInterval latency;
12
+ FSEventStreamCreateFlags flags;
13
+ CFMutableArrayRef paths;
14
+ } config = {
15
+ (UInt64) kFSEventStreamEventIdSinceNow,
16
+ (double) 0.3,
17
+ (UInt32) kFSEventStreamCreateFlagNone,
18
+ NULL
19
+ };
20
+
21
+ // Prototypes
22
+ static void append_path(const char *path);
23
+ static inline void parse_cli_settings(int argc, const char *argv[]);
24
+ static void callback(FSEventStreamRef streamRef,
25
+ void *clientCallBackInfo,
26
+ size_t numEvents,
27
+ void *eventPaths,
28
+ const FSEventStreamEventFlags eventFlags[],
29
+ const FSEventStreamEventId eventIds[]);
30
+
31
+
32
+ // Resolve a path and append it to the CLI settings structure
33
+ // The FSEvents API will, internally, resolve paths using a similar scheme.
34
+ // Performing this ahead of time makes things less confusing, IMHO.
35
+ static void append_path(const char *path)
36
+ {
37
+ #ifdef DEBUG
38
+ fprintf(stderr, "\n");
39
+ fprintf(stderr, "append_path called for: %s\n", path);
40
+ #endif
41
+
42
+ char fullPath[PATH_MAX];
43
+
44
+ if (realpath(path, fullPath) == NULL) {
45
+ #ifdef DEBUG
46
+ fprintf(stderr, " realpath not directly resolvable from path\n");
47
+ #endif
48
+
49
+ if (path[0] != '/') {
50
+ #ifdef DEBUG
51
+ fprintf(stderr, " passed path is not absolute\n");
52
+ #endif
53
+ size_t len;
54
+ getcwd(fullPath, sizeof(fullPath));
55
+ #ifdef DEBUG
56
+ fprintf(stderr, " result of getcwd: %s\n", fullPath);
57
+ #endif
58
+ len = strlen(fullPath);
59
+ fullPath[len] = '/';
60
+ strlcpy(&fullPath[len + 1], path, sizeof(fullPath) - (len + 1));
61
+ } else {
62
+ #ifdef DEBUG
63
+ fprintf(stderr, " assuming path does not YET exist\n");
64
+ #endif
65
+ strlcpy(fullPath, path, sizeof(fullPath));
66
+ }
67
+ }
68
+
69
+ #ifdef DEBUG
70
+ fprintf(stderr, " resolved path to: %s\n", fullPath);
71
+ fprintf(stderr, "\n");
72
+ fflush(stderr);
73
+ #endif
74
+
75
+ CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
76
+ fullPath,
77
+ kCFStringEncodingUTF8);
78
+ CFArrayAppendValue(config.paths, pathRef);
79
+ CFRelease(pathRef);
80
+ }
81
+
82
+ // Parse commandline settings
83
+ static inline void parse_cli_settings(int argc, const char *argv[])
84
+ {
85
+ config.paths = CFArrayCreateMutable(NULL,
86
+ (CFIndex)0,
87
+ &kCFTypeArrayCallBacks);
88
+
89
+ for (int i = 1; i < argc; i++) {
90
+ if (strcmp(argv[i], "--since-when") == 0) {
91
+ config.sinceWhen = strtoull(argv[++i], NULL, 0);
92
+ } else if (strcmp(argv[i], "--latency") == 0) {
93
+ config.latency = strtod(argv[++i], NULL);
94
+ } else if (strcmp(argv[i], "--no-defer") == 0) {
95
+ config.flags |= kFSEventStreamCreateFlagNoDefer;
96
+ } else if (strcmp(argv[i], "--watch-root") == 0) {
97
+ config.flags |= kFSEventStreamCreateFlagWatchRoot;
98
+ } else if (strcmp(argv[i], "--ignore-self") == 0) {
99
+ #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
100
+ config.flags |= kFSEventStreamCreateFlagIgnoreSelf;
101
+ #else
102
+ fprintf(stderr, "MacOSX10.6.sdk is required for --ignore-self\n");
103
+ #endif
104
+ } else {
105
+ append_path(argv[i]);
106
+ }
107
+ }
108
+
109
+ if (CFArrayGetCount(config.paths) == 0) {
110
+ append_path(".");
111
+ }
112
+
113
+ #ifdef DEBUG
114
+ fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen);
115
+ fprintf(stderr, "config.latency %f\n", config.latency);
116
+ fprintf(stderr, "config.flags %#.8x\n", config.flags);
117
+ fprintf(stderr, "config.paths\n");
118
+
119
+ long numpaths = CFArrayGetCount(config.paths);
120
+
121
+ for (long i = 0; i < numpaths; i++) {
122
+ char path[PATH_MAX];
123
+ CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i),
124
+ path,
125
+ PATH_MAX,
126
+ kCFStringEncodingUTF8);
127
+ fprintf(stderr, " %s\n", path);
128
+ }
129
+
130
+ fprintf(stderr, "\n");
131
+ fflush(stderr);
132
+ #endif
133
+ }
134
+
135
+ static void callback(FSEventStreamRef streamRef,
136
+ void *clientCallBackInfo,
137
+ size_t numEvents,
138
+ void *eventPaths,
139
+ const FSEventStreamEventFlags eventFlags[],
140
+ const FSEventStreamEventId eventIds[])
141
+ {
12
142
  char **paths = eventPaths;
13
- for (i = 0; i < numEvents; i++) {
14
- printf("%s", paths[i]);
15
- printf(":");
143
+
144
+ #ifdef DEBUG
145
+ fprintf(stderr, "\n");
146
+ fprintf(stderr, "FSEventStreamCallback fired!\n");
147
+ fprintf(stderr, " numEvents: %lu\n", numEvents);
148
+
149
+ for (size_t i = 0; i < numEvents; i++) {
150
+ fprintf(stderr, " event path: %s\n", paths[i]);
151
+ fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]);
152
+ fprintf(stderr, " event ID: %llu\n", eventIds[i]);
153
+ }
154
+
155
+ fprintf(stderr, "\n");
156
+ fflush(stderr);
157
+ #endif
158
+
159
+ for (size_t i = 0; i < numEvents; i++) {
160
+ fprintf(stdout, "%s", paths[i]);
161
+ fprintf(stdout, ":");
16
162
  }
17
- printf("\n");
163
+
164
+ fprintf(stdout, "\n");
18
165
  fflush(stdout);
19
166
  }
20
167
 
21
- int main (int argc, const char * argv[]) {
22
- // Collect arguments as paths to watch
23
- CFMutableArrayRef pathsToWatch = CFArrayCreateMutable(NULL, argc-1, NULL);
24
- int i;
25
- for(i=1; i<argc; i++) {
26
- CFArrayAppendValue(pathsToWatch, CFStringCreateWithCString(kCFAllocatorDefault, argv[i], kCFStringEncodingUTF8));
27
- }
168
+ int main(int argc, const char *argv[])
169
+ {
170
+ parse_cli_settings(argc, argv);
28
171
 
29
- // Create event stream
30
- void *callbackInfo = NULL;
172
+ FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
31
173
  FSEventStreamRef stream;
32
- CFAbsoluteTime latency = 0.5;
33
- stream = FSEventStreamCreate(
34
- kCFAllocatorDefault,
35
- callback,
36
- callbackInfo,
37
- pathsToWatch,
38
- kFSEventStreamEventIdSinceNow,
39
- latency,
40
- kFSEventStreamCreateFlagNone
41
- );
42
-
43
- // Add stream to run loop
44
- FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
174
+ stream = FSEventStreamCreate(kCFAllocatorDefault,
175
+ (FSEventStreamCallback)&callback,
176
+ &context,
177
+ config.paths,
178
+ config.sinceWhen,
179
+ config.latency,
180
+ config.flags);
181
+
182
+ #ifdef DEBUG
183
+ FSEventStreamShow(stream);
184
+ fprintf(stderr, "\n");
185
+ fflush(stderr);
186
+ #endif
187
+
188
+ FSEventStreamScheduleWithRunLoop(stream,
189
+ CFRunLoopGetCurrent(),
190
+ kCFRunLoopDefaultMode);
45
191
  FSEventStreamStart(stream);
46
192
  CFRunLoopRun();
193
+ FSEventStreamFlushSync(stream);
194
+ FSEventStreamStop(stream);
47
195
 
48
- return 2;
196
+ return 0;
49
197
  }
@@ -1,14 +1,42 @@
1
1
  class FSEvent
2
- attr_reader :paths, :callback, :pipe
2
+ class << self
3
+ class_eval <<-END
4
+ def root_path
5
+ "#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))}"
6
+ end
7
+ END
8
+ class_eval <<-END
9
+ def watcher_path
10
+ "#{File.join(FSEvent.root_path, 'bin', 'fsevent_watch')}"
11
+ end
12
+ END
13
+ end
14
+
15
+ attr_reader :paths, :callback
3
16
 
4
- def watch(paths, &callback)
5
- @paths = paths.kind_of?(Enumerable) ? paths : [paths]
6
- @callback = callback
17
+ def watch(watch_paths, options=nil, &block)
18
+ @paths = watch_paths.kind_of?(Array) ? watch_paths : [watch_paths]
19
+ @callback = block
20
+
21
+ if options.kind_of?(Hash)
22
+ @options = parse_options(options)
23
+ elsif options.kind_of?(Array)
24
+ @options = options
25
+ else
26
+ @options = []
27
+ end
7
28
  end
8
29
 
9
30
  def run
10
- launch_bin
11
- listen
31
+ while !pipe.eof?
32
+ if line = pipe.readline
33
+ modified_dir_paths = line.split(":").select { |dir| dir != "\n" }
34
+ callback.call(modified_dir_paths)
35
+ end
36
+ end
37
+ rescue Interrupt, IOError
38
+ ensure
39
+ stop
12
40
  end
13
41
 
14
42
  def stop
@@ -17,49 +45,58 @@ class FSEvent
17
45
  pipe.close
18
46
  end
19
47
  rescue IOError
48
+ ensure
49
+ @pipe = false
20
50
  end
21
51
 
22
- private
52
+ if RUBY_VERSION < '1.9'
53
+ def pipe
54
+ @pipe ||= IO.popen("#{self.class.watcher_path} #{options_string} #{shellescaped_paths}")
55
+ end
23
56
 
24
- def bin_path
25
- File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin'))
26
- end
57
+ private
27
58
 
28
- def launch_bin
29
- @pipe = IO.popen("#{bin_path}/fsevent_watch #{shellescaped_paths}")
30
- end
59
+ def options_string
60
+ @options.join(' ')
61
+ end
31
62
 
32
- def listen
33
- while !pipe.eof?
34
- if line = pipe.readline
35
- modified_dir_paths = line.split(":").select { |dir| dir != "\n" }
36
- callback.call(modified_dir_paths)
37
- end
63
+ def shellescaped_paths
64
+ @paths.map {|path| shellescape(path)}.join(' ')
38
65
  end
39
- rescue Interrupt, IOError
40
- stop
41
- end
42
66
 
43
- def shellescaped_paths
44
- @paths.map {|path| shellescape(path)}.join(' ')
45
- end
67
+ # for Ruby 1.8.6 support
68
+ def shellescape(str)
69
+ # An empty argument will be skipped, so return empty quotes.
70
+ return "''" if str.empty?
71
+
72
+ str = str.dup
46
73
 
47
- # for Ruby 1.8.6 support
48
- def shellescape(str)
49
- # An empty argument will be skipped, so return empty quotes.
50
- return "''" if str.empty?
74
+ # Process as a single byte sequence because not all shell
75
+ # implementations are multibyte aware.
76
+ str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
51
77
 
52
- str = str.dup
78
+ # A LF cannot be escaped with a backslash because a backslash + LF
79
+ # combo is regarded as line continuation and simply ignored.
80
+ str.gsub!(/\n/, "'\n'")
53
81
 
54
- # Process as a single byte sequence because not all shell
55
- # implementations are multibyte aware.
56
- str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
82
+ return str
83
+ end
84
+ else
85
+ def pipe
86
+ @pipe ||= IO.popen([self.class.watcher_path] + @options + @paths)
87
+ end
88
+ end
57
89
 
58
- # A LF cannot be escaped with a backslash because a backslash + LF
59
- # combo is regarded as line continuation and simply ignored.
60
- str.gsub!(/\n/, "'\n'")
90
+ private
61
91
 
62
- return str
92
+ def parse_options(options={})
93
+ opts = []
94
+ opts.concat(['--since-when', options[:since_when]]) if options[:since_when]
95
+ opts.concat(['--latency', options[:latency]]) if options[:latency]
96
+ opts.push('--no-defer') if options[:no_defer]
97
+ opts.push('--watch-root') if options[:watch_root]
98
+ # ruby 1.9's IO.popen(array-of-stuff) syntax requires all items to be strings
99
+ opts.map {|opt| "#{opt}"}
63
100
  end
64
101
 
65
- end
102
+ end
@@ -1,3 +1,3 @@
1
1
  class FSEvent
2
- VERSION = "0.3.10"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,21 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rb-fsevent
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
5
- prerelease:
4
+ hash: 15
5
+ prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 10
10
- version: 0.3.10
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Thibaud Guillaume-Gentil
14
+ - Travis Tilley
14
15
  autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2011-02-07 00:00:00 +01:00
19
+ date: 2011-03-09 00:00:00 +01:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
@@ -69,6 +70,7 @@ dependencies:
69
70
  description: FSEvents API with Signals catching (without RubyCocoa)
70
71
  email:
71
72
  - thibaud@thibaud.me
73
+ - ttilley@gmail.com
72
74
  executables: []
73
75
 
74
76
  extensions:
@@ -113,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
115
  requirements: []
114
116
 
115
117
  rubyforge_project: rb-fsevent
116
- rubygems_version: 1.5.0
118
+ rubygems_version: 1.3.7
117
119
  signing_key:
118
120
  specification_version: 3
119
121
  summary: Very simple & usable FSEvents API