blob: d4acf966a8b9d9b7f636b7678b701fee54b03692 [file] [log] [blame]
require 'thread'
require 'listen/internals/thread_pool'
module Listen
module Adapter
# Adapter implementation for Mac OS X `FSEvents`.
#
class Darwin < Base
OS_REGEXP = /darwin(?<major_version>1\d+)/i
# The default delay between checking for changes.
DEFAULTS = { latency: 0.1 }
INCOMPATIBLE_GEM_VERSION = <<-EOS.gsub(/^ {8}/, '')
rb-fsevent > 0.9.4 no longer supports OS X 10.6 through 10.8.
Please add the following to your Gemfile to avoid polling for changes:
require 'rbconfig'
if RbConfig::CONFIG['target_os'] =~ /darwin(1[0-3])/i
gem 'rb-fsevent', '<= 0.9.4'
end
EOS
def self.usable?
require 'rb-fsevent'
darwin_version = RbConfig::CONFIG['target_os'][OS_REGEXP, :major_version] or return false
return true if darwin_version.to_i >= 13 # darwin13 is OS X 10.9
return true if Gem::Version.new(FSEvent::VERSION) <= Gem::Version.new('0.9.4')
Kernel.warn INCOMPATIBLE_GEM_VERSION
false
end
private
# NOTE: each directory gets a DIFFERENT callback!
def _configure(dir, &callback)
opts = { latency: options.latency }
@workers ||= ::Queue.new
@workers << FSEvent.new.tap do |worker|
_log :debug, "fsevent: watching: #{dir.to_s.inspect}"
worker.watch(dir.to_s, opts, &callback)
end
end
def _run
first = @workers.pop
# NOTE: _run is called within a thread, so run every other
# worker in it's own thread
_run_workers_in_background(_to_array(@workers))
_run_worker(first)
end
def _process_event(dir, event)
_log :debug, "fsevent: processing event: #{event.inspect}"
event.each do |path|
new_path = Pathname.new(path.sub(/\/$/, ''))
_log :debug, "fsevent: #{new_path}"
# TODO: does this preserve symlinks?
rel_path = new_path.relative_path_from(dir).to_s
_queue_change(:dir, dir, rel_path, recursive: true)
end
end
def _run_worker(worker)
_log :debug, "fsevent: running worker: #{worker.inspect}"
worker.run
rescue
_log_exception 'fsevent: running worker failed: %s:%s called from: %s', caller
end
def _run_workers_in_background(workers)
workers.each do |worker|
# NOTE: while passing local variables to the block below is not
# thread safe, using 'worker' from the enumerator above is ok
Listen::Internals::ThreadPool.add { _run_worker(worker) }
end
end
def _to_array(queue)
workers = []
workers << queue.pop until queue.empty?
workers
end
end
end
end