Replaced Thread (incompatible with inotify) by a whole dir scan after each run_on_changes

This commit is contained in:
Thibaud Guillaume-Gentil 2011-01-19 23:05:45 +01:00
parent 48b9c2b824
commit 9772e9d9c8
11 changed files with 332 additions and 190 deletions

View File

@ -1,3 +1,8 @@
== Jan 19, 2011 [by thibaudgg]
Features:
- The whole directory are now watched after that run_on_change was launched on all guards to detect new files modifications.
== Dec 17, 2010 [by netzpirat] == Dec 17, 2010 [by netzpirat]
Features: Features:
@ -14,7 +19,6 @@ Features:
Features: Features:
- It's now possible to return an enumerable in the 'watch' optional blocks in the Guardfile. - It's now possible to return an enumerable in the 'watch' optional blocks in the Guardfile.
- Listener now continue to watch changed files even when guards plugin are running.
Specs: Specs:
- Guard::Watcher - Guard::Watcher

View File

@ -29,28 +29,28 @@ module Guard
if guards.empty? if guards.empty?
UI.error "No guards found in Guardfile, please add at least one." UI.error "No guards found in Guardfile, please add at least one."
else else
listener.on_change do |files|
if Watcher.match_files?(guards, files)
run { run_on_change_for_all_guards(files) }
end
end
UI.info "Guard is now watching at '#{Dir.pwd}'" UI.info "Guard is now watching at '#{Dir.pwd}'"
guards.each { |g| supervised_task(g, :start) } guards.each { |guard| supervised_task(guard, :start) }
listener.start
Thread.new { listener.start }
wait_for_changes_and_launch_guards
end end
end end
def wait_for_changes_and_launch_guards def run_on_change_for_all_guards(files)
loop do
if !running? && !listener.changed_files.empty?
changed_files = listener.get_and_clear_changed_files
if Watcher.match_files?(guards, changed_files)
run do
guards.each do |guard| guards.each do |guard|
paths = Watcher.match_files(guard, changed_files) paths = Watcher.match_files(guard, files)
supervised_task(guard, :run_on_change, paths) unless paths.empty? supervised_task(guard, :run_on_change, paths) unless paths.empty?
end end
end # Reparse the whole directory to catch new files modified during the guards run
end new_modified_files = listener.modified_files([Dir.pwd + '/'], :all => true)
end listener.update_last_event
sleep 0.2 unless new_modified_files.empty?
run_on_change_for_all_guards(new_modified_files)
end end
end end
@ -66,17 +66,13 @@ module Guard
end end
def run def run
@run = true listener.stop
UI.clear if options[:clear] UI.clear if options[:clear]
begin begin
yield yield
rescue Interrupt rescue Interrupt
end end
@run = false listener.start
end
def running?
@run == true
end end
def add_guard(name, watchers = [], options = {}) def add_guard(name, watchers = [], options = {})

View File

