rb-fsevent 0.3.8 → 0.4.1

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Thibaud Guillaume-Gentil
1
+ Copyright (c) 2011 Thibaud Guillaume-Gentil
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -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-head, with experimental (but sufficient) extconf/mkmf support
8
9
 
9
10
  == Install
10
11
 
@@ -12,26 +13,246 @@ 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
+ Implementation note: It appears that FSEvents will only coalesce events from a maximum of 32 distinct subpaths, making the above completely accurate only when events are to fewer than 32 subpaths. Creating 300 files in one directory, for example, or 30 files in 10 subdirectories, but not 300 files within 300 subdirectories. In the latter case, you may receive 31 callbacks in one go after the latency period. As this appears to be an implementation detail, the number could potentially differ across OS revisions. It is entirely possible that this number is somehow configurable, but I have not yet discovered an accepted method of doing so.
84
+
85
+ === NoDefer
86
+
87
+ 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.
88
+
89
+ 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.
90
+
91
+ === WatchRoot
92
+
93
+ 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".
94
+
95
+ 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 issue an F_GETPATH fcntl() to determine the directory's new path.
96
+
97
+ Please do not use this option until proper support is added in an upcoming (planned) release.
98
+
99
+ === SinceWhen
100
+
101
+ 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.
102
+
103
+ 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.
104
+
105
+ WARNING: passing in 0 as the parameter to :since_when will return events for every directory modified since "the beginning of time".
106
+
107
+ == Debugging output
108
+
109
+ 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 in debugging your application and not just fsevent_watch itself). If enough people find this to be directly useful when developing code that makes use of rb-fsevent, then it wouldn't be hard to clean this up and make it a feature enabled by a commandline argument instead. Until somebody files an issue, however, I will assume otherwise.
110
+
111
+ append_path called for: /tmp/moo/cow/
112
+ resolved path to: /private/tmp/moo/cow
113
+
114
+ config.sinceWhen 18446744073709551615
115
+ config.latency 0.300000
116
+ config.flags 00000000
117
+ config.paths
118
+ /private/tmp/moo/cow
119
+
120
+ FSEventStreamRef @ 0x100108540:
121
+ allocator = 0x7fff705a4ee0
122
+ callback = 0x10000151e
123
+ context = {0, 0x0, 0x0, 0x0, 0x0}
124
+ numPathsToWatch = 1
125
+ pathsToWatch = 0x7fff705a4ee0
126
+ pathsToWatch[0] = '/private/tmp/moo/cow'
127
+ latestEventId = -1
128
+ latency = 300000 (microseconds)
129
+ flags = 0x00000000
130
+ runLoop = 0x0
131
+ runLoopMode = 0x0
132
+
133
+
134
+ FSEventStreamCallback fired!
135
+ numEvents: 32
136
+ event path: /private/tmp/moo/cow/1/a/
137
+ event flags: 00000000
138
+ event ID: 1023767
139
+ event path: /private/tmp/moo/cow/1/b/
140
+ event flags: 00000000
141
+ event ID: 1023782
142
+ event path: /private/tmp/moo/cow/1/c/
143
+ event flags: 00000000
144
+ event ID: 1023797
145
+ event path: /private/tmp/moo/cow/1/d/
146
+ event flags: 00000000
147
+ event ID: 1023812
148
+ event path: /private/tmp/moo/cow/1/e/
149
+ event flags: 00000000
150
+ event ID: 1023827
151
+ event path: /private/tmp/moo/cow/1/f/
152
+ event flags: 00000000
153
+ event ID: 1023842
154
+ event path: /private/tmp/moo/cow/1/g/
155
+ event flags: 00000000
156
+ event ID: 1023857
157
+ event path: /private/tmp/moo/cow/1/h/
158
+ event flags: 00000000
159
+ event ID: 1023872
160
+ event path: /private/tmp/moo/cow/1/i/
161
+ event flags: 00000000
162
+ event ID: 1023887
163
+ event path: /private/tmp/moo/cow/1/j/
164
+ event flags: 00000000
165
+ event ID: 1023902
166
+ event path: /private/tmp/moo/cow/1/k/
167
+ event flags: 00000000
168
+ event ID: 1023917
169
+ event path: /private/tmp/moo/cow/1/l/
170
+ event flags: 00000000
171
+ event ID: 1023932
172
+ event path: /private/tmp/moo/cow/1/m/
173
+ event flags: 00000000
174
+ event ID: 1023947
175
+ event path: /private/tmp/moo/cow/1/n/
176
+ event flags: 00000000
177
+ event ID: 1023962
178
+ event path: /private/tmp/moo/cow/1/o/
179
+ event flags: 00000000
180
+ event ID: 1023977
181
+ event path: /private/tmp/moo/cow/1/p/
182
+ event flags: 00000000
183
+ event ID: 1023992
184
+ event path: /private/tmp/moo/cow/1/q/
185
+ event flags: 00000000
186
+ event ID: 1024007
187
+ event path: /private/tmp/moo/cow/1/r/
188
+ event flags: 00000000
189
+ event ID: 1024022
190
+ event path: /private/tmp/moo/cow/1/s/
191
+ event flags: 00000000
192
+ event ID: 1024037
193
+ event path: /private/tmp/moo/cow/1/t/
194
+ event flags: 00000000
195
+ event ID: 1024052
196
+ event path: /private/tmp/moo/cow/1/u/
197
+ event flags: 00000000
198
+ event ID: 1024067
199
+ event path: /private/tmp/moo/cow/1/v/
200
+ event flags: 00000000
201
+ event ID: 1024082
202
+ event path: /private/tmp/moo/cow/1/w/
203
+ event flags: 00000000
204
+ event ID: 1024097
205
+ event path: /private/tmp/moo/cow/1/x/
206
+ event flags: 00000000
207
+ event ID: 1024112
208
+ event path: /private/tmp/moo/cow/1/y/
209
+ event flags: 00000000
210
+ event ID: 1024127
211
+ event path: /private/tmp/moo/cow/1/z/
212
+ event flags: 00000000
213
+ event ID: 1024142
214
+ event path: /private/tmp/moo/cow/1/
215
+ event flags: 00000000
216
+ event ID: 1024145
217
+ event path: /private/tmp/moo/cow/2/a/
218
+ event flags: 00000000
219
+ event ID: 1024160
220
+ event path: /private/tmp/moo/cow/2/b/
221
+ event flags: 00000000
222
+ event ID: 1024175
223
+ event path: /private/tmp/moo/cow/2/c/
224
+ event flags: 00000000
225
+ event ID: 1024190
226
+ event path: /private/tmp/moo/cow/2/d/
227
+ event flags: 00000000
228
+ event ID: 1024205
229
+ event path: /private/tmp/moo/cow/2/e/
230
+ event flags: 00000000
231
+ event ID: 1024220
232
+
23
233
  == Note about FFI
