Upgrade the vendored version of FSSM to 0.1.0, this fixes some bugs with jruby and adds inotify support for linux.

This commit is contained in:
Chris Eppstein 2009-12-16 08:57:16 -08:00
parent 6d6e1ca0e3
commit 8006c7854c
12 changed files with 670 additions and 317 deletions

3
lib/vendor/fssm.rb vendored
View File

@ -28,9 +28,8 @@ module FSSM
end
require 'thread'
require 'pathname'
require 'fssm/ext'
require 'fssm/pathname'
require 'fssm/support'
require 'fssm/tree'
require 'fssm/path'

View File

@ -1,4 +1,4 @@
require 'fssm/fsevents'
require File.join(File.dirname(__FILE__), 'rubycocoa/fsevents')
module FSSM::Backends
class FSEvents

25
lib/vendor/fssm/backends/inotify.rb vendored Normal file
View File

@ -0,0 +1,25 @@
module FSSM::Backends
class Inotify
def initialize
@notifier = INotify::Notifier.new
end
def add_path(path, preload=true)
handler = FSSM::State.new(path)
@notifier.watch(path.to_s, :all_events) do |event|
handler.refresh(event.name)
end
handler.refresh(path.to_pathname, true) if preload
end
def run
begin
@notifier.run
rescue Interrupt
end
end
end
end

View File

@ -1,8 +1,8 @@
module FSSM::Backends
class Polling
def initialize(options={})
@handlers = []
@latency = options[:latency] || 1
@handlers = []
@latency = options[:latency] || 1.5
end
def add_path(path, preload=true)

View File

@ -6,6 +6,7 @@ module Rucola
attr_reader :fsevents_object
attr_reader :id
attr_reader :path
def initialize(fsevents_object, id, path)
@fsevents_object, @id, @path = fsevents_object, id, path
end
@ -22,7 +23,8 @@ module Rucola
end
end
class StreamError < StandardError; end
class StreamError < StandardError;
end
attr_reader :paths
attr_reader :stream
@ -91,11 +93,11 @@ module Rucola
paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) }
@allocator = options[:allocator] || OSX::KCFAllocatorDefault
@context = options[:context] || nil
@since = options[:since] || OSX::KFSEventStreamEventIdSinceNow
@latency = options[:latency] || 0.0
@flags = options[:flags] || 0
@stream = options[:stream] || nil
@context = options[:context] || nil
@since = options[:since] || OSX::KFSEventStreamEventIdSinceNow
@latency = options[:latency] || 0.0
@flags = options[:flags] || 0
@stream = options[:stream] || nil
@user_callback = block
@callback = Proc.new do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids|

View File

@ -1,193 +0,0 @@
class FSSM::Cache
module Common
include Enumerable
def initialize
@children = Hash.new
end
def each(prefix='./', &block)
@children.each do |segment, node|
cprefix = Pathname.for(prefix.dup).join(segment)
block.call(cprefix, node)
node.each(cprefix, &block)
end
end
protected
def with_lock
@mutex.lock
yield
@mutex.unlock
end
def descendant(path)
recurse_on_key(path, false)
end
def descendant!(path)
recurse_on_key(path, true)
end
def child(segment)
has_child?(segment) ? @children["#{segment}"] : nil
end
def child!(segment)
(@children["#{segment}"] ||= Node.new)
end
def has_child?(segment)
@children.include?("#{segment}")
end
def remove_child(segment)
@children.delete("#{segment}")
end
def remove_children
@children.clear
end
def recurse_on_key(key, create)
key = sanitize_key(key)
node = self
until key.empty?
segment = key.shift
node = create ? node.child!(segment) : node.child(segment)
return nil unless node
end
node
end
def key_for_path(path)
Pathname.for(path).names
end
def relative_path(path)
sanitize_path(path, false)
end
def absolute_path(path)
sanitize_path(path, true)
end
def sanitize_path(path, absolute)
if path.is_a?(Array)
first = absolute ? '/' : path.shift
path = path.inject(Pathname.new("#{first}")) do |pathname, segment|
pathname.join("#{segment}")
end
path
else
path = Pathname.for(path)
absolute ? path.expand_path : path
end
end
end
class Node
include Common
attr_accessor :mtime
attr_accessor :ftype
def <=>(other)
self.mtime <=> other.mtime
end
def from_path(path)
path = absolute_path(path)
@mtime = path.mtime
@ftype = path.ftype
end
protected
def sanitize_key(key)
key_for_path(relative_path(key))
end
end
include Common
def initialize
@mutex = Mutex.new
super
end
def clear
@mutex.lock
@children.clear
@mutex.unlock
end
def set(path)
unset(path)
node = descendant!(path)
node.from_path(path)
node.mtime
end
def unset(path='/')
key = sanitize_key(path)
if key.empty?
self.clear
return nil
end
segment = key.pop
node = descendant(key)
return unless node
@mutex.lock
node.remove_child(segment)
@mutex.unlock
nil
end
def files
ftype('file')
end
def directories
ftype('directory')
end
protected
def each(&block)
prefix='/'
super(prefix, &block)
end
def ftype(ft)
inject({}) do |hash, entry|
path, node = entry
hash["#{path}"] = node.mtime if node.ftype == ft
hash
end
end
def descendant(path)
node = recurse_on_key(path, false)
node
end
def descendant!(path)
@mutex.lock
node = recurse_on_key(path, true)
@mutex.unlock
node
end
def sanitize_key(key)
key_for_path(absolute_path(key))
end
end