@ -7,7 +7,7 @@ module Guard
autoload :Polling, 'guard/listeners/polling' autoload :Polling, 'guard/listeners/polling'
class Listener class Listener
attr_accessor :last_event, :changed_files attr_reader :last_event
def self.select_and_init def self.select_and_init
if mac? && Darwin.usable? if mac? && Darwin.usable?
@ -21,38 +21,32 @@ module Guard
end end
def initialize def initialize
@changed_files = []
update_last_event update_last_event
end end
def get_and_clear_changed_files
files = changed_files.dup
changed_files.clear
files.uniq
end
private
def find_changed_files(dirs, options = {})
files = potentially_changed_files(dirs, options).select { |path| File.file?(path) && changed_file?(path) }
files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
end
def potentially_changed_files(dirs, options = {})
match = options[:all] ? "**/*" : "*"
Dir.glob(dirs.map { |dir| "#{dir}#{match}" })
end
def changed_file?(file)
File.mtime(file) >= last_event
rescue
false
end
def update_last_event def update_last_event
@last_event = Time.now @last_event = Time.now
end end
def modified_files(dirs, options = {})
files = potentially_modified_files(dirs, options).select { |path| File.file?(path) && recent_file?(path) }
files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
end
private
def potentially_modified_files(dirs, options = {})
match = options[:all] ? "**/*" : "*"
Dir.glob(dirs.map { |dir| "#{dir}#{match}" })
end
def recent_file?(file)
File.mtime(file) >= last_event
rescue
false
end
def self.mac? def self.mac?
Config::CONFIG['target_os'] =~ /darwin/i Config::CONFIG['target_os'] =~ /darwin/i
end end
@ -63,3 +57,69 @@ module Guard
end end
end end
# require 'rbconfig'
#
# module Guard
#
# autoload :Darwin, 'guard/listeners/darwin'
# autoload :Linux, 'guard/listeners/linux'
# autoload :Polling, 'guard/listeners/polling'
#
# class Listener
# attr_accessor :last_event, :changed_files
#
# def self.select_and_init
# if mac? && Darwin.usable?
# Darwin.new
# elsif linux? && Linux.usable?
# Linux.new
# else
# UI.info "Using polling (Please help us to support your system better than that.)"
# Polling.new
# end
# end
#
# def initialize
# @changed_files = []
# update_last_event
# end
#
# def get_and_clear_changed_files
# files = changed_files.dup
# changed_files.clear
# files.uniq
# end
#
# private
#
# def find_changed_files(dirs, options = {})
# files = potentially_changed_files(dirs, options).select { |path| File.file?(path) && changed_file?(path) }
# files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
# end
#
# def potentially_changed_files(dirs, options = {})
# match = options[:all] ? "**/*" : "*"
# Dir.glob(dirs.map { |dir| "#{dir}#{match}" })
# end
#
# def changed_file?(file)
# File.mtime(file) >= last_event
# rescue
# false
# end
#
# def update_last_event
# @last_event = Time.now
# end
#
# def self.mac?
# Config::CONFIG['target_os'] =~ /darwin/i
# end
#
# def self.linux?
# Config::CONFIG['target_os'] =~ /linux/i
# end
#
# end
# end

View File

@ -5,22 +5,22 @@ module Guard
def initialize def initialize
super super
@fsevent = FSEvent.new @fsevent = FSEvent.new
fsevent.watch Dir.pwd do |modified_dirs| end
@changed_files += find_changed_files(modified_dirs)
def on_change(&callback)
@fsevent.watch Dir.pwd do |modified_dirs|
files = modified_files(modified_dirs)
update_last_event update_last_event
callback.call(files)
end end
end end
def start def start
# keep relaunching fsevent.run if not stopped by Guard @fsevent.run
while @stop != true
fsevent.run
end
end end
def stop def stop
@stop = true @fsevent.stop
fsevent.stop
end end
def self.usable? def self.usable?

View File

@ -1,23 +1,33 @@
module Guard module Guard
class Linux < Listener class Linux < Listener
attr_reader :inotify attr_reader :inotify, :files, :latency, :callback
def initialize def initialize
super super
@inotify = INotify::Notifier.new @inotify = INotify::Notifier.new
inotify.watch(Dir.pwd, :recursive, :modify, :create, :delete, :move) do |event| @files = []
unless event.name == "" # Event on root directory @latency = 0.5
@changed_files << event.absolute_name.gsub("#{Dir.pwd}/", '')
end
end
end end
def start def start
inotify.run @stop = false
watch_change unless watch_change?
end end
def stop def stop
inotify.stop @stop = true
sleep latency
end
def on_change(&callback)
@callback = callback
inotify.watch(Dir.pwd, :recursive, :modify, :create, :delete, :move) do |event|
unless event.name == "" # Event on root directory
@files << event.absolute_name
end
end
rescue Interrupt
end end
def self.usable? def self.usable?
@ -33,5 +43,31 @@ module Guard
false false
end end
def watch_change?
!!@watch_change
end
private
def watch_change
@watch_change = true
while !@stop
if Config::CONFIG['build'] =~ /java/ || IO.select([inotify.to_io], [], [], latency)
break if @stop
sleep latency
inotify.process
update_last_event
unless files.empty?
files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
callback.call(files)
files.clear
end
end
end
@watch_change = false
end
end end
end end

