fsevents 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
+ == 0.1.0 2008-06-30
2
+
3
+ * 1 minor enhancement:
4
+ * Added optional cache mode for getting an Event's modified files. The old behavior is the mtime mode, which is default.
5
+
6
+ * 1 tiny enhancement:
7
+ * Now stripping trailing slashes from paths (for both Stream and Event objects)
8
+
1
9
  == 0.0.2 2008-06-17
2
10
 
3
11
  * 1 tiny enhancement:
@@ -4,7 +4,7 @@ module FSEvents
4
4
 
5
5
  def initialize(id, path, stream)
6
6
  @id = id
7
- @path = path
7
+ @path = path.sub(%r%/$%, '')
8
8
  @stream = stream
9
9
  end
10
10
 
@@ -13,7 +13,21 @@ module FSEvents
13
13
  end
14
14
 
15
15
  def modified_files
16
- files.select { |f| File.mtime(f) >= stream.last_event }
16
+ case stream.mode
17
+ when :mtime
18
+ files.select { |f| File.mtime(f) >= stream.last_event }
19
+ when :cache
20
+ cache = stream.dirs[path] || {}
21
+
22
+ files.select do |f|
23
+ cached = cache[f]
24
+
25
+ cached.nil? or
26
+
27
+ File.mtime(f) != cached.mtime or
28
+ File.size(f) != cached.size
29
+ end
30
+ end
17
31
  end
18
32
  end
19
33
  end
@@ -2,11 +2,13 @@ require 'fsevents/event'
2
2
 
3
3
  module FSEvents
4
4
  class Stream
5
- attr_reader :stream, :last_event
5
+ attr_reader :stream, :mode, :last_event, :dirs
6
6
  attr_reader :allocator, :context, :paths, :since, :latency, :flags, :callback
7
7
 
8
8
  class StreamError < StandardError; end
9
9
 
10
+ MODES = [:mtime, :cache]
11
+
10
12
  def initialize(*paths, &callback)
11
13
  raise ArgumentError, 'A callback block is required' if callback.nil?
12
14
  @callback = callback
@@ -14,11 +16,16 @@ module FSEvents
14
16
  options = {}
15
17
  options = paths.pop if paths.last.is_a?(Hash)
16
18
 
19
+ @mode = options[:mode] || :mtime
20
+ raise ArgumentError, "Mode '#{mode}' unknown" unless MODES.include?(@mode)
21
+ @dirs = {}
22
+
17
23
  paths = Dir.pwd if paths.empty?
24
+ paths = [paths].flatten.collect { |path| path.sub(%r%/$%, '') }
18
25
 
19
26
  @allocator = options[:allocator] || OSX::KCFAllocatorDefault
20
27
  @context = options[:context] || nil
21
- @paths = [paths].flatten
28
+ @paths = paths
22
29
  @since = options[:since] || OSX::KFSEventStreamEventIdSinceNow
23
30
  @latency = options[:latency] || 1.0
24
31
  @flags = options[:flags] || 0
@@ -53,7 +60,20 @@ module FSEvents
53
60
  end
54
61
 
55
62
  def update_last_event
56
- @last_event = Time.now
63
+ case mode
64
+ when :mtime
65
+ @last_event = Time.now
66
+ when :cache
67
+ cache_paths = paths.dup
68
+ cache_paths.each do |path|
69
+ dirs[path] = {}
70
+ Dir["#{path}/*"].each do |file|
71
+ stat = File::Stat.new(file)
72
+ dirs[path][file] = stat
73
+ cache_paths.push(file) if stat.directory?
74
+ end
75
+ end
76
+ end
57
77
  end
58
78
 
59
79
  def startup
@@ -1,8 +1,8 @@
1
1
  module Fsevents #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 0
5
- TINY = 2
4
+ MINOR = 1
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -34,6 +34,10 @@ describe FSEvents::Event do
34
34
  FSEvents::Event.new(@id, @path, @stream).path.should == @path
35
35
  end
36
36
 
37
+ it 'should strip a trailing / from the path' do
38
+ FSEvents::Event.new(@id, "#{@path}/", @stream).path.should == @path
39
+ end
40
+
37
41
  it 'should store the stream' do
38
42
  FSEvents::Event.new(@id, @path, @stream).stream.should == @stream
39
43
  end
