aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock10
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/file_evented_update_checker.rb67
-rw-r--r--activesupport/test/file_evented_update_checker_test.rb21
-rw-r--r--activesupport/test/file_update_checker_test.rb107
-rw-r--r--activesupport/test/file_update_checker_with_enumerable_test_cases.rb110
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile3
9 files changed, 218 insertions, 104 deletions
diff --git a/Gemfile b/Gemfile
index 260604f570..bc14498372 100644
--- a/Gemfile
+++ b/Gemfile
@@ -45,6 +45,7 @@ end
# Active Support.
gem 'dalli', '>= 2.2.1'
+gem 'listen', '~> 3.0.3'
# Active Job.
group :job do
diff --git a/Gemfile.lock b/Gemfile.lock
index 4d58065000..b3225dedea 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -212,6 +212,9 @@ GEM
delayed_job (>= 3.0, < 5)
erubis (2.7.0)
execjs (2.6.0)
+ ffi (1.9.10)
+ ffi (1.9.10-x64-mingw32)
+ ffi (1.9.10-x86-mingw32)
hitimes (1.2.3)
hitimes (1.2.3-x86-mingw32)
i18n (0.7.0)
@@ -219,6 +222,9 @@ GEM
kindlerb (0.1.1)
mustache
nokogiri
+ listen (3.0.3)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
loofah (2.0.3)
nokogiri (>= 1.5.9)
metaclass (0.0.4)
@@ -252,6 +258,9 @@ GEM
rails-html-sanitizer (1.0.2)
loofah (~> 2.0)
rake (10.4.2)
+ rb-fsevent (0.9.6)
+ rb-inotify (0.9.5)
+ ffi (>= 0.5.0)
rdoc (4.2.0)
redcarpet (3.2.3)
redis (3.2.1)
@@ -333,6 +342,7 @@ DEPENDENCIES
jquery-rails!
json
kindlerb (= 0.1.1)
+ listen (~> 3.0.3)
mail!
minitest (< 5.3.4)
mocha (~> 0.14)
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 63277a65b4..3a2a7d28cb 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -34,6 +34,7 @@ module ActiveSupport
autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
+ autoload :FileEventedUpdateChecker
autoload :LogSubscriber
autoload :Notifications
diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb
new file mode 100644
index 0000000000..d45576bb00
--- /dev/null
+++ b/activesupport/lib/active_support/file_evented_update_checker.rb
@@ -0,0 +1,67 @@
+require 'listen'
+
+module ActiveSupport
+ class FileEventedUpdateChecker
+ attr_reader :listener
+ def initialize(files, directories={}, &block)
+ @files = files.map { |f| File.expand_path(f)}.to_set
+ @dirs = Hash.new
+ directories.each do |key,value|
+ @dirs[File.expand_path(key)] = Array(value) if !Array(value).empty?
+ end
+ @block = block
+ @modified = false
+ watch_dirs = base_directories
+ @listener = Listen.to(*watch_dirs,&method(:changed)) if !watch_dirs.empty?
+ @listener.start if @listener
+ end
+
+ def updated?
+ @modified
+ end
+
+ def execute
+ @block.call
+ ensure
+ @modified = false
+ end
+
+ def execute_if_updated
+ if updated?
+ execute
+ true
+ else
+ false
+ end
+ end
+
+ private
+
+ def watching?(file)
+ return true if @files.include?(file)
+ cfile = file
+ while !cfile.eql? "/"
+ cfile = File.expand_path("#{cfile}/..")
+ if !@dirs[cfile].nil? and file.end_with?(*(@dirs[cfile].map {|ext| ".#{ext.to_s}"}))
+ return true
+ end
+ end
+ false
+ end
+
+ def changed(modified, added, removed)
+ return if updated?
+ if (modified + added + removed).any? { |f| watching? f }
+ @modified = true
+ end
+ end
+
+ def base_directories
+ (@files.map { |f| existing_parent(File.expand_path("#{f}/..")) } + @dirs.keys.map {|dir| existing_parent(dir)}).uniq
+ end
+
+ def existing_parent(path)
+ File.exist?(path) ? path : existing_parent(File.expand_path("#{path}/.."))
+ end
+ end
+end
diff --git a/activesupport/test/file_evented_update_checker_test.rb b/activesupport/test/file_evented_update_checker_test.rb
new file mode 100644
index 0000000000..09087738dc
--- /dev/null
+++ b/activesupport/test/file_evented_update_checker_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+require 'fileutils'
+require 'thread'
+require 'file_update_checker_with_enumerable_test_cases'
+
+MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
+
+class FileEventedUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
+ include FileUpdateCheckerWithEnumerableTestCases
+ def build_new_watcher(files, dirs={}, &block)
+ ActiveSupport::FileEventedUpdateChecker.new(files, dirs, &block)
+ end
+
+ def test_modified_should_become_true_when_watched_file_is_updated
+ watcher = ActiveSupport::FileEventedUpdateChecker.new(FILES){ i += 1 }
+ assert_equal watcher.updated?, false
+ FileUtils.rm(FILES)
+ sleep 1
+ assert_equal watcher.updated?, true
+ end
+end
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index bd1df0f858..c61193d133 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -1,112 +1,13 @@
require 'abstract_unit'
require 'fileutils'
require 'thread'
+require 'file_update_checker_with_enumerable_test_cases'
MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
- FILES = %w(1.txt 2.txt 3.txt)
-
- def setup
- FileUtils.mkdir_p("tmp_watcher")
- FileUtils.touch(FILES)
- end
-
- def teardown
- FileUtils.rm_rf("tmp_watcher")
- FileUtils.rm_rf(FILES)
- end
-
- def test_should_not_execute_the_block_if_no_paths_are_given
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
- checker.execute_if_updated
- assert_equal 0, i
- end
-
- def test_should_not_invoke_the_block_if_no_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- 5.times { assert !checker.execute_if_updated }
- assert_equal 0, i
- end
-
- def test_should_invoke_the_block_if_a_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- sleep(1)
- FileUtils.touch(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_enough_to_handle_deleted_files
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- FileUtils.rm(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_to_handle_files_with_wrong_modified_time
- i = 0
- now = Time.now
- time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
- File.utime time, time, FILES[2]
-
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
-
- sleep(1)
- FileUtils.touch(FILES[0..1])
-
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_cache_updated_result_until_execute
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- assert !checker.updated?
-
- sleep(1)
- FileUtils.touch(FILES)
-
- assert checker.updated?
- checker.execute
- assert !checker.updated?
- end
-
- def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert !checker.execute_if_updated
- assert_equal 0, i
- end
-
- def test_should_not_block_if_a_strange_filename_used
- FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
- FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
-
- test = Thread.new do
- ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
- Thread.exit
- end
- test.priority = -1
- test.join(5)
-
- assert !test.alive?
+ include FileUpdateCheckerWithEnumerableTestCases
+ def build_new_watcher(files, dirs={}, &block)
+ ActiveSupport::FileUpdateChecker.new(files, dirs, &block)
end
end
diff --git a/activesupport/test/file_update_checker_with_enumerable_test_cases.rb b/activesupport/test/file_update_checker_with_enumerable_test_cases.rb
new file mode 100644
index 0000000000..cd1f12d42f
--- /dev/null
+++ b/activesupport/test/file_update_checker_with_enumerable_test_cases.rb
@@ -0,0 +1,110 @@
+module FileUpdateCheckerWithEnumerableTestCases
+ FILES = %w(1.txt 2.txt 3.txt)
+
+ def setup
+ FileUtils.mkdir_p("tmp_watcher")
+ FileUtils.touch(FILES)
+ end
+
+ def teardown
+ FileUtils.rm_rf("tmp_watcher")
+ FileUtils.rm_rf(FILES)
+ end
+
+ def test_should_not_execute_the_block_if_no_paths_are_given
+ i = 0
+ checker = build_new_watcher([]){ i += 1}
+ checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_invoke_the_block_if_no_file_has_changed
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ 5.times { assert !checker.execute_if_updated }
+ assert_equal 0, i
+ end
+
+ def test_should_invoke_the_block_if_a_file_has_changed
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ sleep(1)
+ FileUtils.touch(FILES)
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_be_robust_enough_to_handle_deleted_files
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ FileUtils.rm(FILES)
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_be_robust_to_handle_files_with_wrong_modified_time
+ i = 0
+ now = Time.now
+ time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
+ File.utime time, time, FILES[2]
+
+ checker = build_new_watcher(FILES){ i += 1 }
+
+ sleep(1)
+ FileUtils.touch(FILES[0..1])
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_cache_updated_result_until_execute
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ assert !checker.updated?
+
+ sleep(1)
+ FileUtils.touch(FILES)
+ sleep(1) #extra
+ assert checker.updated?
+ checker.execute
+ assert !checker.updated?
+ end
+
+ def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
+ i = 0
+ checker = build_new_watcher([], "tmp_watcher" => [:txt]){ i += 1 }
+ FileUtils.cd "tmp_watcher" do
+ FileUtils.touch(FILES)
+ end
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
+ i = 0
+ checker = build_new_watcher([], "tmp_watcher" => :rb){ i += 1 }
+ FileUtils.cd "tmp_watcher" do
+ FileUtils.touch(FILES)
+ end
+ sleep(1) #extra
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_block_if_a_strange_filename_used
+ FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
+ FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
+
+ test = Thread.new do
+ build_new_watcher([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
+ Thread.exit
+ end
+ test.priority = -1
+ test.join(5)
+
+ assert !test.alive?
+ end
+end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 785671f70b..6d68eea220 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -44,7 +44,7 @@ module Rails
@railties_order = [:all]
@relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
@reload_classes_only_on_change = true
- @file_watcher = ActiveSupport::FileUpdateChecker
+ @file_watcher = (defined?(Listen) && Listen::Adapter.select()!=Listen::Adapter::Polling)? ActiveSupport::FileEventedUpdateChecker : ActiveSupport::FileUpdateChecker
@exceptions_app = nil
@autoflush_log = true
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 975be07622..87ef60288d 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -53,3 +53,6 @@ end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+
+# Uncomment this, to increase the performance
+# gem 'listen', '~> 3.0.3'