View File

@ -1,37 +0,0 @@
class Pathname
class << self
def for(path)
path.is_a?(Pathname) ? path : new(path)
end
end
# before overwriting chop_basename:
# %total - 29.50%
# %self - 20.50%
# after overwriting chop_basename:
# %total - 24.36%
# %self - 15.47%
CHOP_PAT = /\A#{SEPARATOR_PAT}?\z/
def chop_basename(path)
base = File.basename(path)
# the original version of this method recalculates this regexp
# each run, despite the pattern never changing.
if CHOP_PAT =~ base
return nil
else
return path[0, path.rindex(base)], base
end
end
def segments
prefix, names = split_names(@path)
names.unshift(prefix) unless prefix.empty?
names.shift if names[0] == '.'
names
end
def names
prefix, names = split_names(@path)
names
end
end

View File

@ -75,14 +75,14 @@ class FSSM::Path
end
def split_path(path)
path = Pathname.for(path)
path = FSSM::Pathname.for(path)
[@path, (path.relative? ? path : path.relative_path_from(@path))]
end
def set_path(path)
path = Pathname.for(path)
path = FSSM::Pathname.for(path)
raise FSSM::FileNotFoundError, "#{path}" unless path.exist?
@path = path.realpath
@path = path.expand_path
end
def set_glob(glob)

528
lib/vendor/fssm/pathname.rb vendored Normal file
View File