View File

@ -7,6 +7,10 @@ module Guard
@latency = 1.5 @latency = 1.5
end end
def on_change(&callback)
@callback = callback
end
def start def start
@stop = false @stop = false
watch_change watch_change
@ -21,10 +25,9 @@ module Guard
def watch_change def watch_change
while !@stop while !@stop
start = Time.now.to_f start = Time.now.to_f
if files = find_changed_files([Dir.pwd + '/'], :all => true) files = modified_files([Dir.pwd + '/'], :all => true)
update_last_event update_last_event
@changed_files += files callback.call(files) unless files.empty?
end
nap_time = latency - (Time.now.to_f - start) nap_time = latency - (Time.now.to_f - start)
sleep(nap_time) if nap_time > 0 sleep(nap_time) if nap_time > 0
end end

View File

@ -29,15 +29,15 @@ describe Guard::Listener do
end end
end end
describe "#get_and_clear_changed_files" do describe "#update_last_event" do
subject { Guard::Listener.new } subject { described_class.new }
it "should return uniq changed files and clear it" do it "should update last_event with time.now" do
subject.changed_files = ["foo", "bar", "bar"] time = Time.now
subject.get_and_clear_changed_files.should == ["foo", "bar"] subject.update_last_event
subject.changed_files.should be_empty subject.last_event.should >= time
end
end end
end
end end

View File

@ -16,7 +16,13 @@ describe Guard::Darwin do
end end
describe "watch" do describe "watch" do
subject { Guard::Darwin.new } before(:each) do
@results = []
@listener = Guard::Darwin.new
@listener.on_change do |files|
@results += files
end
end
it "should catch new file" do it "should catch new file" do
file = @fixture_path.join("newfile.rb") file = @fixture_path.join("newfile.rb")
@ -25,7 +31,7 @@ describe Guard::Darwin do
FileUtils.touch file FileUtils.touch file
stop stop
File.delete file File.delete file
subject.changed_files.should == ['spec/fixtures/newfile.rb'] @results.should == ['spec/fixtures/newfile.rb']
end end
it "should catch file update" do it "should catch file update" do
@ -34,7 +40,7 @@ describe Guard::Darwin do
start start
FileUtils.touch file FileUtils.touch file
stop stop
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt'] @results.should == ['spec/fixtures/folder1/file1.txt']
end end
it "should catch files update" do it "should catch files update" do
@ -46,7 +52,7 @@ describe Guard::Darwin do
FileUtils.touch file1 FileUtils.touch file1
FileUtils.touch file2 FileUtils.touch file2
stop stop
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] @results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end end
end end
end end
@ -55,13 +61,13 @@ private
def start def start
sleep 1 sleep 1
Thread.new { subject.start } Thread.new { @listener.start }
sleep 1 sleep 1
end end
def stop def stop
sleep 1 sleep 1
subject.stop @listener.stop
end end
end end

View File