24
234
 
25
- 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].
235
+ rb-fsevent doesn't use {ruby-ffi}[http://github.com/ffi/ffi] anymore because it sadly doesn't allow for catching Signals. You can still see the code in the {ffi branch}[http://github.com/thibaudgg/rb-fsevent/tree/ffi].
26
236
 
27
237
  == Development
28
238
 
29
239
  - Source hosted at {GitHub}[http://github.com/thibaudgg/rb-fsevent]
30
240
  - Report issues/Questions/Feature requests on {GitHub Issues}[http://github.com/thibaudgg/rb-fsevent/issues]
31
241
 
32
- Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change
33
- you make.
242
+ Pull requests are quite welcome! Please ensure that your commits are in a topic branch for each individual changeset than can be reasonably isolated. It is also important to ensure that your changes are well tested... whether that means new tests, modified tests, or fixing a scenario where the existing tests currently fail. If you have rvm and the required rubies currently installed, we have a helper task for running the testsuite in all of them:
243
+
244
+ rake spec:portability
245
+
246
+ The list of tested RVM targets is currently:
247
+
248
+ %w[1.8.6 1.8.7 1.9.2 jruby-head]
249
+
250
+ Development note: As of my last attempt to do so, jruby-head's cext support will NOT compile, as-is, using xcode 4.0.1. Simply editing the makefile to point -isysroot to '/' rather than depend on an SDK specific environment and switching the minimum OS to 10.6 was enough to get past this. Making these changes within the context of an RVM installed ruby, however, requires creating a patch and then informing rvm that it needs to apply this patch pre-build.
251
+
252
+ If you have xcode4, and jruby-head has not yet been fixed to allow unmodified compilation, then it's perfectly understandable and acceptable to just remove jruby-head as a testing target. This is especially true considering that it requires an unreleased version of JRuby to begin with.
34
253
 
35
254
  == Authors
36
255
 
37
- {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
256
+ - {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
257
+ - {Travis Tilley}[http://github.com/ttilley]
258
+
data/ext/extconf.rb CHANGED
@@ -5,16 +5,52 @@ 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
+ begin
9
+ xcode_path = %x[xcode-select -print-path].to_s.strip!
10
+ rescue Errno::ENOENT
11
+ end
12
+
13
+ raise "Could not find a suitable Xcode installation" unless xcode_path
14
+
15
+ gem_root = File.expand_path(File.join('..'))
16
+ darwin_version = `uname -r`.to_i
17
+ sdk_version = { 9 => '10.5', 10 => '10.6', 11 => '10.7' }[darwin_version]
18
+
19
+ raise "Only Darwin systems greater than 8 (Mac OS X 10.5+) are supported" unless sdk_version
20
+
21
+ core_flags = %W{
22
+ -isysroot #{xcode_path}/SDKs/MacOSX#{sdk_version}.sdk
23
+ -mmacosx-version-min=#{sdk_version} -mdynamic-no-pic -std=gnu99
24
+ }
25
+
26
+ cflags = core_flags + %w{-Os -pipe}
27
+
28
+ wflags = %w{
29
+ -Wmissing-prototypes -Wreturn-type -Wmissing-braces -Wparentheses -Wswitch
30
+ -Wunused-function -Wunused-label -Wunused-parameter -Wunused-variable
31
+ -Wunused-value -Wuninitialized -Wunknown-pragmas -Wshadow
32
+ -Wfour-char-constants -Wsign-compare -Wnewline-eof -Wconversion
33
+ -Wshorten-64-to-32 -Wglobal-constructors -pedantic
34
+ }
35
+
36
+ ldflags = %w{
37
+ -dead_strip -framework CoreServices
38
+ }
39
+
40
+ gcc_opts = core_flags + ldflags
41
+
42
+ gcc_opts += %w{
43
+ -D DEBUG=true
44
+ } if ENV['FWDEBUG'] == "true"
45
+
46
+ compile_command = "CFLAGS='#{cflags.join(' ')} #{wflags.join(' ')}' /usr/bin/gcc #{gcc_opts.join(' ')} -o '#{gem_root}/bin/fsevent_watch' fsevent/fsevent_watch.c"
47
+
48
+ STDERR.puts(compile_command)
49
+
14
50
  # Compile the actual fsevent_watch binary
15
51
  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
-
52
+ system compile_command
53
+
18
54
  unless File.executable?("#{gem_root}/bin/fsevent_watch")
19
55
  raise "Compilation of fsevent_watch failed (see README)"
20
56
  end
@@ -1,44 +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]);
16
153
  }
17
- printf("\n");
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, ":");
162
+ }
163
+
164
+ fprintf(stdout, "\n");
18
165
  fflush(stdout);
19
166
  }