@@ -56,49 +60,147 @@ describe FSEvents::Event do
56
60
  describe 'listing modified files' do
57
61
  before :each do
58
62
  @now = Time.now
59
- @stream.stubs(:last_event).returns(@now)
60
- @files = Array.new(5) do |i|
61
- file = stub("file #{i+1}")
62
- File.stubs(:mtime).with(file).returns(@now + i - 2)
63
- file
64
- end
63
+ @files = Array.new(5) { |i| stub("file #{i+1}") }
65
64
  @event.stubs(:files).returns(@files)
66
65
  end
67
-
68
- it 'should get the file list' do
69
- @event.expects(:files).returns(@files)
70
- @event.modified_files
71
- end
72
-
73
- it 'should get the last event time from the stream' do
74
- @stream.expects(:last_event).returns(@now)
66
+
67
+ it 'should check the stream mode' do
68
+ @stream.expects(:mode)
75
69
  @event.modified_files
76
70
  end
77
-
78
- it 'should return files modified after the last event time' do
79
- expected_files = @files.values_at(3, 4)
80
- modified_files = @event.modified_files
81
-
82
- expected_files.each do |file|
83
- modified_files.should include(file)
71
+
72
+ describe 'when the stream mode is mtime' do
73
+ before :each do
74
+ @stream.stubs(:mode).returns(:mtime)
84
75
  end
85
- end
86
-
87
- it 'should return files modified at the last event time' do
88
- expected_files = @files.values_at(2)
89
- modified_files = @event.modified_files
90
-
91
- expected_files.each do |file|
92
- modified_files.should include(file)
76
+
77
+ before :each do
78
+ @stream.stubs(:last_event).returns(@now)
79
+ @files.each_with_index do |file, i|
80
+ File.stubs(:mtime).with(file).returns(@now + i - 2)
81
+ end
82
+ end
83
+
84
+ it 'should get the file list' do
85
+ @event.expects(:files).returns(@files)
86
+ @event.modified_files
87
+ end
88
+
89
+ it 'should get the last event time from the stream' do
90
+ @stream.expects(:last_event).returns(@now)
91
+ @event.modified_files
92
+ end
93
+
94
+ it 'should return files modified after the last event time' do
95
+ expected_files = @files.values_at(3, 4)
96
+ modified_files = @event.modified_files
97
+
98
+ expected_files.each do |file|
99
+ modified_files.should include(file)
100
+ end
101
+ end
102
+
103
+ it 'should return files modified at the last event time' do
104
+ expected_files = @files.values_at(2)
105
+ modified_files = @event.modified_files
106
+
107
+ expected_files.each do |file|
108
+ modified_files.should include(file)
109
+ end
110
+ end
111
+
112
+ it 'should not return files not modified after the last event time' do
113
+ unexpected_files = @files.values_at(0, 1)
114
+ modified_files = @event.modified_files
115
+
116
+ unexpected_files.each do |file|
117
+ modified_files.should_not include(file)
118
+ end
93
119
  end
94
120
  end
