src/scripts/fswatch
author Dave Dribin <dave@dribin.org>
Wed Jan 23 11:58:46 2008 -0600 (2008-01-23)
changeset 73 e493ac32ba79
parent 68 73cc78a030bf
child 80 858e155c15b4
permissions -rwxr-xr-x
Add --watch
     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 def run_command(*args)
    20   fork do
    21     begin
    22       exec(*args)
    23     rescue SystemCallError  => e
    24       puts e.message
    25       exit 127
    26     end
    27   end
    28   pid = Process.wait
    29   returnCode = $? >> 8
    30   sleep(0.1)
    31   return returnCode
    32 end
    33 
    34 def wait_until_interrupt
    35   begin
    36     $stderr.puts "Press Control-C to stop monitoring"
    37     sleep(1.0) while (true)
    38   rescue Interrupt
    39     puts
    40   end
    41 end
    42 
    43 options = OpenStruct.new
    44 options.directoriesOnly = false
    45 options.outputFile = "-"
    46 options.wait = false
    47 
    48 opts = OptionParser.new do |opts|
    49   opts.banner = $USAGE
    50   opts.separator ""
    51   opts.separator "Specific options:"
    52 
    53   opts.on("-d", "--dir-only", "Only display directories") do
    54     options.directoriesOnly = true
    55   end
    56   
    57   opts.on("-o", "--output FILE", "Write output to a file") do |fileName|
    58     options.outputFile = fileName
    59   end
    60   
    61   opts.on("-w", "--wait", "Wait until interrupted") do
    62     options.wait = true
    63   end
    64   
    65   opts.on_tail("-h", "--help", "Show this message") do
    66     puts opts
    67     exit
    68   end
    69 end
    70 opts.parse!(ARGV)
    71 
    72 if !options.wait and ARGV == 0
    73   die $USAGE
    74 end
    75 
    76 if options.outputFile == "-"
    77   outputHandle = $stdout.clone
    78 else
    79   outputHandle = File.open(options.outputFile, "w")
    80 end
    81 
    82 path = "/"
    83 
    84 startId = FSEventsGetCurrentEventId()
    85 # Used to compare with mtime, which only has second accuracy
    86 startTime = Time.now.to_i
    87 
    88 if options.wait then
    89   wait_until_interrupt
    90   returnCode = 0
    91 else
    92   returnCode = run_command(*ARGV)
    93 end
    94 
    95 allPaths = Set.new
    96 
    97 fsevents_cb = proc do |stream, ctx, numEvents, paths, marks, eventIDs|
    98   paths.regard_as('*')
    99   numEvents.times do |n|
   100     allPaths.add(paths[n])
   101   end
   102 end
   103 
   104 stream = FSEventStreamCreate(
   105   KCFAllocatorDefault,
   106   fsevents_cb,
   107   nil,
   108   [path],
   109   startId,
   110   1.0,
   111   KFSEventStreamCreateFlagNoDefer)
   112 
   113 die "Failed to create the FSEventStream" unless stream
   114 
   115 FSEventStreamScheduleWithRunLoop(
   116   stream,
   117   CFRunLoopGetCurrent(),
   118   KCFRunLoopDefaultMode)
   119 
   120 ok = FSEventStreamStart(stream)
   121 die "Failed to start the FSEventStream" unless ok
   122 
   123 FSEventStreamFlushSync(stream)
   124 allPaths.sort.each do |path|
   125   if File.exists?(path)
   126     outputHandle.puts path
   127     if (!options.directoriesOnly)
   128       Dir.foreach(path) do |file|
   129         fullPath = File.join(path, file)
   130         stat = File.stat(fullPath)
   131         if (stat.mtime.to_i >= startTime)
   132           outputHandle.puts "  #{file}"
   133         end
   134       end
   135     end
   136   else
   137     outputHandle.puts "#{path} !"
   138   end
   139 end
   140 
   141 FSEventStreamStop(stream)
   142 FSEventStreamInvalidate(stream)
   143 FSEventStreamRelease(stream)
   144 
   145 
   146 outputHandle.flush
   147 
   148 exit(returnCode)