@ -16,8 +16,38 @@ describe Guard::Linux do
subject.should be_usable subject.should be_usable
end end
describe "start" do
before(:each) do
@listener = Guard::Linux.new
end
it "should call watch_change if first start" do
@listener.should_receive(:watch_change)
start
end
it "should not call watch_change if start after stop" do
@listener.stub!(:stop)
start
stop
@listener.should be_watch_change
@listener.should_not_receive(:watch_change)
start
@listener.unstub!(:stop)
stop
@listener.should_not be_watch_change
end
end
describe "watch" do describe "watch" do
subject { Guard::Linux.new } before(:each) do
@results = []
@listener = Guard::Linux.new
@listener.on_change do |files|
@results += files
end
end
it "should catch new file" do it "should catch new file" do
file = @fixture_path.join("newfile.rb") file = @fixture_path.join("newfile.rb")
@ -26,7 +56,7 @@ describe Guard::Linux do
FileUtils.touch file FileUtils.touch file
stop stop
File.delete file File.delete file
subject.changed_files.should == ['spec/fixtures/newfile.rb'] @results.should == ['spec/fixtures/newfile.rb']
end end
it "should catch file update" do it "should catch file update" do
@ -35,7 +65,7 @@ describe Guard::Linux do
start start
File.open(file, 'w') {|f| f.write('') } File.open(file, 'w') {|f| f.write('') }
stop stop
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt'] @results.should == ['spec/fixtures/folder1/file1.txt']
end end
it "should catch files update" do it "should catch files update" do
@ -47,7 +77,7 @@ describe Guard::Linux do
File.open(file1, 'w') {|f| f.write('') } File.open(file1, 'w') {|f| f.write('') }
File.open(file2, 'w') {|f| f.write('') } File.open(file2, 'w') {|f| f.write('') }
stop stop
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] @results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end end
it "should catch deleted file" do it "should catch deleted file" do
@ -57,7 +87,7 @@ describe Guard::Linux do
File.delete file File.delete file
stop stop
FileUtils.touch file FileUtils.touch file
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt'] @results.should == ['spec/fixtures/folder1/file1.txt']
end end
it "should catch moved file" do it "should catch moved file" do
@ -69,17 +99,17 @@ describe Guard::Linux do
FileUtils.mv file1, file2 FileUtils.mv file1, file2
stop stop
FileUtils.mv file2, file1 FileUtils.mv file2, file1
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt'] @results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt']
end end
# it "should not process change if stopped" do it "should not process change if stopped" do
# file = @fixture_path.join("folder1/file1.txt") file = @fixture_path.join("folder1/file1.txt")
# File.exists?(file).should be_true File.exists?(file).should be_true
# start start
# subject.changed_files.inotify.should_not_receive(:process) @listener.inotify.should_not_receive(:process)
# stop stop
# File.open(file, 'w') {|f| f.write('') } File.open(file, 'w') {|f| f.write('') }
# end end
end end
end end
@ -87,13 +117,13 @@ private
def start def start
sleep 1 sleep 1
Thread.new { subject.start } Thread.new { @listener.start }
sleep 1 sleep 1
end end
def stop def stop
sleep 1 sleep 1
subject.stop @listener.stop
sleep 1 sleep 1
end end

View File

@ -2,7 +2,14 @@ require 'spec_helper'
require 'guard/listeners/polling' require 'guard/listeners/polling'
describe Guard::Polling do describe Guard::Polling do
subject { Guard::Polling.new }
before(:each) do
@results = []
@listener = Guard::Polling.new
@listener.on_change do |files|
@results += files
end
end
it "should catch new file" do it "should catch new file" do
file = @fixture_path.join("newfile.rb") file = @fixture_path.join("newfile.rb")
@ -11,7 +18,7 @@ describe Guard::Polling do
FileUtils.touch file FileUtils.touch file
stop stop
File.delete file File.delete file
subject.changed_files.should == ['spec/fixtures/newfile.rb'] @results.should == ['spec/fixtures/newfile.rb']
end end
it "should catch file update" do it "should catch file update" do
@ -20,7 +27,7 @@ describe Guard::Polling do
start start
FileUtils.touch file FileUtils.touch file
stop stop
subject.changed_files.should == ['spec/fixtures/folder1/file1.txt'] @results.should == ['spec/fixtures/folder1/file1.txt']
end end
it "should catch files update" do it "should catch files update" do
@ -32,19 +39,19 @@ describe Guard::Polling do
FileUtils.touch file1 FileUtils.touch file1
FileUtils.touch file2 FileUtils.touch file2
stop stop
subject.changed_files.sort.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] @results.sort.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end end
private private
def start def start
Thread.new { subject.start } Thread.new { @listener.start }
sleep 1 sleep 1
end end
def stop def stop
sleep 1 sleep 1
subject.stop @listener.stop
end end
end end