95
-
96
- it 'should not return files not modified after the last event time' do
97
- unexpected_files = @files.values_at(0, 1)
98
- modified_files = @event.modified_files
99
-
100
- unexpected_files.each do |file|
101
- modified_files.should_not include(file)
121
+
122
+ describe 'when the stream mode is cache' do
123
+ before :each do
124
+ @stream.stubs(:mode).returns(:cache)
125
+ end
126
+
127
+ before :each do
128
+ @dir_cache = { @path => {} }
129
+ @files.each_with_index do |file, i|
130
+ size = 50 * (i + 1)
131
+ mtime = @now + i - 2
132
+ File.stubs(:size).with(file).returns(size)
133
+ File.stubs(:mtime).with(file).returns(mtime)
134
+ stat = stub("file #{i+1} stat", :mtime => mtime, :size => size)
135
+ @dir_cache[@path][file] = stat
136
+ end
137
+ @stream.stubs(:dirs).returns(@dir_cache)
138
+ end
139
+
140
+ it 'should get the file list' do
141
+ @event.expects(:files).returns(@files)
142
+ @event.modified_files
143
+ end
144
+
145
+ it 'should get the stream dir cache' do
146
+ @stream.expects(:dirs).returns(@dir_cache)
147
+ @event.modified_files
148
+ end
149
+
150
+ it 'should get the dir cache for the event path' do
151
+ sub_cache = @dir_cache[@path]
152
+ @dir_cache.expects(:[]).with(@path).returns(sub_cache)
153
+ @event.modified_files
154
+ end
155
+
156
+ it 'should return files that do not appear in the cache' do
157
+ expected_files = Array.new(2) { |i| stub("new file #{i+1}") }
158
+ expected_files.each { |file| @files.push(file) }
159
+ modified_files = @event.modified_files
160
+
161
+ expected_files.each do |file|
162
+ modified_files.should include(file)
163
+ end
164
+ end
165
+
166
+ it 'should return files with sizes that differ from the cache' do
167
+ @dir_cache[@path][@files[3]].stubs(:size).returns(3)
168
+ @dir_cache[@path][@files[4]].stubs(:size).returns(101)
169
+ expected_files = @files.values_at(3, 4)
170
+ modified_files = @event.modified_files
171
+
172
+ expected_files.each do |file|
173
+ modified_files.should include(file)
174
+ end
175
+ end
176
+
177
+ it 'should return files with mtimes that differ from the cache' do
178
+ @dir_cache[@path][@files[2]].stubs(:mtime).returns(@now - 234)
179
+ expected_files = @files.values_at(2)
180
+ modified_files = @event.modified_files
181
+
182
+ expected_files.each do |file|
183
+ modified_files.should include(file)
184
+ end
185
+ end
186
+
187
+ it 'should not return files not modified from the cache' do
188
+ unexpected_files = @files.values_at(0, 1)
189
+ modified_files = @event.modified_files
190
+
191
+ unexpected_files.each do |file|
192
+ modified_files.should_not include(file)
193
+ end
194
+ end
195
+
196
+ it 'should handle this path not yet cached' do
197
+ @dir_cache.delete(@path)
198
+ expected_files = @files
199
+ modified_files = @event.modified_files
200
+
201
+ expected_files.each do |file|
202
+ modified_files.should include(file)
203
+ end
102
204
  end
103
205
  end
104
206
  end
@@ -50,6 +50,7 @@ describe FSEvents::Stream do
50
50
  [:allocator, :context, :since, :latency, :flags].each do |opt|
51
51
  @options[opt] = stub(opt.to_s)
52
52
  end
53
+ @options[:mode] = :cache
53
54
  @other_path = '/other/path'
54
55
  end
55
56
 
@@ -87,6 +88,10 @@ describe FSEvents::Stream do
87
88
  FSEvents::Stream.new(@options) {}.paths.should == [Dir.pwd]
88
89
  end
89
90
 
91
+ it 'should strip a trailing slash from the path' do
92
+ FSEvents::Stream.new("#{@path}/", "#{@other_path}/", @options) {}.paths.should == [@path, @other_path]
93
+ end
94
+
90
95
  it "should store 'since' (event ID)" do
91
96
  FSEvents::Stream.new(@path, @options) {}.since.should == @options[:since]
92
97
  end
@@ -113,6 +118,19 @@ describe FSEvents::Stream do
113
118
  @options.delete(:flags)
114
119
  FSEvents::Stream.new(@path, @options) {}.flags.should == 0
115
120
  end
121
+
122
+ it 'should store mode' do
123
+ FSEvents::Stream.new(@path, @options) {}.mode.should == @options[:mode]
124
+ end
125
+
126
+ it 'should default mode to mtime' do
127
+ @options.delete(:mode)
128
+ FSEvents::Stream.new(@path, @options) {}.mode.should == :mtime
129
+ end
130
+
131
+ it 'should not accept any mode other than mtime or cache' do
132
+ lambda { FSEvents::Stream.new(@path, @options.merge(:mode => :something_else)) {} }.should raise_error(ArgumentError)
133
+ end
116
134
  end
117
135
  end
118
136
 
@@ -272,7 +290,7 @@ describe FSEvents::Stream do
272
290
 
273
291
  it 'should extend the event array' do
274
292
  @args = @args_hash.values_at(*@callback_arg_order)
275
- @callback.expects(:call).with { |events| events.is_a?(EventArray) }
293
+ @callback.expects(:call).with(kind_of(EventArray))
276
294
  @proc.call(*@args)
277
295
  end
278
296
 
@@ -405,11 +423,101 @@ describe FSEvents::Stream do
405
423
  end
