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