src/scripts/fswatch
author Dave Dribin <dave@dribin.org>
Tue Jan 01 21:33:48 2008 -0600 (2008-01-01)
changeset 66 152f19907eb1
parent 64 src/scripts/watch@cec0c8caccc2
child 68 73cc78a030bf
permissions -rwxr-xr-x
Rename watch to fswatch
     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 
    15 options = OpenStruct.new
    16 options.directoriesOnly = false
    17 options.outputFile = "-"
    18 
    19 opts = OptionParser.new do |opts|
    20   opts.banner = $USAGE
    21   opts.separator ""
    22   opts.separator "Specific options:"
    23 
    24   opts.on("-d", "--dir-only", "Only display directories") do
    25     options.directoriesOnly = true
    26   end
    27   
    28   opts.on("-o", "--output FILE", "Write output to a file") do |fileName|
    29     options.outputFile = fileName
    30   end
    31   
    32   opts.on_tail("-h", "--help", "Show this message") do
    33     puts opts
    34     exit
    35   end
    36 end
    37 opts.parse!(ARGV)
    38 
    39 if (ARGV.size < 1)
    40   $stderr.puts $USAGE
    41   exit 1
    42 end
    43 
    44 if options.outputFile == "-"
    45   outputHandle = $stdout.clone
    46 else
    47   outputHandle = File.open(options.outputFile, "w")
    48 end
    49 
    50 path = "/"
    51 
    52 startId = FSEventsGetCurrentEventId()
    53 # Used to compare with mtime, which only has second accuracy
    54 startTime = Time.now.to_i
    55 
    56 fork do
    57   begin
    58     exec(*ARGV)
    59   rescue SystemCallError  => e
    60     puts e.message
    61     exit 127
    62   end
    63 end
    64 pid = Process.wait
    65 returnCode = $? >> 8
    66 
    67 sleep(0.1)
    68 
    69 allPaths = Set.new
    70 
    71 fsevents_cb = proc do |stream, ctx, numEvents, paths, marks, eventIDs|
    72   paths.regard_as('*')
    73   numEvents.times do |n|
    74     allPaths.add(paths[n])
    75   end
    76 end
    77 
    78 stream = FSEventStreamCreate(
    79   KCFAllocatorDefault,
    80   fsevents_cb,
    81   nil,
    82   [path],
    83   startId,
    84   1.0,
    85   KFSEventStreamCreateFlagNoDefer)
    86 
    87 die "Failed to create the FSEventStream" unless stream
    88 
    89 FSEventStreamScheduleWithRunLoop(
    90   stream,
    91   CFRunLoopGetCurrent(),
    92   KCFRunLoopDefaultMode)
    93 
    94 ok = FSEventStreamStart(stream)
    95 die "Failed to start the FSEventStream" unless ok
    96 
    97 begin
    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 rescue Interrupt
   116   FSEventStreamStop(stream)
   117   FSEventStreamInvalidate(stream)
   118   FSEventStreamRelease(stream)
   119 end
   120 
   121 outputHandle.flush
   122 
   123 exit(returnCode)