406
424
 
407
425
  describe 'updating its last event' do
408
- it 'should store the last event time' do
409
- now = Time.now
410
- Time.stubs(:now).returns(now)
411
- @stream.update_last_event
412
- @stream.last_event.should == now
426
+ describe 'when mode is mtime' do
427
+ before :each do
428
+ @stream.stubs(:mode).returns(:mtime)
429
+ end
430
+
431
+ it 'should store the last event time' do
432
+ now = Time.now
433
+ Time.stubs(:now).returns(now)
434
+ @stream.update_last_event
435
+ @stream.last_event.should == now
436
+ end
437
+ end
438
+
439
+ describe 'when mode is cache' do
440
+ before :each do
441
+ @stream.stubs(:mode).returns(:cache)
442
+ @files = Array.new(5) { |i| stub("file #{i+1}") }
443
+ @stats = Array.new(5) { |i| stub("file #{i+1} stat", :directory? => false) }
444
+
445
+ @files.zip(@stats).each do |file, stat|
446
+ File::Stat.stubs(:new).with(file).returns(stat)
447
+ end
448
+
449
+ Dir.stubs(:[]).returns(@files)
450
+ end
451
+
452
+ it 'should get the contents of its path' do
453
+ Dir.expects(:[]).with("#{@path}/*").returns([])
454
+ @stream.update_last_event
455
+ end
456
+
457
+ it 'should get stat objects for the path contents' do
458
+ @files.zip(@stats).each do |file, stat|
459
+ File::Stat.expects(:new).with(file).returns(stat)
460
+ end
461
+ @stream.update_last_event
462
+ end
463
+
464
+ it 'should cache the stat objects' do
465
+ @stream.update_last_event
466
+
467
+ @files.zip(@stats).each do |file, stat|
468
+ @stream.dirs[@path][file].should == stat
469
+ end
470
+ end
471
+
472
+ it 'should update already-existent cache entries' do
473
+ file = @files[3]
474
+ val = @stats[3]
475
+ @stream.dirs[@path] = { file => 'some other val' }
476
+
477
+ @stream.update_last_event
478
+ @stream.dirs[@path][file].should == val
479
+ end
480
+
481
+ it 'should remove non-needed cache entries' do
482
+ file = 'some other file'
483
+ @stream.dirs[@path] = { file => 'some val' }
484
+
485
+ @stream.update_last_event
486
+ @stream.dirs[@path].should_not have_key(file)
487
+ end
488
+
489
+ it 'should handle multiple paths' do
490
+ other_path = '/other/path'
491
+ paths = [@path, other_path]
492
+ @stream.stubs(:paths).returns(paths)
493
+
494
+ paths.each do |path|
495
+ Dir.expects(:[]).with("#{path}/*").returns([])
496
+ end
497
+ @stream.update_last_event
498
+ end
499
+
500
+ it 'should see if there are any subdirectories' do
501
+ @stats.each { |stat| stat.expects(:directory?) }
502
+ @stream.update_last_event
503
+ end
504
+
505
+ it 'should cache subdirectories' do
506
+ subdir_path = '/sub/dir/path'
507
+ @files[3].stubs(:to_s).returns(subdir_path)
508
+ @stats[3].stubs(:directory?).returns(true)
509
+ Dir.expects(:[]).with("#{subdir_path}/*").returns([])
510
+ @stream.update_last_event
511
+ end
512
+
513
+ it 'should not add cached subdirectories to the watched paths' do
514
+ subdir_path = '/sub/dir/path'
515
+ @files[3].stubs(:to_s).returns(subdir_path)
516
+ @stats[3].stubs(:directory?).returns(true)
517
+ Dir.stubs(:[]).with("#{subdir_path}/*").returns([])
518
+ @stream.update_last_event
519
+ @stream.paths.should_not include(@files[3])
520
+ end
413
521
  end
414
522
  end
415
523
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fsevents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yossef Mendelssohn
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-17 00:00:00 -05:00
12
+ date: 2008-06-30 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  requirements: []
77
77
 
78
78
  rubyforge_project: yomendel
79
- rubygems_version: 1.1.1
79
+ rubygems_version: 1.2.0
80
80
  signing_key:
81
81
  specification_version: 2
82
82
  summary: Simple, usable FSEvents API