@ -0,0 +1,528 @@
# The bundled ruby pathname library is a slow and hideous beast.
# There. I said it. This version is based on pathname3.
module FSSM
class Pathname < String
SEPARATOR = Regexp.quote(File::SEPARATOR)
if File::ALT_SEPARATOR
ALT_SEPARATOR = Regexp.quote(File::ALT_SEPARATOR)
SEPARATOR_PAT = Regexp.compile("[#{SEPARATOR}#{ALT_SEPARATOR}]")
else
SEPARATOR_PAT = Regexp.compile(SEPARATOR)
end
if RUBY_PLATFORM =~ /(:?mswin|mingw|bccwin)/
PREFIX_PAT = Regexp.compile("^([A-Za-z]:#{SEPARATOR_PAT})")
else
PREFIX_PAT = Regexp.compile("^(#{SEPARATOR_PAT})")
end
class << self
def for(path)
path = path.is_a?(::FSSM::Pathname) ? path : new(path)
path.dememo
path
end
end
def initialize(path)
if path =~ %r{\0}
raise ArgumentError, "path cannot contain ASCII NULLs"
end
dememo
super(path)
end
def to_path
self
end
def to_s
"#{self}"
end
alias to_str to_s
def to_a
return @segments if @segments
set_prefix_and_names
@segments = @names.dup
@segments.delete('.')
@segments.unshift(@prefix) unless @prefix.empty?
@segments
end
alias segments to_a
def each_filename(&block)
to_a.each(&block)
end
def ascend
parts = to_a
parts.length.downto(1) do |i|
yield self.class.join(parts[0, i])
end
end
def descend
parts = to_a
1.upto(parts.length) do |i|
yield self.class.join(parts[0, i])
end
end
def root?
set_prefix_and_names
@names.empty? && !@prefix.empty?
end
def parent
self + '..'
end
def relative?
set_prefix_and_names
@prefix.empty?
end
def absolute?
!relative?
end
def +(path)
dup << path
end
def <<(path)
replace(join(path).cleanpath!)
end
def cleanpath!
parts = to_a
final = []
parts.each do |part|
case part
when '.' then
next
when '..' then
case final.last
when '..' then
final.push('..')
when nil then
final.push('..')
else
final.pop
end
else
final.push(part)
end
end
replace(final.empty? ? Dir.pwd : File.join(*final))
end
def cleanpath
dup.cleanpath!
end
def realpath
raise unless self.exist?
if File.symlink?(self)
file = self.dup
while true
file = File.join(File.dirname(file), File.readlink(file))
break unless File.symlink?(file)
end
self.class.new(file).clean
else
self.class.new(Dir.pwd) + self
end
end
def relative_path_from(base)
base = self.class.for(base)
if self.absolute? != base.absolute?
raise ArgumentError, 'no relative path between a relative and absolute'
end
if self.prefix != base.prefix
raise ArgumentError, "different prefix: #{@prefix.inspect} and #{base.prefix.inspect}"
end
base = base.cleanpath!.segments
dest = dup.cleanpath!.segments
while !dest.empty? && !base.empty? && dest[0] == base[0]
base.shift
dest.shift
end
base.shift if base[0] == '.'
dest.shift if dest[0] == '.'
if base.include?('..')
raise ArgumentError, "base directory may not contain '..'"
end
path = base.fill('..') + dest
path = self.class.join(*path)
path = self.class.new('.') if path.empty?
path
end
def replace(path)
if path =~ %r{\0}
raise ArgumentError, "path cannot contain ASCII NULLs"
end
dememo
super(path)
end
def unlink
Dir.unlink(self)
true
rescue Errno::ENOTDIR
File.unlink(self)
true
end
def prefix
set_prefix_and_names
@prefix
end
def names
set_prefix_and_names
@names
end
def dememo
@set = nil
@segments = nil
@prefix = nil
@names = nil
end
private
def set_prefix_and_names
return if @set
@names = []
if (match = PREFIX_PAT.match(self))
@prefix = match[0].to_s
@names += match.post_match.split(SEPARATOR_PAT)
else
@prefix = ''
@names += self.split(SEPARATOR_PAT)
end
@names.compact!
@names.delete('')
@set = true
end
end
class Pathname
class << self
def glob(pattern, flags=0)
dirs = Dir.glob(pattern, flags)
dirs.map! {|path| new(path)}
if block_given?
dirs.each {|dir| yield dir}
nil
else
dirs
end
end
def [](pattern)
Dir[pattern].map! {|path| new(path)}
end
def pwd
new(Dir.pwd)
end
end
def entries
Dir.entries(self).map! {|e| FSSM::Pathname.new(e) }
end
def mkdir(mode = 0777)
Dir.mkdir(self, mode)
end
def opendir(&blk)
Dir.open(self, &blk)
end
def rmdir
Dir.rmdir(self)
end
def chdir
blk = lambda { yield self } if block_given?
Dir.chdir(self, &blk)
end
end
class Pathname
def blockdev?
FileTest.blockdev?(self)
end
def chardev?
FileTest.chardev?(self)
end
def directory?
FileTest.directory?(self)
end
def executable?
FileTest.executable?(self)
end
def executable_real?
FileTest.executable_real?(self)
end
def exists?
FileTest.exists?(self)
end
def file?
FileTest.file?(self)
end
def grpowned?
FileTest.grpowned?(self)
end
def owned?
FileTest.owned?(self)
end
def pipe?
FileTest.pipe?(self)
end
def readable?
FileTest.readable?(self)
end
def readable_real?
FileTest.readable_real?(self)
end
def setgid?
FileTest.setgit?(self)
end
def setuid?
FileTest.setuid?(self)
end
def socket?
FileTest.socket?(self)
end
def sticky?
FileTest.sticky?(self)
end
def symlink?
FileTest.symlink?(self)
end
def world_readable?
FileTest.world_readable?(self)
end
def world_writable?
FileTest.world_writable?(self)
end
def writable?
FileTest.writable?(self)
end
def writable_real?
FileTest.writable_real?(self)
end
def zero?
FileTest.zero?(self)
end
alias exist? exists?
end
class Pathname
def atime
File.atime(self)
end
def ctime
File.ctime(self)
end
def ftype
File.ftype(self)
end
def lstat
File.lstat(self)
end
def mtime
File.mtime(self)
end
def stat
File.stat(self)
end
def utime(atime, mtime)
File.utime(self, atime, mtime)
end
end
class Pathname
class << self
def join(*parts)
new(File.join(*parts.reject {|p| p.empty? }))
end
end
def basename
self.class.new(File.basename(self))
end
def chmod(mode)
File.chmod(mode, self)
end
def chown(owner, group)
File.chown(owner, group, self)
end
def dirname
self.class.new(File.dirname(self))
end
def expand_path(from = nil)
self.class.new(File.expand_path(self, from))
end
def extname
File.extname(self)
end
def fnmatch?(pat, flags = 0)
File.fnmatch(pat, self, flags)
end
def join(*parts)
self.class.join(self, *parts)
end
def lchmod(mode)
File.lchmod(mode, self)
end
def lchown(owner, group)
File.lchown(owner, group, self)
end
def link(to)
File.link(self, to)
end
def open(mode = 'r', perm = nil, &blk)
File.open(self, mode, perm, &blk)
end
def readlink
self.class.new(File.readlink(self))
end
def rename(to)
File.rename(self, to)
replace(to)
end
def size
File.size(self)
end
def size?
File.size?(self)
end
def symlink(to)
File.symlink(self, to)
end
def truncate
File.truncate(self)
end
end
class Pathname
def mkpath
self.class.new(FileUtils.mkpath(self))
end
def rmtree
self.class.new(FileUtils.rmtree(self).first)
end
def touch
self.class.new(FileUtils.touch(self).first)
end
end
class Pathname
def each_line(sep = $/, &blk)
IO.foreach(self, sep, &blk)
end
def read(len = nil, off = 0)
IO.read(self, len, off)
end
def readlines(sep = $/)
IO.readlines(self, sep)
end
def sysopen(mode = 'r', perm = nil)
IO.sysopen(self, mode, perm)
end
end
class Pathname
def find
Find.find(self) {|path| yield FSSM::Pathname.new(path) }
end
end
end

