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.
- data/README.rdoc +212 -3
- data/ext/extconf.rb +37 -8
- data/ext/fsevent/fsevent_watch.c +184 -36
- data/lib/rb-fsevent/fsevent.rb +75 -38
- data/lib/rb-fsevent/version.rb +1 -1
- metadata +9 -7
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
|
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]
|
data/ext/extconf.rb
CHANGED
@@ -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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
raise "Only Darwin systems
|
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
|
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
|
data/ext/fsevent/fsevent_watch.c
CHANGED
@@ -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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
163
|
+
|
164
|
+
fprintf(stdout, "\n");
|
18
165
|
fflush(stdout);
|
19
166
|
}
|
20
167
|
|
21
|
-
int main
|
22
|
-
|
23
|
-
|
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
|
-
|
30
|
-
void *callbackInfo = NULL;
|
172
|
+
FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
|
31
173
|
FSEventStreamRef stream;
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
);
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
196
|
+
return 0;
|
49
197
|
}
|
data/lib/rb-fsevent/fsevent.rb
CHANGED
@@ -1,14 +1,42 @@
|
|
1
1
|
class FSEvent
|
2
|
-
|
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(
|
5
|
-
@paths
|
6
|
-
@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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
25
|
-
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin'))
|
26
|
-
end
|
57
|
+
private
|
27
58
|
|
28
|
-
|
29
|
-
|
30
|
-
|
59
|
+
def options_string
|
60
|
+
@options.join(' ')
|
61
|
+
end
|
31
62
|
|
32
|
-
|
33
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
59
|
-
# combo is regarded as line continuation and simply ignored.
|
60
|
-
str.gsub!(/\n/, "'\n'")
|
90
|
+
private
|
61
91
|
|
62
|
-
|
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
|
data/lib/rb-fsevent/version.rb
CHANGED
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:
|
5
|
-
prerelease:
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
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.
|
118
|
+
rubygems_version: 1.3.7
|
117
119
|
signing_key:
|
118
120
|
specification_version: 3
|
119
121
|
summary: Very simple & usable FSEvents API
|