20
167
 
21
- int main (int argc, const char * argv[]) {
22
- // Create event stream
23
- CFStringRef pathToWatch = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingUTF8);
24
- CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&pathToWatch, 1, NULL);
25
- void *callbackInfo = NULL;
168
+ int main(int argc, const char *argv[])
169
+ {
170
+ parse_cli_settings(argc, argv);
171
+
172
+ FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
26
173
  FSEventStreamRef stream;
27
- CFAbsoluteTime latency = 0.5;
28
- stream = FSEventStreamCreate(
29
- kCFAllocatorDefault,
30
- callback,
31
- callbackInfo,
32
- pathsToWatch,
33
- kFSEventStreamEventIdSinceNow,
34
- latency,
35
- kFSEventStreamCreateFlagNone
36
- );
37
-
38
- // Add stream to run loop
39
- 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);
40
191
  FSEventStreamStart(stream);
41
192
  CFRunLoopRun();
42
-
43
- return 2;
44
- }
193
+ FSEventStreamFlushSync(stream);
194
+ FSEventStreamStop(stream);
195
+
196
+ return 0;
197
+ }
@@ -1,35 +1,33 @@
1
1
  class FSEvent
2
- attr_reader :path, :callback, :pipe
3
-
4
- def watch(path, &callback)
5
- @path = path
6
- @callback = callback
7
- end
8
-
9
- def run
10
- launch_bin
11
- listen
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
12
13
  end
