fssm 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ nbproject
7
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Travis Tilley
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ Monitor API
2
+ ===========
3
+
4
+ There are three ways you can run the monitor.
5
+
6
+ 1. call monitor with a path parameter, and define callbacks in a block
7
+ 2. call monitor with a block to configure multiple paths and callbacks
8
+ 3. create a monitor object and run each step manually
9
+
10
+ Monitor with path
11
+ -----------------
12
+
13
+ This form watches one path, and enters the run loop automatically. The first parameter is the path to watch, and the second parameter is an optional glob pattern or array of glob patterns that a file must match in order to trigger a callback. The default glob, if ommitted, is `'**/*'`.
14
+
15
+ FSSM.monitor('/some/directory/', '**/*') do
16
+ update {|base, relative|}
17
+ delete {|base, relative|}
18
+ create {|base, relative|}
19
+ end
20
+
21
+ Monitor with block
22
+ ------------------
23
+
24
+ This form watches one or more paths, and enters the run loop automatically. The glob configuration call can be ommitted, and defaults to `'**/*'`.
25
+
26
+ FSSM.monitor do
27
+ path '/some/directory/' do
28
+ glob '**/*.yml'
29
+
30
+ update {|base, relative|}
31
+ delete {|base, relative|}
32
+ create {|base, relative|}
33
+ end
34
+
35
+ path '/some/other/directory/' do
36
+ update {|base, relative|}
37
+ delete {|base, relative|}
38
+ create {|base, relative|}
39
+ end
40
+ end
41
+
42
+ Monitor object
43
+ --------------
44
+
45
+ This form doesn't enter the run loop automatically.
46
+
47
+ monitor = FSSM::Monitor.new
48
+
49
+ monitor.path '/some/directory/' do
50
+ update {|base, relative|}
51
+ delete {|base, relative|}
52
+ create {|base, relative|}
53
+ end
54
+
55
+ monitor.run
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "fssm"
8
+ gem.summary = %Q{file system state monitor}
9
+ gem.description = %Q{file system state monitor}
10
+ gem.email = "ttilley@gmail.com"
11
+ gem.homepage = "http://github.com/ttilley/fssm"
12
+ gem.authors = ["Travis Tilley"]
13
+ gem.add_development_dependency "rspec"
14
+ gem.add_development_dependency "yard"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ begin
36
+ require 'reek/rake_task'
37
+ Reek::RakeTask.new do |t|
38
+ t.fail_on_error = true
39
+ t.verbose = false
40
+ t.source_files = 'lib/**/*.rb'
41
+ end
42
+ rescue LoadError
43
+ task :reek do
44
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
45
+ end
46
+ end
47
+
48
+ begin
49
+ require 'roodi'
50
+ require 'roodi_task'
51
+ RoodiTask.new do |t|
52
+ t.verbose = false
53
+ end
54
+ rescue LoadError
55
+ task :roodi do
56
+ abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
57
+ end
58
+ end
59
+
60
+ task :default => :spec
61
+
62
+ begin
63
+ require 'yard'
64
+ YARD::Rake::YardocTask.new
65
+ rescue LoadError
66
+ task :yardoc do
67
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
68
+ end
69
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 0
3
+ :patch: 8
4
+ :major: 0
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'fssm'
4
+
5
+ FSSM.monitor('.', '**/*') do
6
+ update {|b,r| puts "Update in #{b} to #{r}"}
7
+ delete {|b,r| puts "Delete in #{b} to #{r}"}
8
+ create {|b,r| puts "Create in #{b} to #{r}"}
9
+ end
@@ -0,0 +1,77 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{fssm}
8
+ s.version = "0.0.8"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Travis Tilley"]
12
+ s.date = %q{2009-09-08}
13
+ s.description = %q{file system state monitor}
14
+ s.email = %q{ttilley@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.markdown",
24
+ "Rakefile",
25
+ "VERSION.yml",
26
+ "example.rb",
27
+ "fssm.gemspec",
28
+ "lib/fssm.rb",
29
+ "lib/fssm/backends/fsevents.rb",
30
+ "lib/fssm/backends/polling.rb",
31
+ "lib/fssm/fsevents.rb",
32
+ "lib/fssm/monitor.rb",
33
+ "lib/fssm/path.rb",
34
+ "lib/fssm/pathname.rb",
35
+ "lib/fssm/state.rb",
36
+ "lib/fssm/support.rb",
37
+ "lib/fssm/tree.rb",
38
+ "profile/prof-cache.rb",
39
+ "profile/prof-fssm-pathname.html",
40
+ "profile/prof-pathname.rb",
41
+ "profile/prof-plain-pathname.html",
42
+ "profile/prof.html",
43
+ "spec/path_spec.rb",
44
+ "spec/root/duck/quack.txt",
45
+ "spec/root/file.css",
46
+ "spec/root/file.rb",
47
+ "spec/root/file.yml",
48
+ "spec/root/moo/cow.txt",
49
+ "spec/spec_helper.rb"
50
+ ]
51
+ s.homepage = %q{http://github.com/ttilley/fssm}
52
+ s.rdoc_options = ["--charset=UTF-8"]
53
+ s.require_paths = ["lib"]
54
+ s.rubygems_version = %q{1.3.5}
55
+ s.summary = %q{file system state monitor}
56
+ s.test_files = [
57
+ "spec/path_spec.rb",
58
+ "spec/root/file.rb",
59
+ "spec/spec_helper.rb"
60
+ ]
61
+
62
+ if s.respond_to? :specification_version then
63
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
67
+ s.add_development_dependency(%q<rspec>, [">= 0"])
68
+ s.add_development_dependency(%q<yard>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<rspec>, [">= 0"])
71
+ s.add_dependency(%q<yard>, [">= 0"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<rspec>, [">= 0"])
75
+ s.add_dependency(%q<yard>, [">= 0"])
76
+ end
77
+ end
@@ -0,0 +1,40 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
3
+
4
+ module FSSM
5
+ FileNotFoundError = Class.new(StandardError)
6
+ CallbackError = Class.new(StandardError)
7
+
8
+ class << self
9
+ def dbg(msg=nil)
10
+ STDERR.puts(msg)
11
+ end
12
+
13
+ def monitor(*args, &block)
14
+ monitor = FSSM::Monitor.new
15
+ context = args.empty? ? monitor : monitor.path(*args)
16
+
17
+ if block_given?
18
+ if block.arity == 1
19
+ block.call(context)
20
+ else
21
+ context.instance_eval(&block)
22
+ end
23
+ end
24
+
25
+ monitor.run
26
+ end
27
+ end
28
+ end
29
+
30
+ require 'thread'
31
+
32
+ require 'fssm/pathname'
33
+ require 'fssm/support'
34
+ require 'fssm/tree'
35
+ require 'fssm/path'
36
+ require 'fssm/state'
37
+ require 'fssm/monitor'
38
+
39
+ require "fssm/backends/#{FSSM::Support.backend.downcase}"
40
+ FSSM::Backends::Default = FSSM::Backends.const_get(FSSM::Support.backend)
@@ -0,0 +1,37 @@
1
+ require 'fssm/fsevents'
2
+
3
+ module FSSM::Backends
4
+ class FSEvents
5
+ def initialize
6
+ @handlers = {}
7
+ @fsevents = []
8
+ end
9
+
10
+ def add_path(path, preload=true)
11
+ handler = FSSM::State.new(path)
12
+ @handlers["#{path}"] = handler
13
+
14
+ fsevent = Rucola::FSEvents.new("#{path}", {:latency => 0.5}) do |events|
15
+ events.each do |event|
16
+ handler.refresh(event.path)
17
+ end
18
+ end
19
+
20
+ fsevent.create_stream
21
+ handler.refresh(path.to_pathname, true) if preload
22
+ fsevent.start
23
+ @fsevents << fsevent
24
+ end
25
+
26
+ def run
27
+ begin
28
+ OSX.CFRunLoopRun
29
+ rescue Interrupt
30
+ @fsevents.each do |fsev|
31
+ fsev.stop
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ module FSSM::Backends
2
+ class Polling
3
+ def initialize(options={})
4
+ @handlers = []
5
+ @latency = options[:latency] || 1.5
6
+ end
7
+
8
+ def add_path(path, preload=true)
9
+ handler = FSSM::State.new(path)
10
+ handler.refresh(path.to_pathname, true) if preload
11
+ @handlers << handler
12
+ end
13
+
14
+ def run
15
+ begin
16
+ loop do
17
+ start = Time.now.to_f
18
+ @handlers.each {|handler| handler.refresh}
19
+ nap_time = @latency - (Time.now.to_f - start)
20
+ sleep nap_time if nap_time > 0
21
+ end
22
+ rescue Interrupt
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,129 @@
1
+ OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
2
+
3
+ module Rucola
4
+ class FSEvents
5
+ class FSEvent
6
+ attr_reader :fsevents_object
7
+ attr_reader :id
8
+ attr_reader :path
9
+ def initialize(fsevents_object, id, path)
10
+ @fsevents_object, @id, @path = fsevents_object, id, path
11
+ end
12
+
13
+ # Returns an array of the files/dirs in the path that the event occurred in.
14
+ # The files are sorted by the modification time, the first entry is the last modified file.
15
+ def files
16
+ Dir.glob("#{File.expand_path(path)}/*").sort_by {|f| File.mtime(f) }.reverse
17
+ end
18
+
19
+ # Returns the last modified file in the path that the event occurred in.
20
+ def last_modified_file
21
+ files.first
22
+ end
23
+ end
24
+
25
+ class StreamError < StandardError; end
26
+
27
+ attr_reader :paths
28
+ attr_reader :stream
29
+
30
+ attr_accessor :allocator
31
+ attr_accessor :context
32
+ attr_accessor :since
33
+ attr_accessor :latency
34
+ attr_accessor :flags
35
+
36
+ # Initializes a new FSEvents `watchdog` object and starts watching the directories you specify for events. The
37
+ # block is used as a handler for events, which are passed as the block's argument. This method is the easiest
38
+ # way to start watching some directories if you don't care about the details of setting up the event stream.
39
+ #
40
+ # Rucola::FSEvents.start_watching('/tmp') do |events|
41
+ # events.each { |event| log.debug("#{event.files.inspect} were changed.") }
42
+ # end
43
+ #
44
+ # Rucola::FSEvents.start_watching('/var/log/system.log', '/var/log/secure.log', :since => last_id, :latency => 5) do
45
+ # Growl.notify("Something was added to your log files!")
46
+ # end
47
+ #
48
+ # Note that the method also returns the FSEvents object. This enables you to control the event stream if you want to.
49
+ #
50
+ # fsevents = Rucola::FSEvents.start_watching('/Volumes') do |events|
51
+ # events.each { |event| Growl.notify("Volume changes: #{event.files.to_sentence}") }
52
+ # end
53
+ # fsevents.stop
54
+ def self.start_watching(*params, &block)
55
+ fsevents = new(*params, &block)
56
+ fsevents.create_stream
57
+ fsevents.start
58
+ fsevents
59
+ end
60
+
61
+ # Creates a new FSEvents `watchdog` object. You can specify a list of paths to watch and options to control the
62
+ # behaviour of the watchdog. The block you pass serves as a callback when an event is generated on one of the
63
+ # specified paths.
64
+ #
65
+ # fsevents = FSEvents.new('/etc/passwd') { Mailer.send_mail("Someone touched the password file!") }
66
+ # fsevents.create_stream
67
+ # fsevents.start
68
+ #
69
+ # fsevents = FSEvents.new('/home/upload', :since => UploadWatcher.last_event_id) do |events|
70
+ # events.each do |event|
71
+ # UploadWatcher.last_event_id = event.id
72
+ # event.files.each do |file|
73
+ # UploadWatcher.logfile.append("#{file} was changed")
74
+ # end
75
+ # end
76
+ # end
77
+ #
78
+ # *:since: The service will report events that have happened after the supplied event ID. Never use 0 because that
79
+ # will cause every fsevent since the "beginning of time" to be reported. Use OSX::KFSEventStreamEventIdSinceNow
80
+ # if you want to receive events that have happened after this call. (Default: OSX::KFSEventStreamEventIdSinceNow).
81
+ # You can find the ID's passed with :since in the events passed to your block.
82
+ # *:latency: Number of seconds to wait until an FSEvent is reported, this allows the service to bundle events. (Default: 0.0)
83
+ #
84
+ # Please refer to the Cocoa documentation for the rest of the options.
85
+ def initialize(*params, &block)
86
+ raise ArgumentError, 'No callback block was specified.' unless block_given?
87
+
88
+ options = params.last.kind_of?(Hash) ? params.pop : {}
89
+ @paths = params.flatten
90
+
91
+ paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) }
92
+
93
+ @allocator = options[:allocator] || OSX::KCFAllocatorDefault
94
+ @context = options[:context] || nil
95
+ @since = options[:since] || OSX::KFSEventStreamEventIdSinceNow
96
+ @latency = options[:latency] || 0.0
97
+ @flags = options[:flags] || 0
98
+ @stream = options[:stream] || nil
99
+
100
+ @user_callback = block
101
+ @callback = Proc.new do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids|
102
+ paths_pointer.regard_as('*')
103
+ events = []
104
+ number_of_events.times {|i| events << Rucola::FSEvents::FSEvent.new(self, event_ids[i], paths_pointer[i]) }
105
+ @user_callback.call(events)
106
+ end
107
+ end
108
+
109
+ # Create the stream.
110
+ # Raises a Rucola::FSEvents::StreamError if the stream could not be created.
111
+ def create_stream
112
+ @stream = OSX.FSEventStreamCreate(@allocator, @callback, @context, @paths, @since, @latency, @flags)
113
+ raise(StreamError, 'Unable to create FSEvents stream.') unless @stream
114
+ OSX.FSEventStreamScheduleWithRunLoop(@stream, OSX.CFRunLoopGetCurrent, OSX::KCFRunLoopDefaultMode)
115
+ end
116
+
117
+ # Start the stream.
118
+ # Raises a Rucola::FSEvents::StreamError if the stream could not be started.
119
+ def start
120
+ raise(StreamError, 'Unable to start FSEvents stream.') unless OSX.FSEventStreamStart(@stream)
121
+ end
122
+
123
+ # Stop the stream.
124
+ # You can resume it by calling `start` again.
125
+ def stop
126
+ OSX.FSEventStreamStop(@stream)
127
+ end
128
+ end
129
+ end