View File

@ -32,7 +32,7 @@ class FSSM::State
end
def recache(base)
base = Pathname.for(base)
base = FSSM::Pathname.for(base)
previous = @cache.files
snapshot(base)
current = @cache.files
@ -40,13 +40,13 @@ class FSSM::State
end
def snapshot(base)
base = Pathname.for(base)
base = FSSM::Pathname.for(base)
@cache.unset(base)
@path.glob.each {|glob| add_glob(base, glob)}
end
def add_glob(base, glob)
Pathname.glob(base.join(glob).to_s).each do |fn|
FSSM::Pathname.glob(base.join(glob).to_s).each do |fn|
@cache.set(fn)
end
end

View File

@ -1,19 +1,48 @@
require 'rbconfig'
module FSSM::Support
class << self
def backend
(mac? && carbon_core?) ? 'FSEvents' : 'Polling'
@@backend ||= case
when mac? && !jruby? && carbon_core?
'FSEvents'
when linux? && rb_inotify?
'Inotify'
else
'Polling'
end
end
def jruby?
defined?(JRUBY_VERSION)
end
def mac?
@@mac ||= RUBY_PLATFORM =~ /darwin/i
Config::CONFIG['target_os'] =~ /darwin/i
end
def linux?
Config::CONFIG['target_os'] =~ /linux/i
end
def carbon_core?
@@carbon_core ||= begin
begin
require 'osx/foundation'
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
true
rescue LoadError
STDERR.puts("Warning: Unable to load CarbonCore. FSEvents will be unavailable.")
false
end
end
def rb_inotify?
begin
require 'rubygems'
require 'rb-inotify'
true
rescue LoadError, Gem::LoadError
STDERR.puts("Warning: Unable to load rb-inotify. Inotify will be unavailable.")
false
end
end

View File

@ -34,9 +34,9 @@ module FSSM::Tree
def each(prefix=nil, &block)
@children.each do |segment, node|
cprefix = prefix ?
Pathname.for(prefix).join(segment) :
Pathname.for(segment)
block.call(cprefix, node)
FSSM::Pathname.for(prefix).join(segment) :
FSSM::Pathname.for(segment)
block.call([cprefix, node])
node.each(cprefix, &block)
end
end
@ -72,7 +72,7 @@ module FSSM::Tree
def key_segments(key)
return key if key.is_a?(Array)
Pathname.for(key).segments
FSSM::Pathname.for(key).segments
end
def descendant(path)
@ -127,7 +127,7 @@ module FSSM::Tree
end
def from_path(path)
path = Pathname.for(path)
path = FSSM::Pathname.for(path)
@ftype = path.ftype
# this handles bad symlinks without failing. why handle bad symlinks at
# all? well, we could still be interested in their creation and deletion.
@ -145,7 +145,7 @@ module FSSM::Tree
def set(path)
# all paths set from this level need to be absolute
# realpath will fail on broken links
path = Pathname.for(path).expand_path
path = FSSM::Pathname.for(path).expand_path
super(path)
end
@ -160,13 +160,13 @@ module FSSM::Tree
def links
ftype('link')
end
alias symlinks links
private
def ftype(ft)
inject({}) do |hash, entry|
path, node = entry
inject({}) do |hash, (path, node)|
hash["#{path}"] = node.mtime if node.ftype == ft
hash
end