13
-
14
- def stop
15
- if pipe
16
- Process.kill("KILL", pipe.pid)
17
- pipe.close
14
+
15
+ attr_reader :paths, :callback
16
+
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 = []
18
27
  end
19
- rescue Interrupt, IOError
20
28
  end
21
-
22
- private
23
-
24
- def bin_path
25
- File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin'))
26
- end
27
-
28
- def launch_bin
29
- @pipe = IO.popen("#{bin_path}/fsevent_watch #{shellescape(path)}")
30
- end
31
-
32
- def listen
29
+
30
+ def run
33
31
  while !pipe.eof?
34
32
  if line = pipe.readline
35
33
  modified_dir_paths = line.split(":").select { |dir| dir != "\n" }
@@ -37,25 +35,68 @@ private
37
35
  end
38
36
  end
39
37
  rescue Interrupt, IOError
38
+ ensure
40
39
  stop
41
40
  end
42
-
43
- # for Ruby 1.8.6 support
44
- def shellescape(str)
45
- # An empty argument will be skipped, so return empty quotes.
46
- return "''" if str.empty?
47
-
48
- str = str.dup
49
-
50
- # Process as a single byte sequence because not all shell
51
- # implementations are multibyte aware.
52
- str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
53
-
54
- # A LF cannot be escaped with a backslash because a backslash + LF
55
- # combo is regarded as line continuation and simply ignored.
56
- str.gsub!(/\n/, "'\n'")
57
-
58
- return str
41
+
42
+ def stop
43
+ if pipe
44
+ Process.kill("KILL", pipe.pid)
45
+ pipe.close
46
+ end
47
+ rescue IOError
48
+ ensure
49
+ @pipe = false
50
+ end
51
+
52
+ if RUBY_VERSION < '1.9'
53
+ def pipe
54
+ @pipe ||= IO.popen("#{self.class.watcher_path} #{options_string} #{shellescaped_paths}")
55
+ end
56
+
57
+ private
58
+
59
+ def options_string
60
+ @options.join(' ')
61
+ end
62
+
63
+ def shellescaped_paths
64
+ @paths.map {|path| shellescape(path)}.join(' ')
65
+ end
66
+
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
73
+
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")
77
+
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'")
81
+
82
+ return str
83
+ end
84
+ else
85
+ def pipe
86
+ @pipe ||= IO.popen([self.class.watcher_path] + @options + @paths)
87
+ end
88
+ end
89
+
90
+ private
91
+
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}"}
59
100
  end
60
-
61
- end
101
+
102
+ end
@@ -1,3 +1,3 @@
1
1
  class FSEvent
