4

I'm working on GUI app in MacRuby and I need to use FSEvents. I'm registering a couple of streams for different directories. Changes in any of those directories result in running a callback, but there's a big issue: no matter which directory changes, the last registered callback is executed.

Below is isolated test script:

framework 'Cocoa'
framework 'CoreServices'

class Monitor
  def initialize(dir)
    @dir = dir
  end

  def start(&block)
    callback = Proc.new do |stream, context, count, paths, flags, ids|
      p @dir
      block.call
    end

    flags = KFSEventStreamCreateFlagUseCFTypes

    @stream = FSEventStreamCreate(KCFAllocatorDefault, callback, nil, [@dir], KFSEventStreamEventIdSinceNow, 0.0, flags)
    FSEventStreamScheduleWithRunLoop(@stream, CFRunLoopGetCurrent(), KCFRunLoopDefaultMode)
    FSEventStreamStart(@stream)
  end
end

Monitor.new(Dir.pwd + "/dir1").start { p "dir1" }
Monitor.new(Dir.pwd + "/dir2").start { p "dir2" }
Monitor.new(Dir.pwd + "/dir3").start { p "dir3" }

app = NSApplication.sharedApplication
app.run

When I run it and start modifying those directories:

~/tmp/fsevents $ touch dir1/test
~/tmp/fsevents $ touch dir2/test
~/tmp/fsevents $ touch dir3/test

the output is:

"/Users/janek/tmp/fsevents/dir3"
"dir3"
"/Users/janek/tmp/fsevents/dir3"
"dir3"
"/Users/janek/tmp/fsevents/dir3"
"dir3"

What I'd rather expect is:

"/Users/janek/tmp/fsevents/dir1"
"dir1"
"/Users/janek/tmp/fsevents/dir2"
"dir2"
"/Users/janek/tmp/fsevents/dir3"
"dir3"

Maybe I could work around this issue by providing the data I need via context argument (because inspecting paths inside callback reveals directory that actually changed), but still, the current behaviour is totally unexpected to me.

I'm using OS X 10.8.2 (12C60) and MacRuby 0.12 (ruby 1.9.2) [universal-darwin10.0, x86_64].

Jan Dudek
  • 807
  • 1
  • 9
  • 17

1 Answers1

0

Yeah, this is really weird. I also have this behavior. It seems like the latest registered callback always gets called. But on the bright side, it is possible, from the fourth argument to the callback, to get the path to the directory that actually got called. I had to use a construct like this.

I'm not much of a rubyist, but this code works for me.

framework 'Cocoa'
framework 'CoreServices'


class Monitor
  @@registry = {}
  def self.register(dir, other_data)
    @@registry[dir] = other_data
  end

  def initialize(dir, other_data)
      @dir = dir

      self.class.register(dir, other_data)
      callback = Proc.new do |stream, context, count, paths, flags, ids|
          paths.cast!('*')

          p "the callback that triggered has closure variable @dir=#{@dir}"
          p "but the actual callback said the dir was #{paths[0]}"
          p "the metadata that I stored associated with that directory is #{@@registry[paths[0]]}"
      end


      @stream = FSEventStreamCreate(KCFAllocatorDefault, callback, nil, [@dir], KFSEventStreamEventIdSinceNow, 0.0, 0)
      FSEventStreamScheduleWithRunLoop(@stream, CFRunLoopGetCurrent(), KCFRunLoopDefaultMode)
      FSEventStreamStart(@stream)
  end
end

Monitor.new(Dir.pwd + "/dir1/", 'dir1 data')
Monitor.new(Dir.pwd + "/dir2/", 'dir2 data')
Monitor.new(Dir.pwd + "/dir3/", 'dir3 data')

app = NSApplication.sharedApplication
app.run

Here's the output I see:

rmcgibbo@Roberts-MacBook-Pro-2 ~/local/fsync
$ macruby fsevents.rb &
[1] 14638

rmcgibbo@Roberts-MacBook-Pro-2 ~/local/fsync
$ touch dir1/mao

rmcgibbo@Roberts-MacBook-Pro-2 ~/local/fsync
$ "the callback that triggered has closure variable @dir=/Users/rmcgibbo/local/fsync/dir3/"
"but the actual callback said the dir was /Users/rmcgibbo/local/fsync/dir1/"
"the metadata that I stored associated with that directory is dir1 data"
Robert T. McGibbon
  • 5,075
  • 3
  • 37
  • 45