src/scripts/fswatch
author Dave Dribin <dave@dribin.org>
Tue Jun 21 21:01:42 2011 -0500 (11 months ago)
changeset 111 b525fe70ba68
parent 73 e493ac32ba79
permissions -rwxr-xr-x
Add --url option to configure API URL
     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 options.show_current_id = false
    48 options.start_id = nil
    49 options.path = "/"
    50 
    51 opts = OptionParser.new do |opts|
    52   opts.banner = $USAGE
    53   opts.separator ""
    54   opts.separator "Specific options:"
    55 
    56   opts.on("-d", "--dir-only", "Only display directories") do
    57     options.directoriesOnly = true
    58   end
    59   
    60   opts.on("-o", "--output FILE", "Write output to a file") do |fileName|
    61     options.outputFile = fileName
    62   end
    63   
    64   opts.on("-w", "--wait", "Wait until interrupted") do
    65     options.wait = true
    66   end
    67   
    68   opts.on("-c", "--current-id", "Show current id and exit") do
    69     options.show_current_id = true
    70   end
    71   
    72   opts.on("-C", "--use-start-id START_ID", "Use start ID") do |start_id|
    73     options.start_id = start_id
    74   end
    75   
    76   opts.on("-p", "--path PATH", "Path to show") do |path|
    77     options.path = path
    78   end
    79   
    80   opts.on_tail("-h", "--help", "Show this message") do
    81     puts opts
    82     exit
    83   end
    84 end
    85 opts.parse!(ARGV)
    86 
    87 if !options.wait and ARGV == 0
    88   die $USAGE
    89 end
    90 
    91 if options.outputFile == "-"
    92   outputHandle = $stdout.clone
    93 else
    94   outputHandle = File.open(options.outputFile, "w")
    95 end
    96 
    97 path = options.path
    98 
    99 startId = FSEventsGetCurrentEventId()
   100 # Used to compare with mtime, which only has second accuracy
   101 startTime = Time.now.to_i
   102 
   103 if (options.show_current_id)
   104   puts "#{startId}:#{startTime}"
   105   exit 0
   106 end
   107 
   108 if !options.start_id.nil? then
   109   startId, startTime = options.start_id.split(':').map { |s| s.to_i }
   110   returnCode = 0
   111 elsif options.wait then
   112   wait_until_interrupt
   113   returnCode = 0
   114 else
   115   returnCode = run_command(*ARGV)
   116 end
   117 
   118 allPaths = Set.new
   119 
   120 fsevents_cb = proc do |stream, ctx, numEvents, paths, marks, eventIDs|
   121   paths.regard_as('*')
   122   numEvents.times do |n|
   123     allPaths.add(paths[n])
   124   end
   125 end
   126 
   127 stream = FSEventStreamCreate(
   128   KCFAllocatorDefault,
   129   fsevents_cb,
   130   nil,
   131   [path],
   132   startId,
   133   1.0,
   134   KFSEventStreamCreateFlagNoDefer)
   135 
   136 die "Failed to create the FSEventStream" unless stream
   137 
   138 FSEventStreamScheduleWithRunLoop(
   139   stream,
   140   CFRunLoopGetCurrent(),
   141   KCFRunLoopDefaultMode)
   142 
   143 ok = FSEventStreamStart(stream)
   144 die "Failed to start the FSEventStream" unless ok
   145 
   146 FSEventStreamFlushSync(stream)
   147 puts allPaths.to_a
   148 puts
   149 allPaths.sort.each do |path|
   150   if File.exists?(path)
   151     outputHandle.puts path
   152     if (!options.directoriesOnly)
   153       Dir.foreach(path) do |file|
   154         fullPath = File.join(path, file)
   155         stat = File.stat(fullPath)
   156         if (stat.mtime.to_i >= startTime)
   157           outputHandle.puts "  #{file}"
   158         end
   159       end
   160     end
   161   else
   162     outputHandle.puts "#{path} !"
   163   end
   164 end
   165 
   166 FSEventStreamStop(stream)
   167 FSEventStreamInvalidate(stream)
   168 FSEventStreamRelease(stream)
   169 
   170 
   171 outputHandle.flush
   172 
   173 exit(returnCode)