Permalink
Browse files

Merge pull request #56 from guard/wdm

Replace FChange with WDM as the platform-specific adapter for Windows.
  • Loading branch information...
Maher4Ever committed Aug 18, 2012
2 parents e985b25 + ed6a39a commit c37d9aa9f906cf2e308ae7587e39190ac5a6c7ab
View
@@ -11,7 +11,10 @@ rvm:
- rbx-18mode
- rbx-19mode
bundler_args: --without=development
-env: TEST_LATENCY=1.0
+matrix:
+ allow_failures:
+ - rvm: ruby-head
+ - rvm: jruby-head
notifications:
recipients:
- thibaud@thibaud.me
View
@@ -1,9 +1,15 @@
+require 'rbconfig'
+
source :rubygems
gemspec
gem 'rake'
+gem 'rb-fsevent', '~> 0.9.1' if RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
+gem 'rb-inotify', '~> 0.8.8', :github => 'mbj/rb-inotify' if RbConfig::CONFIG['target_os'] =~ /linux/i
+gem 'wdm', '~> 0.0.3' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+
group :development do
platform :ruby do
gem 'coolline'
@@ -20,4 +26,4 @@ end
group :test do
gem 'rspec'
-end
+end
View
@@ -251,7 +251,6 @@ want to force the use of the polling adapter, either use the `:force_polling` op
while initializing the listener or call the `force_polling` method on your listener
before starting it.
-<a name="fallback"/>
## Polling fallback
When a OS-specific adapter doesn't work the Listen gem automatically falls back to the polling adapter.
View
@@ -1,10 +1,11 @@
module Listen
- autoload :Turnstile, 'listen/turnstile'
- autoload :Listener, 'listen/listener'
- autoload :MultiListener, 'listen/multi_listener'
- autoload :DirectoryRecord, 'listen/directory_record'
- autoload :Adapter, 'listen/adapter'
+ autoload :Turnstile, 'listen/turnstile'
+ autoload :Listener, 'listen/listener'
+ autoload :MultiListener, 'listen/multi_listener'
+ autoload :DirectoryRecord, 'listen/directory_record'
+ autoload :DependencyManager, 'listen/dependency_manager'
+ autoload :Adapter, 'listen/adapter'
module Adapters
autoload :Darwin, 'listen/adapters/darwin'
View
@@ -10,8 +10,15 @@ class Adapter
# The default delay between checking for changes.
DEFAULT_LATENCY = 0.25
+ # The default warning message when there is a missing dependency.
+ MISSING_DEPENDENCY_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ For a better performance, it's recommended that you satisfy the missing dependency.
+ EOS
+
# The default warning message when falling back to polling adapter.
- POLLING_FALLBACK_MESSAGE = "WARNING: Listen has fallen back to polling, learn more at https://github.com/guard/listen#fallback."
+ POLLING_FALLBACK_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ Listen will be polling changes. Learn more at https://github.com/guard/listen#polling-fallback.
+ EOS
# Selects the appropriate adapter implementation for the
# current OS and initializes it.
@@ -31,25 +38,34 @@ class Adapter
def self.select_and_initialize(directories, options = {}, &callback)
return Adapters::Polling.new(directories, options, &callback) if options.delete(:force_polling)
- if Adapters::Darwin.usable_and_works?(directories, options)
- Adapters::Darwin.new(directories, options, &callback)
- elsif Adapters::Linux.usable_and_works?(directories, options)
- Adapters::Linux.new(directories, options, &callback)
- elsif Adapters::Windows.usable_and_works?(directories, options)
- Adapters::Windows.new(directories, options, &callback)
- else
- unless options[:polling_fallback_message] == false
- Kernel.warn(options[:polling_fallback_message] || POLLING_FALLBACK_MESSAGE)
+ warning = ''
+
+ begin
+ if Adapters::Darwin.usable_and_works?(directories, options)
+ return Adapters::Darwin.new(directories, options, &callback)
+ elsif Adapters::Linux.usable_and_works?(directories, options)
+ return Adapters::Linux.new(directories, options, &callback)
+ elsif Adapters::Windows.usable_and_works?(directories, options)
+ return Adapters::Windows.new(directories, options, &callback)
end
- Adapters::Polling.new(directories, options, &callback)
+ rescue DependencyManager::Error => e
+ warning += e.message + "\n" + MISSING_DEPENDENCY_MESSAGE
+ end
+
+ unless options[:polling_fallback_message] == false
+ warning += options[:polling_fallback_message] || POLLING_FALLBACK_MESSAGE
+ Kernel.warn "[Listen warning]:\n" + warning.gsub(/^(.*)/, ' \1')
end
+
+ Adapters::Polling.new(directories, options, &callback)
end
# Initializes the adapter.
#
# @param [String, Array<String>] directories the directories to watch
# @param [Hash] options the adapter options
# @option options [Float] latency the delay between checking for changes in seconds
+ # @option options [Boolean] report_changes whether or not to automatically report changes (run the callback)
#
# @yield [changed_dirs, options] callback Callback called when a change happens
# @yieldparam [Array<String>] changed_dirs the changed directories
@@ -60,12 +76,13 @@ def self.select_and_initialize(directories, options = {}, &callback)
def initialize(directories, options = {}, &callback)
@directories = Array(directories)
@callback = callback
- @latency ||= DEFAULT_LATENCY
- @latency = options[:latency] if options[:latency]
@paused = false
@mutex = Mutex.new
@changed_dirs = Set.new
@turnstile = Turnstile.new
+ @latency ||= DEFAULT_LATENCY
+ @latency = options[:latency] if options[:latency]
+ @report_changes = options[:report_changes].nil? ? true : options[:report_changes]
end
# Starts the adapter.
@@ -92,12 +109,37 @@ def started?
end
# Blocks the main thread until the poll thread
- # calls the callback.
+ # runs the callback.
#
def wait_for_callback
@turnstile.wait unless @paused
end
+ # Blocks the main thread until N changes are
+ # detected.
+ #
+ def wait_for_changes(goal = 0)
+ changes = 0
+
+ loop do
+ @mutex.synchronize { changes = @changed_dirs.size }
+
+ return if @paused || @stop
+ return if changes >= goal
+
+ sleep(@latency)
+ end
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ load_depenencies
+ dependencies_loaded?
+ end
+
# Checks if the adapter is usable and works on the current OS.
#
# @param [String, Array<String>] directories the directories to watch
@@ -140,26 +182,29 @@ def self.works?(directory, options = {})
adapter.stop if adapter && adapter.started?
end
+ # Runs the callback and passes it the changes if there are any.
+ #
+ def report_changes
+ changed_dirs = nil
+
+ @mutex.synchronize do
+ return if @changed_dirs.empty?
+ changed_dirs = @changed_dirs.to_a
+ @changed_dirs.clear
+ end
+
+ @callback.call(changed_dirs, {})
+ end
+
private
# Polls changed directories and reports them back
# when there are changes.
#
- # @option [Boolean] recursive whether or not to pass the recursive option to the callback
- #
- def poll_changed_dirs(recursive = false)
+ def poll_changed_dirs
until @stop
sleep(@latency)
- next if @changed_dirs.empty?
-
- changed_dirs = []
-
- @mutex.synchronize do
- changed_dirs = @changed_dirs.to_a
- @changed_dirs.clear
- end
-
- @callback.call(changed_dirs, recursive ? {:recursive => recursive} : {})
+ report_changes
@turnstile.signal
end
end
@@ -4,6 +4,10 @@ module Adapters
# Adapter implementation for Mac OS X `FSEvents`.
#
class Darwin < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-fsevent', '~> 0.9.1'
LAST_SEPARATOR_REGEX = /\/$/
@@ -25,13 +29,14 @@ def start(blocking = true)
end
@worker_thread = Thread.new { @worker.run }
- @poll_thread = Thread.new { poll_changed_dirs }
# The FSEvent worker needs sometime to startup. Turnstiles can't
# be used to wait for it as it runs in a loop.
# TODO: Find a better way to block until the worker starts.
- sleep @latency
- @poll_thread.join if blocking
+ sleep 0.1
+
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+ @worker_thread.join if blocking
end
# Stops the adapter.
@@ -43,8 +48,8 @@ def stop
end
@worker.stop
- Thread.kill(@worker_thread) if @worker_thread
- @poll_thread.join
+ @worker_thread.join if @worker_thread
+ @poll_thread.join if @poll_thread
end
# Checks if the adapter is usable on the current OS.
@@ -53,11 +58,7 @@ def stop
#
def self.usable?
return false unless RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
-
- require 'rb-fsevent'
- true
- rescue LoadError
- false
+ super
end
private
@@ -4,6 +4,10 @@ module Adapters
# Listener implementation for Linux `inotify`.
#
class Linux < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-inotify', '~> 0.8.8'
# Watched inotify events
#
@@ -41,8 +45,9 @@ def start(blocking = true)
end
@worker_thread = Thread.new { @worker.run }
- @poll_thread = Thread.new { poll_changed_dirs }
- @poll_thread.join if blocking
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+
+ @worker_thread.join if blocking
end
# Stops the adapter.
@@ -55,20 +60,16 @@ def stop
@worker.stop
Thread.kill(@worker_thread) if @worker_thread
- @poll_thread.join
+ @poll_thread.join if @poll_thread
end
- # Check if the adapter is usable on the current OS.
+ # Checks if the adapter is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable?
return false unless RbConfig::CONFIG['target_os'] =~ /linux/i
-
- require 'rb-inotify'
- true
- rescue LoadError
- false
+ super
end
private
@@ -79,29 +80,31 @@ def self.usable?
# @return [INotify::Notifier] initialized worker
#
def init_worker
- worker = INotify::Notifier.new
- @directories.each do |directory|
- worker.watch(directory, *EVENTS.map(&:to_sym)) do |event|
- if @paused || (
- # Event on root directory
- event.name == ""
- ) || (
- # INotify reports changes to files inside directories as events
- # on the directories themselves too.
- #
- # @see http://linux.die.net/man/7/inotify
- event.flags.include?(:isdir) and event.flags & [:close, :modify] != []
- )
- # Skip all of these!
- next
- end
-
- @mutex.synchronize do
- @changed_dirs << File.dirname(event.absolute_name)
- end
+ callback = lambda do |event|
+ if @paused || (
+ # Event on root directory
+ event.name == ""
+ ) || (
+ # INotify reports changes to files inside directories as events
+ # on the directories themselves too.
+ #
+ # @see http://linux.die.net/man/7/inotify
+ event.flags.include?(:isdir) and event.flags & [:close, :modify] != []
+ )
+ # Skip all of these!
+ next
+ end
+
+ @mutex.synchronize do
+ @changed_dirs << File.dirname(event.absolute_name)
+ end
+ end
+
+ INotify::Notifier.new.tap do |worker|
+ @directories.each do |directory|
+ worker.watch(directory, *EVENTS.map(&:to_sym), &callback)
end
end
- worker
end
end
@@ -10,6 +10,7 @@ module Adapters
# file IO that the other implementations.
#
class Polling < Adapter
+ extend DependencyManager
# Initialize the Adapter. See {Listen::Adapter#initialize} for more info.
#
Oops, something went wrong.

0 comments on commit c37d9aa

Please sign in to comment.