swerling-sinotify 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ module Sinotify
2
+
3
+ #
4
+ # Sinotify::PrimEvent is a ruby wrapper for an inotify event
5
+ # Use the Sinotify::PrimNotifier to register to listen for these Events.
6
+ #
7
+ # Most users of Sinotify will not want to listen for prim events, instead opting
8
+ # to use a Sinotify::Notifier to listen for Sinotify::Events. See docs for those classes.
9
+ #
10
+ # Methods :name, :mask, and :wd defined in sinotify.c
11
+ #
12
+ # For convenience, inotify masks are represented in the PrimEvent as an 'etype',
13
+ # which is just a ruby symbol corresponding to the mask. For instance, a mask
14
+ # represented as Sinotify::MODIFY has an etype of :modify. You can still get
15
+ # the mask if you want the 'raw' int mask value. In other words:
16
+ # <pre>
17
+ # $ irb
18
+ # >> require 'sinotify'
19
+ # => true
20
+ # >> Sinotify::MODIFY
21
+ # => 2
22
+ # >> Sinotify::Event.etype_from_mask(Sinotify::MODIFY)
23
+ # => :modify
24
+ # >> Sinotify::Event.mask_from_etype(:modify)
25
+ # => 2
26
+ # </pre>
27
+ #
28
+ # See docs for Sinotify::Event class for full list of supported event symbol types and
29
+ # their symbols.
30
+ #
31
+ class PrimEvent
32
+
33
+ # map the constants defined in the 'c' lib to ruby symbols
34
+ @@mask_to_etype_map = {
35
+ Sinotify::PrimEvent::CREATE => :create,
36
+ Sinotify::PrimEvent::MOVE => :move,
37
+ Sinotify::PrimEvent::ACCESS => :access,
38
+ Sinotify::PrimEvent::MODIFY => :modify,
39
+ Sinotify::PrimEvent::ATTRIB => :attrib,
40
+ Sinotify::PrimEvent::CLOSE_WRITE => :close_write,
41
+ Sinotify::PrimEvent::CLOSE_NOWRITE => :close_nowrite,
42
+ Sinotify::PrimEvent::OPEN => :open,
43
+ Sinotify::PrimEvent::MOVED_FROM => :moved_from,
44
+ Sinotify::PrimEvent::MOVED_TO => :moved_to,
45
+ Sinotify::PrimEvent::DELETE => :delete,
46
+ Sinotify::PrimEvent::DELETE_SELF => :delete_self,
47
+ Sinotify::PrimEvent::MOVE_SELF => :move_self,
48
+ Sinotify::PrimEvent::UNMOUNT => :unmount,
49
+ Sinotify::PrimEvent::Q_OVERFLOW => :q_overflow,
50
+ Sinotify::PrimEvent::IGNORED => :ignored,
51
+ Sinotify::PrimEvent::CLOSE => :close,
52
+ Sinotify::PrimEvent::MASK_ADD => :mask_add,
53
+ Sinotify::PrimEvent::ISDIR => :isdir,
54
+ Sinotify::PrimEvent::ONLYDIR => :onlydir,
55
+ Sinotify::PrimEvent::DONT_FOLLOW => :dont_follow,
56
+ Sinotify::PrimEvent::ONESHOT => :oneshot,
57
+ Sinotify::PrimEvent::ALL_EVENTS => :all_events,
58
+ }
59
+
60
+ @@etype_to_mask_map = {}
61
+ @@mask_to_etype_map.each{|k,v| @@etype_to_mask_map[v] = k}
62
+
63
+ # def self.etype_from_mask(mask)
64
+ # @@mask_to_etype_map[mask]
65
+ # end
66
+
67
+ def self.etype_from_mask(mask)
68
+ @@mask_to_etype_map[mask]
69
+ end
70
+
71
+ def self.mask_from_etype(etype)
72
+ @@etype_to_mask_map[etype]
73
+ end
74
+
75
+ def self.all_etypes
76
+ @@mask_to_etype_map.values.sort{|e1,e2| e1.to_s <=> e2.to_s}
77
+ end
78
+
79
+ def name
80
+ @name ||= self.prim_name
81
+ end
82
+
83
+ def wd
84
+ @wd ||= self.prim_wd
85
+ end
86
+
87
+ def mask
88
+ @mask ||= self.prim_mask
89
+ end
90
+
91
+ # When first creating a watch, inotify sends a bunch of events that have masks
92
+ # don't seem to match up w/ any of the masks defined in inotify.h. Pass on those.
93
+ def recognized?
94
+ return !self.etypes.empty?
95
+ end
96
+
97
+ # Return whether this event has etype specified
98
+ def has_etype?(etype)
99
+ mask_for_etype = self.class.mask_from_etype(etype)
100
+ return (self.mask & mask_for_etype).eql?(mask_for_etype)
101
+ end
102
+
103
+ def etypes
104
+ @etypes ||= self.class.all_etypes.select{|et| self.has_etype?(et) }
105
+ end
106
+
107
+ def watch_descriptor
108
+ self.wd
109
+ end
110
+
111
+ def inspect
112
+ "<#{self.class} :name => '#{self.name}', :etypes => #{self.etypes.inspect}, :mask => #{self.mask.to_s(16)}, :watch_descriptor => #{self.watch_descriptor}>"
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
@@ -0,0 +1,21 @@
1
+ module Sinotify
2
+ #
3
+ # Just a little struct to describe a single inotifier watch
4
+ # Note that the is_dir needs to be saved because we won't be
5
+ # able to deduce that later if it was a deleted object.
6
+ #
7
+ class Watch
8
+ attr_accessor :is_dir, :path, :watch_descriptor
9
+ def initialize(args={})
10
+ args.each{|k,v| self.send("#{k}=",v)}
11
+ @timestamp ||= Time.now
12
+ @is_dir = File.directory?(path)
13
+ end
14
+ def directory?
15
+ self.is_dir.eql?(true)
16
+ end
17
+ def to_s
18
+ "Sinotify::Watch[:is_dir => #{is_dir}, :path => #{path}, :watch_descriptor => #{watch_descriptor}]"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+ module Sinotify
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ # :stopdoc:
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args )
20
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ end
22
+
23
+ # Returns the lpath for the module. If any arguments are given,
24
+ # they will be joined to the end of the path using
25
+ # <tt>File.join</tt>.
26
+ #
27
+ def self.path( *args )
28
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
29
+ end
30
+
31
+ # Utility method used to require all files ending in .rb that lie in the
32
+ # directory below this file that has the same name as the filename passed
33
+ # in. Optionally, a specific _directory_ name can be passed in such that
34
+ # the _filename_ does not have to be equivalent to the directory.
35
+ #
36
+ def self.require_all_libs_relative_to( fname, dir = nil )
37
+ dir ||= ::File.basename(fname, '.*')
38
+ search_me = ::File.expand_path(
39
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
40
+
41
+ Dir.glob(search_me).sort.each {|rb| require rb}
42
+ end
43
+
44
+ # :startdoc:
45
+
46
+ end # module Sinotify
47
+
data/sinotify.gemspec ADDED
@@ -0,0 +1,79 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{sinotify}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Steven Swerling"]
9
+ s.date = %q{2009-07-19}
10
+ s.description = %q{ALPHA Alert -- just uploaded initial release.
11
+
12
+ Linux inotify is a means to receive events describing file system activity (create, modify, delete, close, etc).
13
+
14
+ Sinotify was derived from aredridel's package (http://raa.ruby-lang.org/project/ruby-inotify/), with the addition of
15
+ Paul Boon's tweak for making the event_check thread more polite (see
16
+ http://www.mindbucket.com/2009/02/24/ruby-daemons-verifying-good-behavior/)
17
+
18
+ In sinotify, the classes Sinotify::PrimNotifier and Sinotify::PrimEvent provide a low level wrapper to inotify, with
19
+ the ability to establish 'watches' and then listen for inotify events using one of inotify's synchronous event loops,
20
+ and providing access to the events' masks (see 'man inotify' for details). Sinotify::PrimEvent class adds a little semantic sugar
21
+ to the event in to the form of 'etypes', which are just ruby symbols that describe the event mask. If the event has a
22
+ raw mask of (DELETE_SELF & IS_DIR), then the etypes array would be [:delete_self, :is_dir].
23
+
24
+ In addition to the 'straight' wrapper in inotify, sinotify provides an asynchronous implementation of the 'observer
25
+ pattern' for notification. In other words, Sinotify::Notifier listens in the background for inotify events, adapting
26
+ them into instances of Sinotify::Event as they come in and immediately placing them in a concurrent queue, from which
27
+ they are 'announced' to 'subscribers' of the event. [Sinotify uses the 'cosell' implementation of the Announcements
28
+ event notification framework, hence the terminology 'subscribe' and 'announce' rather then 'listen' and 'trigger' used
29
+ in the standard event observer pattern. See the 'cosell' package on github for details.]
30
+
31
+ A variety of 'knobs' are provided for controlling the behavior of the notifier: whether a watch should apply to a
32
+ single directory or should recurse into subdirectores, how fast it should broadcast queued events, etc (see
33
+ Sinotify::Notifier, and the example in the synopsis section below). An event 'spy' can also be setup to log all
34
+ Sinotify::PrimEvents and Sinotify::Events.
35
+
36
+ Sinotify::Event simplifies inotify's muddled event model, sending events only for those files/directories that have
37
+ changed. That's not to say you can't setup a notifier that recurses into subdirectories, just that any individual
38
+ event will apply to a single file, and not to its children. Also, event types are identified using words (in the form
39
+ of ruby :symbols) instead of inotify's event masks. See Sinotify::Event for more explanation.
40
+
41
+ The README for inotify:
42
+
43
+ http://www.kernel.org/pub/linux/kernel/people/rml/inotify/README
44
+
45
+ Selected quotes from the README for inotify:
46
+
47
+ * "Rumor is that the 'd' in 'dnotify' does not stand for 'directory' but for 'suck.'"
48
+
49
+ * "The 'i' in inotify does not stand for 'suck' but for 'inode' -- the logical
50
+ choice since inotify is inode-based."
51
+
52
+ (The 's' in 'sinotify' does in fact stand for 'suck.')}
53
+ s.email = %q{sswerling@yahoo.com}
54
+ s.extensions = ["ext/extconf.rb"]
55
+ s.extra_rdoc_files = ["History.txt", "README.txt"]
56
+ s.files = [".gitignore", "History.txt", "README.rdoc", "README.txt", "Rakefile", "examples/watcher.rb", "ext/extconf.rb", "ext/src/inotify-syscalls.h", "ext/src/inotify.h", "ext/src/sinotify.c", "lib/sinotify.rb", "lib/sinotify/event.rb", "lib/sinotify/notifier.rb", "lib/sinotify/prim_event.rb", "lib/sinotify/watch.rb", "lib/sinotify_info.rb", "sinotify.gemspec", "spec/prim_notify_spec.rb", "spec/sinotify_spec.rb", "spec/spec_helper.rb"]
57
+ s.homepage = %q{http://tab-a.slot-z.net}
58
+ s.rdoc_options = ["--inline-source", "--main", "README.txt"]
59
+ s.require_paths = ["lib", "ext"]
60
+ s.rubyforge_project = %q{sinotify}
61
+ s.rubygems_version = %q{1.3.3}
62
+ s.summary = %q{ALPHA Alert -- just uploaded initial release}
63
+
64
+ if s.respond_to? :specification_version then
65
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
66
+ s.specification_version = 3
67
+
68
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
69
+ s.add_runtime_dependency(%q<cosell>, [">= 0"])
70
+ s.add_development_dependency(%q<bones>, [">= 2.4.0"])
71
+ else
72
+ s.add_dependency(%q<cosell>, [">= 0"])
73
+ s.add_dependency(%q<bones>, [">= 2.4.0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<cosell>, [">= 0"])
77
+ s.add_dependency(%q<bones>, [">= 2.4.0"])
78
+ end
79
+ end
@@ -0,0 +1,98 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'fileutils'
3
+
4
+ #
5
+ # The tests for the inotify wrapper.
6
+ # Mostly taken straight from ruby-inotify's tests, w/ some tweaks.
7
+ #
8
+ describe Sinotify::PrimNotifier do
9
+
10
+ before(:each) do
11
+ @inotify = Sinotify::PrimNotifier.new
12
+ end
13
+
14
+ it "should be able to create and remove a watch descriptor" do
15
+ wd = @inotify.add_watch("/tmp", Sinotify::PrimEvent::CREATE)
16
+ wd.class.should be_eql(Fixnum)
17
+ wd.should_not be_eql(0)
18
+ @inotify.rm_watch(wd).should be_true
19
+ end
20
+
21
+ it "should get events on watched directory and get name of altered file in watched directory" do
22
+ test_fn = "/tmp/sinotify-test"
23
+ FileUtils.rm_f test_fn
24
+ wd = @inotify.add_watch("/tmp", Sinotify::PrimEvent::DELETE | Sinotify::PrimEvent::CREATE)
25
+ begin
26
+ FileUtils.touch test_fn
27
+ @inotify.each_event do |ev|
28
+ #puts "-----------#{ev.etypes.inspect}"
29
+ ev.class.should be_eql(Sinotify::PrimEvent)
30
+ ev.name.should be_eql('sinotify-test')
31
+ ev.mask.should be_eql(Sinotify::PrimEvent::CREATE)
32
+ ev.has_etype?(:create).should be_true
33
+ ev.etypes.size.should be_eql(1)
34
+ ev.inspect.should be_eql "<Sinotify::PrimEvent :name => 'sinotify-test', :etypes => [:create], :mask => 100, :watch_descriptor => 1>"
35
+ break
36
+ end
37
+ FileUtils.rm_f test_fn
38
+ @inotify.each_event do |ev|
39
+ #puts "-----------#{ev.etypes.inspect}"
40
+ ev.has_etype?(:delete).should be_true
41
+ ev.etypes.size.should be_eql(1)
42
+ break
43
+ end
44
+ ensure
45
+ @inotify.rm_watch(wd)
46
+ FileUtils.rm_f test_fn
47
+ end
48
+ end
49
+
50
+ it "should get events on watched file" do
51
+ test_fn = "/tmp/sinotify-test"
52
+ FileUtils.touch test_fn
53
+ wd = @inotify.add_watch(test_fn, Sinotify::PrimEvent::ATTRIB | Sinotify::PrimEvent::DELETE | Sinotify::PrimEvent::MODIFY)
54
+ begin
55
+ FileUtils.touch test_fn
56
+ @inotify.each_event do |ev|
57
+ ev.name.should be_nil # name is only set when watching a directory
58
+ ev.has_etype?(:attrib).should be_true
59
+ break
60
+ end
61
+
62
+ File.open(test_fn, 'a'){|f| f << 'hi'}
63
+ @inotify.each_event do |ev|
64
+ ev.name.should be_nil # name is only set when watching a directory
65
+ ev.has_etype?(:modify).should be_true
66
+ break
67
+ end
68
+
69
+ FileUtils.rm_f test_fn
70
+ @inotify.each_event do |ev|
71
+ #puts "-----------#{ev.inspect}"
72
+ # TODO: Look into this -- when deleting a file, it gets an event of type :attrib instead of :delete.
73
+ # Is this a bug or something I am doing?
74
+ # ev.has_etype?(:delete).should be_true
75
+ break
76
+ end
77
+
78
+ # since the event is deleted, it should not be possible to remove the watch
79
+ lambda{@inotify.rm_watch(wd)}.should raise_error
80
+
81
+ ensure
82
+ @inotify.rm_watch(wd) rescue nil
83
+ FileUtils.rm_f test_fn
84
+ end
85
+ end
86
+
87
+ protected
88
+
89
+ def little_bench(msg, &block)
90
+ start = Time.now
91
+ result = block.call
92
+ puts "#{msg}: #{Time.now - start} sec"
93
+ return result
94
+ end
95
+ end
96
+
97
+ # EOF
98
+
@@ -0,0 +1,247 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+ require 'fileutils'
3
+
4
+ class MockPrimEvent
5
+ attr_accessor :etypes, :mask, :wd, :name
6
+ end
7
+
8
+ #
9
+ # WARNING: These tests are a bit brittle. They depend on events taking place in threads as a result
10
+ # of filesytem events (inotify events). Sometimes the file system events dont come in as fast as
11
+ # desirable for the test, or sometimes ruby threads themselves may not get scheduled fast enough.
12
+ # If a test is failing on your system, it may start to succeed if you increase the values
13
+ # in tiny_pause!, pause!, or big_pause! methods below.
14
+ #
15
+ describe Sinotify do
16
+
17
+ # A lot of Sinotify work occurs in background threads (eg. adding watches, adding subdirectories),
18
+ # so the tests may insert a tiny pause to allow the bg threads to do their thing before making
19
+ # any assertions.
20
+ def tiny_pause!; sleep 0.05; end
21
+ def pause!; sleep 0.5; end
22
+ def big_pause!; sleep 1.5; end
23
+
24
+ def reset_test_dir!
25
+ raise 'not gonna happen' unless @test_root_dir =~ /\/tmp\//
26
+ FileUtils.rm_rf(@test_root_dir)
27
+ FileUtils.mkdir(@test_root_dir)
28
+ ('a'..'z').each{|ch| FileUtils.mkdir(File.join(@test_root_dir, ch))}
29
+ pause!
30
+ end
31
+
32
+ before(:each) do
33
+ @test_root_dir = '/tmp/sinotifytestdir'
34
+ end
35
+
36
+ it "should properly create event mask from etypes" do
37
+ notifier = Sinotify::Notifier.new('/tmp', :recurse => false, :etypes => [:create, :modify])
38
+
39
+ #decided to make raw_mask private: notifier.raw_mask.should be_eql(Sinotify::CREATE | Sinotify::MODIFY)
40
+
41
+ # should not be able to create a notifier using a bogus event type (eg. 'blah')
42
+ lambda{Sinotify::Notifier.new('/tmp', :recurse => false, :etypes => [:blah])}.should raise_error
43
+ lambda{Sinotify::Notifier.new('/tmp', :recurse => false, :etypes => [:create, :blah])}.should raise_error
44
+ end
45
+
46
+ it "should properly create Event from PrimEvent" do
47
+ # mimic delete of a directory -- change :delete_self into :delete
48
+ prim_event = MockPrimEvent.new
49
+ prim_event.etypes = [:delete_self]
50
+ watch = Sinotify::Watch.new(:is_dir => true, :path => '/tmp')
51
+ event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
52
+ event.etypes.should be_include(:delete)
53
+ event.etypes.should_not be_include(:delete_self)
54
+
55
+ # :close should get added if event is :close_nowrite or :close_write
56
+ prim_event = MockPrimEvent.new
57
+ prim_event.etypes = [:close_nowrite]
58
+ event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
59
+ event.etypes.should be_include(:close)
60
+ prim_event = MockPrimEvent.new
61
+ prim_event.etypes = [:close_write]
62
+ event = Sinotify::Event.from_prim_event_and_watch(prim_event, watch)
63
+ event.etypes.should be_include(:close)
64
+ end
65
+
66
+ it "should add watches for all child directories if recursive" do
67
+
68
+ reset_test_dir!
69
+
70
+ # make a watch, recurse false. There should only be one watch
71
+ notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => false).watch!
72
+
73
+ tiny_pause!
74
+ notifier.all_directories_being_watched.should be_eql([@test_root_dir])
75
+
76
+ # make a watch, recurse TRUE. There should only be 27 watches (a-z, and @test_root_dir)
77
+ notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
78
+ # notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
79
+
80
+ pause!
81
+ notifier.all_directories_being_watched.size.should be_eql(27)
82
+
83
+ # check a single announcement on a file in a subdir
84
+ events = []
85
+ test_fn = File.join(@test_root_dir, 'a', 'hi')
86
+ notifier.when_announcing(Sinotify::Event) { |event| events << event }
87
+ FileUtils.touch test_fn
88
+ pause!
89
+ #puts events.map{|e|e.to_s}.join("\n")
90
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:create) }.should_not be_nil
91
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:open) }.should_not be_nil
92
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close_write) }.should_not be_nil
93
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close) }.should_not be_nil
94
+
95
+ events = []
96
+ File.open(test_fn, 'a'){|f| f << 'ho'}
97
+ pause!
98
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:open) }.should_not be_nil
99
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:modify) }.should_not be_nil
100
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close_write) }.should_not be_nil
101
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:close) }.should_not be_nil
102
+
103
+ # quickly create and delete the file
104
+ events = []
105
+ FileUtils.rm test_fn
106
+ tiny_pause!
107
+ FileUtils.touch test_fn
108
+ pause!
109
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:delete) }.should_not be_nil
110
+ events.detect{|e| e.path.eql?(test_fn) && e.etypes.include?(:create) }.should_not be_nil
111
+ end
112
+
113
+ it "should add a watch when a new subdirectory is created" do
114
+ # setup
115
+ reset_test_dir! # creates 27 directories, the root dir and 'a'...'z'
116
+ subdir_a = File.join(@test_root_dir, 'a')
117
+ events = []
118
+ notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
119
+ # notifier.spy!(:logger => spylog = Logger.new('/tmp/spy.log'))
120
+ notifier.when_announcing(Sinotify::Event) { |event| events << event }
121
+
122
+ # one watch for the root and the 26 subdirs 'a'..'z'
123
+ pause!
124
+ notifier.all_directories_being_watched.size.should be_eql(27)
125
+
126
+ # create a new subdir
127
+ FileUtils.mkdir File.join(@test_root_dir, 'a', 'abc')
128
+ big_pause! # takes a moment to sink in because the watch is added in a bg thread
129
+ notifier.all_directories_being_watched.size.should be_eql(28)
130
+ pause!
131
+ end
132
+
133
+ it "should delete watches for on subdirectires when a parent directory is deleted" do
134
+
135
+ # Setup (create the usual test dir and 26 subdirs, and an additional sub-subdir, and a file
136
+ reset_test_dir! # creates the root dir and 'a'...'z'
137
+ subdir_a = File.join(@test_root_dir, 'a')
138
+ FileUtils.mkdir File.join(@test_root_dir, 'a', 'def')
139
+ test_fn = File.join(subdir_a, 'hi')
140
+ FileUtils.touch test_fn
141
+
142
+ # Setup: create the notifier
143
+ events = []
144
+ notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
145
+ #notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
146
+ notifier.when_announcing(Sinotify::Event) { |event| events << event }
147
+
148
+ # first assert: all directories should have a watch
149
+ pause!
150
+ notifier.all_directories_being_watched.size.should be_eql(28) # all the directories should have watches
151
+
152
+
153
+ # Should get delete events for the subdir_a and its file 'hi' when removing subdir_a.
154
+ # There should be 26 watches left (after removing watches for subdir_a and its sub-subdir)
155
+ FileUtils.rm_rf subdir_a
156
+ pause!
157
+ events.detect{|e| e.path.eql?(subdir_a) && e.directory? && e.etypes.include?(:delete) }.should_not be_nil
158
+ events.detect{|e| e.path.eql?(test_fn) && !e.directory? && e.etypes.include?(:delete) }.should_not be_nil
159
+ notifier.all_directories_being_watched.size.should be_eql(26)
160
+ end
161
+
162
+ it "should exit and nil out watch_thread when closed" do
163
+ # really need this?
164
+ end
165
+
166
+ it "should close children when closed if recursive" do
167
+ # Setup (create the usual test dir and 26 subdirs, and an additional sub-subdir, and a file
168
+ reset_test_dir! # creates the root dir and 'a'...'z'
169
+ FileUtils.mkdir File.join(@test_root_dir, 'a', 'def')
170
+
171
+ # Setup: create the notifier
172
+ events = []
173
+ notifier = Sinotify::Notifier.new(@test_root_dir, :recurse => true).watch!
174
+ #notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
175
+ notifier.when_announcing(Sinotify::Event) { |event| events << event }
176
+
177
+ # first assert: all directories should have a watch
178
+ pause!
179
+ notifier.all_directories_being_watched.size.should be_eql(28) # all the directories should have watches
180
+
181
+ notifier.close!
182
+ notifier.all_directories_being_watched.size.should be_eql(0) # all watches should have been deleted
183
+ end
184
+
185
+ it "pound it" do
186
+ # Setup (create the usual test dir and 26 subdirs, and an additional sub-subdir, and a file
187
+ reset_test_dir! # creates the root dir and 'a'...'z'
188
+
189
+ a_z = ('a'..'z').collect{|x|x}
190
+
191
+ # Setup: create the notifier
192
+ notifier = Sinotify::Notifier.new(@test_root_dir,
193
+ :announcements_sleep_time => 0.01,
194
+ :announcement_throttle => 10000,
195
+ :etypes => [:create, :modify, :delete, :close],
196
+ :recurse => true).watch!
197
+ #notifier.spy!(:logger => Logger.new('/tmp/spy.log'))
198
+ creates = deletes = modifies = closes = 0
199
+ notifier.when_announcing(Sinotify::Event) do |event|
200
+ creates += 1 if event.etypes.include?(:create)
201
+ deletes += 1 if event.etypes.include?(:delete)
202
+ modifies += 1 if event.etypes.include?(:modify)
203
+ closes += 1 if event.etypes.include?(:close)
204
+ end
205
+
206
+ # Create, append to, and then delete a bunch of random files
207
+ tiny_pause!
208
+ total_iterations = 1000
209
+ total_iterations.times do
210
+ sub_dir = File.join(@test_root_dir, a_z[rand(a_z.size)])
211
+ test_fn = File.join(sub_dir, "zzz#{rand(10000)}")
212
+ FileUtils.touch test_fn
213
+ File.open(test_fn, 'a'){|f| f << rand(1000).to_s }
214
+ FileUtils.rm test_fn
215
+ end
216
+ puts "created and modified and deleted #{total_iterations} files in sub directories of #{@test_root_dir}"
217
+
218
+ start_wait = Time.now
219
+
220
+ # wait up to 15 seconds for all the create events to come through
221
+ waits = 0
222
+ puts "Waiting for events, will wait for up to 30 sec"
223
+ while(creates < total_iterations) do
224
+ sleep 1
225
+ waits += 1
226
+ break if waits > 30
227
+ #raise "Tired of waiting for create events to reach #{total_iterations}, it is only at #{creates}" if waits > 30
228
+ end
229
+ puts "It took #{Time.now - start_wait} seconds for all the create/modify/delete/close events to come through"
230
+
231
+ pause!; pause!; pause! # give it a tiny bit longer to let any remaining modify/delete/close stragglers to come through
232
+
233
+ puts "Ceates detected: #{creates}"
234
+ puts "Deletes: #{deletes}"
235
+ puts "Modifies: #{modifies}"
236
+ puts "Closes: #{closes}"
237
+
238
+ creates.should be_eql(total_iterations)
239
+ deletes.should be_eql(total_iterations)
240
+ modifies.should be_eql(total_iterations)
241
+ closes.should be_eql(2 * total_iterations) # should get a close both after the create and the modify
242
+
243
+ end
244
+
245
+ end
246
+
247
+ # EOF