forked from Compass/compass
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use FSSM by Travis Tilley to monitor for filesystem changes. On mac t…
…his will use filesystem events instead of polling. Fixes an infinite looping issue when compilation errors occur.
- Loading branch information
1 parent
d40f72a
commit 005f6d4
Showing
9 changed files
with
342 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module FSSM | ||
FileNotFoundError = Class.new(StandardError) | ||
CallbackError = Class.new(StandardError) | ||
|
||
class << self | ||
def monitor(*args, &block) | ||
monitor = FSSM::Monitor.new | ||
context = args.empty? ? monitor : monitor.path(*args) | ||
if block && block.arity == 0 | ||
context.instance_eval(&block) | ||
elsif block && block.arity == 1 | ||
block.call(context) | ||
end | ||
monitor.run | ||
end | ||
end | ||
end | ||
|
||
$:.unshift(File.dirname(__FILE__)) | ||
require 'pathname' | ||
require 'fssm/ext' | ||
require 'fssm/support' | ||
require 'fssm/path' | ||
require 'fssm/state' | ||
require 'fssm/monitor' | ||
|
||
require "fssm/backends/#{FSSM::Support.backend.downcase}" | ||
FSSM::Backends::Default = FSSM::Backends.const_get(FSSM::Support.backend) | ||
$:.shift | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
module FSSM::Backends | ||
class FSEvents | ||
def initialize(options={}) | ||
@streams = [] | ||
@handlers = {} | ||
@allocator = options[:allocator] || OSX::KCFAllocatorDefault | ||
@context = options[:context] || nil | ||
@since = options[:since] || OSX::KFSEventStreamEventIdSinceNow | ||
@latency = options[:latency] || 0.0 | ||
@flags = options[:flags] || 0 | ||
end | ||
|
||
def add_path(path, preload=true) | ||
@handlers["#{path}"] = FSSM::State.new(path, preload) | ||
|
||
cb = lambda do |stream, context, number, paths, flags, ids| | ||
paths.regard_as('*') | ||
watched = OSX.FSEventStreamCopyPathsBeingWatched(stream).first | ||
@handlers["#{watched}"].refresh | ||
# TODO: support this level of granularity | ||
# number.times do |n| | ||
# @handlers["#{watched}"].refresh_path(paths[n]) | ||
# end | ||
end | ||
|
||
@streams << create_stream(cb, "#{path}") | ||
end | ||
|
||
def run | ||
@streams.each do |stream| | ||
schedule_stream(stream) | ||
start_stream(stream) | ||
end | ||
|
||
begin | ||
OSX.CFRunLoopRun | ||
rescue Interrupt | ||
@streams.each do |stream| | ||
stop_stream(stream) | ||
invalidate_stream(stream) | ||
release_stream(stream) | ||
end | ||
end | ||
|
||
end | ||
|
||
private | ||
|
||
def create_stream(callback, paths) | ||
paths = [paths] unless paths.is_a?(Array) | ||
OSX.FSEventStreamCreate(@allocator, callback, @context, paths, @since, @latency, @flags) | ||
end | ||
|
||
def schedule_stream(stream, options={}) | ||
run_loop = options[:run_loop] || OSX.CFRunLoopGetCurrent | ||
loop_mode = options[:loop_mode] || OSX::KCFRunLoopDefaultMode | ||
|
||
OSX.FSEventStreamScheduleWithRunLoop(stream, run_loop, loop_mode) | ||
end | ||
|
||
def start_stream(stream) | ||
OSX.FSEventStreamStart(stream) | ||
end | ||
|
||
def stop_stream(stream) | ||
OSX.FSEventStreamStop(stream) | ||
end | ||
|
||
def invalidate_stream(stream) | ||
OSX.FSEventStreamInvalidate(stream) | ||
end | ||
|
||
def release_stream(stream) | ||
OSX.FSEventStreamRelease(stream) | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module FSSM::Backends | ||
class Polling | ||
def initialize(options={}) | ||
@handlers = [] | ||
@latency = options[:latency] || 1 | ||
end | ||
|
||
def add_path(path, preload=true) | ||
@handlers << FSSM::State.new(path, preload) | ||
end | ||
|
||
def run | ||
begin | ||
loop do | ||
start = Time.now.to_f | ||
@handlers.each {|handler| handler.refresh} | ||
nap_time = @latency - (Time.now.to_f - start) | ||
sleep nap_time if nap_time > 0 | ||
end | ||
rescue Interrupt | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class Pathname | ||
class << self | ||
def for(path) | ||
path.is_a?(Pathname) ? path : new(path) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
class FSSM::Monitor | ||
def initialize(options={}) | ||
@options = options | ||
@backend = FSSM::Backends::Default.new | ||
end | ||
|
||
def path(*args, &block) | ||
path = FSSM::Path.new(*args) | ||
if block && block.arity == 0 | ||
path.instance_eval(&block) | ||
elsif block && block.arity == 1 | ||
block.call(path) | ||
end | ||
@backend.add_path(path) | ||
path | ||
end | ||
|
||
def run | ||
@backend.run | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
class FSSM::Path | ||
def initialize(path=nil, glob=nil, &block) | ||
set_path(path || '.') | ||
set_glob(glob || '**/*') | ||
init_callbacks | ||
if block && block.arity == 0 | ||
self.instance_eval(&block) | ||
elsif block && block.arity == 1 | ||
block.call(self) | ||
end | ||
end | ||
|
||
def to_s | ||
@path.to_s | ||
end | ||
|
||
def to_pathname | ||
@path | ||
end | ||
|
||
def glob(value=nil) | ||
return @glob if value.nil? | ||
set_glob(value) | ||
end | ||
|
||
def create(callback_or_path=nil, &block) | ||
callback_action(:create, (block_given? ? block : callback_or_path)) | ||
end | ||
|
||
def update(callback_or_path=nil, &block) | ||
callback_action(:update, (block_given? ? block : callback_or_path)) | ||
end | ||
|
||
def delete(callback_or_path=nil, &block) | ||
callback_action(:delete, (block_given? ? block : callback_or_path)) | ||
end | ||
|
||
private | ||
|
||
def init_callbacks | ||
do_nothing = lambda {|base, relative|} | ||
@callbacks = Hash.new(do_nothing) | ||
end | ||
|
||
def callback_action(type, arg=nil) | ||
if arg.is_a?(Proc) | ||
set_callback(type, arg) | ||
elsif arg.nil? | ||
get_callback(type) | ||
else | ||
run_callback(type, arg) | ||
end | ||
end | ||
|
||
def set_callback(type, arg) | ||
raise ArgumentError, "Proc expected" unless arg.is_a?(Proc) | ||
@callbacks[type] = arg | ||
end | ||
|
||
def get_callback(type) | ||
@callbacks[type] | ||
end | ||
|
||
def run_callback(type, arg) | ||
base, relative = split_path(arg) | ||
|
||
begin | ||
@callbacks[type].call(base, relative) | ||
rescue Exception => e | ||
raise FSSM::CallbackError, "#{type} - #{base.join(relative)}: #{e.message}", e.backtrace | ||
end | ||
end | ||
|
||
def split_path(path) | ||
path = Pathname.for(path) | ||
[@path, (path.relative? ? path : path.relative_path_from(@path))] | ||
end | ||
|
||
def set_path(path) | ||
path = Pathname.for(path) | ||
raise FSSM::FileNotFoundError, "#{path}" unless path.exist? | ||
@path = path.realpath | ||
end | ||
|
||
def set_glob(glob) | ||
@glob = glob.is_a?(Array) ? glob : [glob] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
class FSSM::State | ||
def initialize(path, preload=true) | ||
@path = path | ||
@snapshot = {} | ||
snapshot if preload | ||
end | ||
|
||
def refresh | ||
previous = @snapshot | ||
current = snapshot | ||
|
||
deleted(previous, current) | ||
created(previous, current) | ||
modified(previous, current) | ||
end | ||
|
||
private | ||
|
||
def created(previous, current) | ||
(current.keys - previous.keys).each {|created| @path.create(created)} | ||
end | ||
|
||
def deleted(previous, current) | ||
(previous.keys - current.keys).each {|deleted| @path.delete(deleted)} | ||
end | ||
|
||
def modified(previous, current) | ||
(current.keys & previous.keys).each do |file| | ||
@path.update(file) if (current[file] <=> previous[file]) != 0 | ||
end | ||
end | ||
|
||
def snapshot | ||
snap = {} | ||
@path.glob.each {|glob| add_glob(snap, glob)} | ||
@snapshot = snap | ||
end | ||
|
||
def add_glob(snap, glob) | ||
Pathname.glob(@path.to_pathname.join(glob)).each do |fn| | ||
next unless fn.file? | ||
snap["#{fn}"] = fn.mtime | ||
end | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module FSSM::Support | ||
class << self | ||
# def backend | ||
# (mac? && carbon_core?) ? 'FSEvents' : 'Polling' | ||
# end | ||
|
||
def backend | ||
'Polling' | ||
end | ||
|
||
def mac? | ||
@@mac ||= RUBY_PLATFORM =~ /darwin/i | ||
end | ||
|
||
def carbon_core? | ||
@@carbon_core ||= begin | ||
require 'osx/foundation' | ||
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework' | ||
true | ||
rescue LoadError | ||
false | ||
end | ||
end | ||
|
||
end | ||
end |