2
- VERSION = "0.3.8"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,81 +1,60 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rb-fsevent
3
- version: !ruby/object:Gem::Version
4
- hash: 3
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 3
9
- - 8
10
- version: 0.3.8
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Thibaud Guillaume-Gentil
9
+ - Travis Tilley
14
10
  autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
-
18
- date: 2010-11-11 00:00:00 +01:00
13
+ date: 2011-07-06 00:00:00.000000000 +02:00
19
14
  default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
22
17
  name: bundler
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirement: &2152430220 !ruby/object:Gem::Requirement
25
19
  none: false
26
- requirements:
20
+ requirements:
27
21
  - - ~>
28
- - !ruby/object:Gem::Version
29
- hash: 17
30
- segments:
31
- - 1
32
- - 0
33
- - 3
34
- version: 1.0.3
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.10
35
24
  type: :development
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: rspec
39
25
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
26
+ version_requirements: *2152430220
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: &2152429140 !ruby/object:Gem::Requirement
41
30
  none: false
42
- requirements:
31
+ requirements:
43
32
  - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 13
46
- segments:
47
- - 2
48
- - 0
49
- - 1
50
- version: 2.0.1
33
+ - !ruby/object:Gem::Version
34
+ version: 2.5.0
51
35
  type: :development
52
- version_requirements: *id002
53
- - !ruby/object:Gem::Dependency
54
- name: guard-rspec
55
36
  prerelease: false
56
- requirement: &id003 !ruby/object:Gem::Requirement
37
+ version_requirements: *2152429140
38
+ - !ruby/object:Gem::Dependency
39
+ name: guard-rspec
40
+ requirement: &2152427640 !ruby/object:Gem::Requirement
57
41
  none: false
58
- requirements:
42
+ requirements:
59
43
  - - ~>
60
- - !ruby/object:Gem::Version
61
- hash: 19
62
- segments:
63
- - 0
64
- - 1
65
- - 4
66
- version: 0.1.4
44
+ - !ruby/object:Gem::Version
45
+ version: 0.1.9
67
46
  type: :development
68
- version_requirements: *id003
47
+ prerelease: false
48
+ version_requirements: *2152427640
69
49
  description: FSEvents API with Signals catching (without RubyCocoa)
70
- email:
50
+ email:
71
51
  - thibaud@thibaud.me
52
+ - ttilley@gmail.com
72
53
  executables: []
73
-
74
- extensions:
54
+ extensions:
75
55
  - ext/extconf.rb
76
56
  extra_rdoc_files: []
77
-
78
- files:
57
+ files:
79
58
  - lib/rb-fsevent/fsevent.rb
80
59
  - lib/rb-fsevent/version.rb
81
60
  - lib/rb-fsevent.rb
@@ -86,36 +65,32 @@ files:
86
65
  has_rdoc: true
87
66
  homepage: http://rubygems.org/gems/rb-fsevent
88
67
  licenses: []
89
-
90
68
  post_install_message:
91
69
  rdoc_options: []
92
-
93
- require_paths:
70
+ require_paths:
94
71
  - lib
95
- required_ruby_version: !ruby/object:Gem::Requirement
72
+ required_ruby_version: !ruby/object:Gem::Requirement
96
73
  none: false
97
- requirements:
98
- - - ">="
99
- - !ruby/object:Gem::Version
100
- hash: 3
101
- segments:
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ segments:
102
79
  - 0
103
- version: "0"
104
- required_rubygems_version: !ruby/object:Gem::Requirement
80
+ hash: -3134696196885608875
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
82
  none: false
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- hash: 3
110
- segments:
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ segments:
111
88
  - 0
112
- version: "0"
89
+ hash: -3134696196885608875
113
90
  requirements: []
114
-
115
91
  rubyforge_project: rb-fsevent
116
- rubygems_version: 1.3.7
92
+ rubygems_version: 1.6.2
117
93
  signing_key:
118
94
  specification_version: 3
119
95
  summary: Very simple & usable FSEvents API
120
96
  test_files: []
121
-