|
dave@59
|
1 |
#!/usr/bin/ruby |
|
dave@59
|
2 |
|
|
dave@59
|
3 |
# This script watches modifications on the given directory, using the new # FSEvents API in Leopard. |
|
dave@59
|
4 |
|
|
dave@59
|
5 |
require 'osx/foundation' |
|
dave@59
|
6 |
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework' |
|
dave@59
|
7 |
include OSX |
|
dave@59
|
8 |
require 'set' |
|
dave@63
|
9 |
require 'optparse' |
|
dave@63
|
10 |
require 'ostruct' |
|
dave@59
|
11 |
|
|
dave@62
|
12 |
$COMMAND = File.basename($0) |
|
dave@64
|
13 |
$USAGE = "Usage: #{$COMMAND} [OPTIONS] <command> [<args> ...]" |
|
dave@68
|
14 |
def die(s) |
|
dave@68
|
15 |
$stderr.puts s |
|
dave@68
|
16 |
exit 1 |
|
dave@68
|
17 |
end |
|
dave@63
|
18 |
|
|
dave@63
|
19 |
options = OpenStruct.new |
|
dave@63
|
20 |
options.directoriesOnly = false |
|
dave@64
|
21 |
options.outputFile = "-" |
|
dave@64
|
22 |
|
|
dave@63
|
23 |
opts = OptionParser.new do |opts| |
|
dave@63
|
24 |
opts.banner = $USAGE |
|
dave@63
|
25 |
opts.separator "" |
|
dave@63
|
26 |
opts.separator "Specific options:" |
|
dave@63
|
27 |
|
|
dave@63
|
28 |
opts.on("-d", "--dir-only", "Only display directories") do |
|
dave@63
|
29 |
options.directoriesOnly = true |
|
dave@63
|
30 |
end |
|
dave@63
|
31 |
|
|
dave@64
|
32 |
opts.on("-o", "--output FILE", "Write output to a file") do |fileName| |
|
dave@64
|
33 |
options.outputFile = fileName |
|
dave@64
|
34 |
end |
|
dave@64
|
35 |
|
|
dave@63
|
36 |
opts.on_tail("-h", "--help", "Show this message") do |
|
dave@63
|
37 |
puts opts |
|
dave@63
|
38 |
exit |
|
dave@63
|
39 |
end |
|
dave@63
|
40 |
end |
|
dave@63
|
41 |
opts.parse!(ARGV) |
|
dave@63
|
42 |
|
|
dave@68
|
43 |
die $USAGE unless ARGV.size >= 1 |
|
dave@66
|
44 |
|
|
dave@64
|
45 |
if options.outputFile == "-" |
|
dave@64
|
46 |
outputHandle = $stdout.clone |
|
dave@64
|
47 |
else |
|
dave@64
|
48 |
outputHandle = File.open(options.outputFile, "w") |
|
dave@64
|
49 |
end |
|
dave@64
|
50 |
|
|
dave@59
|
51 |
path = "/" |
|
dave@59
|
52 |
|
|
dave@59
|
53 |
startId = FSEventsGetCurrentEventId() |
|
dave@62
|
54 |
# Used to compare with mtime, which only has second accuracy |
|
dave@62
|
55 |
startTime = Time.now.to_i |
|
dave@59
|
56 |
|
|
dave@62
|
57 |
fork do |
|
dave@62
|
58 |
begin |
|
dave@59
|
59 |
exec(*ARGV) |
|
dave@62
|
60 |
rescue SystemCallError => e |
|
dave@62
|
61 |
puts e.message |
|
dave@62
|
62 |
exit 127 |
|
dave@59
|
63 |
end |
|
dave@59
|
64 |
end |
|
dave@62
|
65 |
pid = Process.wait |
|
dave@62
|
66 |
returnCode = $? >> 8 |
|
dave@59
|
67 |
|
|
dave@59
|
68 |
sleep(0.1) |
|
dave@59
|
69 |
|
|
dave@59
|
70 |
allPaths = Set.new |
|
dave@59
|
71 |
|
|
dave@59
|
72 |
fsevents_cb = proc do |stream, ctx, numEvents, paths, marks, eventIDs| |
|
dave@59
|
73 |
paths.regard_as('*') |
|
dave@59
|
74 |
numEvents.times do |n| |
|
dave@62
|
75 |
allPaths.add(paths[n]) |
|
dave@59
|
76 |
end |
|
dave@59
|
77 |
end |
|
dave@59
|
78 |
|
|
dave@59
|
79 |
stream = FSEventStreamCreate( |
|
dave@59
|
80 |
KCFAllocatorDefault, |
|
dave@59
|
81 |
fsevents_cb, |
|
dave@59
|
82 |
nil, |
|
dave@59
|
83 |
[path], |
|
dave@59
|
84 |
startId, |
|
dave@59
|
85 |
1.0, |
|
dave@59
|
86 |
KFSEventStreamCreateFlagNoDefer) |
|
dave@59
|
87 |
|
|
dave@59
|
88 |
die "Failed to create the FSEventStream" unless stream |
|
dave@59
|
89 |
|
|
dave@59
|
90 |
FSEventStreamScheduleWithRunLoop( |
|
dave@59
|
91 |
stream, |
|
dave@59
|
92 |
CFRunLoopGetCurrent(), |
|
dave@59
|
93 |
KCFRunLoopDefaultMode) |
|
dave@59
|
94 |
|
|
dave@59
|
95 |
ok = FSEventStreamStart(stream) |
|
dave@59
|
96 |
die "Failed to start the FSEventStream" unless ok |
|
dave@59
|
97 |
|
|
dave@68
|
98 |
FSEventStreamFlushSync(stream) |
|
dave@68
|
99 |
allPaths.sort.each do |path| |
|
dave@68
|
100 |
if File.exists?(path) |
|
dave@68
|
101 |
outputHandle.puts path |
|
dave@68
|
102 |
if (!options.directoriesOnly) |
|
dave@68
|
103 |
Dir.foreach(path) do |file| |
|
dave@68
|
104 |
fullPath = File.join(path, file) |
|
dave@68
|
105 |
stat = File.stat(fullPath) |
|
dave@68
|
106 |
if (stat.mtime.to_i >= startTime) |
|
dave@68
|
107 |
outputHandle.puts " #{file}" |
|
dave@62
|
108 |
end |
|
dave@62
|
109 |
end |
|
dave@62
|
110 |
end |
|
dave@68
|
111 |
else |
|
dave@68
|
112 |
outputHandle.puts "#{path} !" |
|
dave@59
|
113 |
end |
|
dave@59
|
114 |
end |
|
dave@62
|
115 |
|
|
dave@68
|
116 |
FSEventStreamStop(stream) |
|
dave@68
|
117 |
FSEventStreamInvalidate(stream) |
|
dave@68
|
118 |
FSEventStreamRelease(stream) |
|
dave@68
|
119 |
|
|
dave@68
|
120 |
|
|
dave@64
|
121 |
outputHandle.flush |
|
dave@64
|
122 |
|
|
dave@62
|
123 |
exit(returnCode) |