directory_watcher 1.4.1 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/History.txt +10 -0
- data/README.txt +1 -1
- data/Rakefile +16 -5
- data/lib/directory_watcher.rb +166 -139
- data/lib/directory_watcher/collector.rb +283 -0
- data/lib/directory_watcher/configuration.rb +228 -0
- data/lib/directory_watcher/coolio_scanner.rb +61 -127
- data/lib/directory_watcher/em_scanner.rb +81 -153
- data/lib/directory_watcher/event.rb +72 -0
- data/lib/directory_watcher/eventable_scanner.rb +242 -0
- data/lib/directory_watcher/file_stat.rb +65 -0
- data/lib/directory_watcher/logable.rb +26 -0
- data/lib/directory_watcher/notifier.rb +49 -0
- data/lib/directory_watcher/paths.rb +55 -0
- data/lib/directory_watcher/rev_scanner.rb +68 -131
- data/lib/directory_watcher/scan.rb +72 -0
- data/lib/directory_watcher/scan_and_queue.rb +22 -0
- data/lib/directory_watcher/scanner.rb +26 -209
- data/lib/directory_watcher/threaded.rb +277 -0
- data/lib/directory_watcher/version.rb +8 -0
- data/spec/directory_watcher_spec.rb +37 -0
- data/spec/paths_spec.rb +7 -0
- data/spec/scanner_scenarios.rb +236 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/utility_classes.rb +117 -0
- data/version.txt +1 -1
- metadata +123 -23
- data/bin/dw +0 -2
@@ -0,0 +1,277 @@
|
|
1
|
+
#
|
2
|
+
# == Synopsis
|
3
|
+
# The Threaded module is used to perform some activity at a specified
|
4
|
+
# interval.
|
5
|
+
#
|
6
|
+
# == Details
|
7
|
+
# Sometimes it is useful for an object to have its own thread of execution
|
8
|
+
# to perform a task at a recurring interval. The Threaded module
|
9
|
+
# encapsulates this functionality so you don't have to write it yourself. It
|
10
|
+
# can be used with any object that responds to the +run+ method.
|
11
|
+
#
|
12
|
+
# The threaded object is run by calling the +start+ method. This will create
|
13
|
+
# a new thread that will invoke the +run+ method at the desired interval.
|
14
|
+
# Just before the thread is created the +before_starting+ method will be
|
15
|
+
# called (if it is defined by the threaded object). Likewise, after the
|
16
|
+
# thread is created the +after_starting+ method will be called (if it is
|
17
|
+
# defined by the threaded object).
|
18
|
+
#
|
19
|
+
# The threaded object is stopped by calling the +stop+ method. This sets an
|
20
|
+
# internal flag and then wakes up the thread. The thread gracefully exits
|
21
|
+
# after checking the flag. Like the start method, before and after methods
|
22
|
+
# are defined for stopping as well. Just before the thread is stopped the
|
23
|
+
# +before_stopping+ method will be called (if it is defined by the threaded
|
24
|
+
# object). Likewise, after the thread has died the +after_stopping+ method
|
25
|
+
# will be called (if it is defined by the threaded object).
|
26
|
+
#
|
27
|
+
# Calling the +join+ method on a threaded object will cause the calling
|
28
|
+
# thread to wait until the threaded object has stopped. An optional timeout
|
29
|
+
# parameter can be given.
|
30
|
+
#
|
31
|
+
module DirectoryWatcher::Threaded
|
32
|
+
|
33
|
+
# This method will be called by the activity thread at the desired
|
34
|
+
# interval. Implementing classes are expect to provide this
|
35
|
+
# functionality.
|
36
|
+
#
|
37
|
+
def run
|
38
|
+
raise NotImplementedError,
|
39
|
+
'The run method must be defined by the threaded object.'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Start the activity thread. If already started this method will return
|
43
|
+
# without taking any action.
|
44
|
+
#
|
45
|
+
# If the including class defines a 'before_starting' method, it will be
|
46
|
+
# called before the thread is created and run. Likewise, if the
|
47
|
+
# including class defines an 'after_starting' method, it will be called
|
48
|
+
# after the thread is created.
|
49
|
+
#
|
50
|
+
def start
|
51
|
+
return self if _activity_thread.running?
|
52
|
+
|
53
|
+
before_starting if self.respond_to?(:before_starting)
|
54
|
+
@_activity_thread.start self
|
55
|
+
after_starting if self.respond_to?(:after_starting)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Stop the activity thread. If already stopped this method will return
|
60
|
+
# without taking any action.
|
61
|
+
#
|
62
|
+
# If the including class defines a 'before_stopping' method, it will be
|
63
|
+
# called before the thread is stopped. Likewise, if the including class
|
64
|
+
# defines an 'after_stopping' method, it will be called after the thread
|
65
|
+
# has stopped.
|
66
|
+
#
|
67
|
+
def stop
|
68
|
+
return self unless _activity_thread.running?
|
69
|
+
|
70
|
+
before_stopping if self.respond_to?(:before_stopping)
|
71
|
+
@_activity_thread.stop
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Stop the activity thread from doing work. This will not stop the activity
|
76
|
+
# thread, it will just stop it from calling the 'run' method on every
|
77
|
+
# iteration. It will also not increment the number of iterations it has run.
|
78
|
+
def pause
|
79
|
+
@_activity_thread.working = false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Resume the activity thread
|
83
|
+
def resume
|
84
|
+
@_activity_thread.working = true
|
85
|
+
end
|
86
|
+
|
87
|
+
# Wait on the activity thread. If the thread is already stopped, this
|
88
|
+
# method will return without taking any action. Otherwise, this method
|
89
|
+
# does not return until the activity thread has stopped, or a specific
|
90
|
+
# number of iterations has passed since this method was called.
|
91
|
+
#
|
92
|
+
def wait( limit = nil )
|
93
|
+
return self unless _activity_thread.running?
|
94
|
+
initial_iterations = @_activity_thread.iterations
|
95
|
+
loop {
|
96
|
+
break unless @_activity_thread.running?
|
97
|
+
break if limit and @_activity_thread.iterations > ( initial_iterations + limit )
|
98
|
+
Thread.pass
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# If the activity thread is running, the calling thread will suspend
|
103
|
+
# execution and run the activity thread. This method does not return until
|
104
|
+
# the activity thread is stopped or until _limit_ seconds have passed.
|
105
|
+
#
|
106
|
+
# If the activity thread is not running, this method returns immediately
|
107
|
+
# with +nil+.
|
108
|
+
#
|
109
|
+
def join( limit = nil )
|
110
|
+
_activity_thread.join(limit) ? self : nil
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns +true+ if the activity thread is running. Returns +false+
|
114
|
+
# otherwise.
|
115
|
+
#
|
116
|
+
def running?
|
117
|
+
_activity_thread.running?
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns +true+ if the activity thread has finished its maximum
|
121
|
+
# number of iterations or the thread is no longer running.
|
122
|
+
# Returns +false+ otherwise.
|
123
|
+
#
|
124
|
+
def finished_iterations?
|
125
|
+
return true unless _activity_thread.running?
|
126
|
+
@_activity_thread.finished_iterations?
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns the status of threaded object.
|
130
|
+
#
|
131
|
+
# 'sleep' : sleeping or waiting on I/O
|
132
|
+
# 'run' : executing
|
133
|
+
# 'aborting' : aborting
|
134
|
+
# false : not running or terminated normally
|
135
|
+
# nil : terminated with an exception
|
136
|
+
#
|
137
|
+
# If this method returns +nil+, then calling join on the threaded object
|
138
|
+
# will cause the exception to be raised in the calling thread.
|
139
|
+
#
|
140
|
+
def status
|
141
|
+
return false if _activity_thread.thread.nil?
|
142
|
+
@_activity_thread.thread.status
|
143
|
+
end
|
144
|
+
|
145
|
+
# Sets the number of seconds to sleep between invocations of the
|
146
|
+
# threaded object's 'run' method.
|
147
|
+
#
|
148
|
+
def interval=( value )
|
149
|
+
value = Float(value)
|
150
|
+
raise ArgumentError, "Sleep interval must be >= 0" unless value >= 0
|
151
|
+
_activity_thread.interval = value
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the number of seconds to sleep between invocations of the
|
155
|
+
# threaded object's 'run' method.
|
156
|
+
#
|
157
|
+
def interval
|
158
|
+
_activity_thread.interval
|
159
|
+
end
|
160
|
+
|
161
|
+
# Sets the maximum number of invocations of the threaded object's
|
162
|
+
# 'run' method
|
163
|
+
#
|
164
|
+
def maximum_iterations=( value )
|
165
|
+
unless value.nil?
|
166
|
+
value = Integer(value)
|
167
|
+
raise ArgumentError, "maximum iterations must be >= 1" unless value >= 1
|
168
|
+
end
|
169
|
+
|
170
|
+
_activity_thread.maximum_iterations = value
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns the maximum number of invocations of the threaded
|
174
|
+
# object's 'run' method
|
175
|
+
#
|
176
|
+
def maximum_iterations
|
177
|
+
_activity_thread.maximum_iterations
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the number of iterations of the threaded object's 'run' method
|
181
|
+
# completed thus far.
|
182
|
+
#
|
183
|
+
def iterations
|
184
|
+
_activity_thread.iterations
|
185
|
+
end
|
186
|
+
|
187
|
+
# Set to +true+ to continue running the threaded object even if an error
|
188
|
+
# is raised by the +run+ method. The default behavior is to stop the
|
189
|
+
# activity thread when an error is raised by the run method.
|
190
|
+
#
|
191
|
+
# A SystemExit will never be caught; it will always cause the Ruby
|
192
|
+
# interpreter to exit.
|
193
|
+
#
|
194
|
+
def continue_on_error=( value )
|
195
|
+
_activity_thread.continue_on_error = (value ? true : false)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Returns +true+ if the threaded object should continue running even if an
|
199
|
+
# error is raised by the run method. The default is to return +false+. The
|
200
|
+
# threaded object will stop running when an error is raised.
|
201
|
+
#
|
202
|
+
def continue_on_error?
|
203
|
+
_activity_thread.continue_on_error
|
204
|
+
end
|
205
|
+
|
206
|
+
# :stopdoc:
|
207
|
+
def _activity_thread
|
208
|
+
@_activity_thread ||= ::DirectoryWatcher::Threaded::ThreadContainer.new(60, 0, nil, false);
|
209
|
+
end # @private
|
210
|
+
|
211
|
+
# @private
|
212
|
+
ThreadContainer = Struct.new( :interval, :iterations, :maximum_iterations, :continue_on_error, :thread, :running, :working) {
|
213
|
+
def start( threaded )
|
214
|
+
|
215
|
+
self.working = true
|
216
|
+
self.running = true
|
217
|
+
self.iterations = 0
|
218
|
+
self.thread = Thread.new { run threaded }
|
219
|
+
Thread.pass
|
220
|
+
end # @private
|
221
|
+
|
222
|
+
def stop
|
223
|
+
self.running = false
|
224
|
+
thread.wakeup
|
225
|
+
end # @private
|
226
|
+
|
227
|
+
def run( threaded )
|
228
|
+
loop do
|
229
|
+
begin
|
230
|
+
break unless running?
|
231
|
+
do_work( threaded )
|
232
|
+
|
233
|
+
sleep interval if running?
|
234
|
+
rescue SystemExit; raise
|
235
|
+
rescue Exception => err
|
236
|
+
if continue_on_error
|
237
|
+
$stderr.puts err
|
238
|
+
else
|
239
|
+
$stderr.puts err
|
240
|
+
raise err
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
ensure
|
245
|
+
if threaded.respond_to?(:after_stopping) and !self.running
|
246
|
+
threaded.after_stopping
|
247
|
+
end
|
248
|
+
self.running = false
|
249
|
+
end # @private
|
250
|
+
|
251
|
+
def join( limit = nil )
|
252
|
+
return if thread.nil?
|
253
|
+
limit ? thread.join(limit) : thread.join
|
254
|
+
end # @private
|
255
|
+
|
256
|
+
def do_work( threaded )
|
257
|
+
if working then
|
258
|
+
threaded.run
|
259
|
+
|
260
|
+
if maximum_iterations
|
261
|
+
self.iterations += 1
|
262
|
+
if finished_iterations?
|
263
|
+
self.running = false
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end # @private
|
268
|
+
|
269
|
+
def finished_iterations?
|
270
|
+
return true if maximum_iterations and (iterations >= maximum_iterations)
|
271
|
+
return false
|
272
|
+
end # @private
|
273
|
+
|
274
|
+
alias :running? :running
|
275
|
+
}
|
276
|
+
# :startdoc:
|
277
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DirectoryWatcher do
|
4
|
+
it "has a version" do
|
5
|
+
DirectoryWatcher.version.should =~ /\d\.\d\.\d/
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "Scanners" do
|
10
|
+
[ nil, :em, :coolio ].each do |scanner|
|
11
|
+
# [ :rev ].each do |scanner|
|
12
|
+
context "#{scanner} Scanner" do
|
13
|
+
|
14
|
+
let( :default_options ) { { :glob => "**/*", :interval => 0.05} }
|
15
|
+
let( :options ) { default_options.merge( :scanner => scanner ) }
|
16
|
+
let( :options_with_pre_load ) { options.merge( :pre_load => true ) }
|
17
|
+
let( :options_with_stable ) { options.merge( :stable => 2 ) }
|
18
|
+
let( :options_with_glob ) { options.merge( :glob => '**/*.42' ) }
|
19
|
+
let( :options_with_persist ) { options.merge( :persist => scratch_path( 'persist.yml' ) ) }
|
20
|
+
|
21
|
+
let( :directory_watcher ) { DirectoryWatcher.new( @scratch_dir, options ) }
|
22
|
+
let( :directory_watcher_with_pre_load ) { DirectoryWatcher.new( @scratch_dir, options_with_pre_load ) }
|
23
|
+
let( :directory_watcher_with_stable ) { DirectoryWatcher.new( @scratch_dir, options_with_stable ) }
|
24
|
+
let( :directory_watcher_with_glob ) { DirectoryWatcher.new( @scratch_dir, options_with_glob ) }
|
25
|
+
let( :directory_watcher_with_persist ) { DirectoryWatcher.new( @scratch_dir, options_with_persist ) }
|
26
|
+
|
27
|
+
let( :scenario ) { DirectoryWatcherSpecs::Scenario.new( directory_watcher) }
|
28
|
+
let( :scenario_with_pre_load ) { DirectoryWatcherSpecs::Scenario.new( directory_watcher_with_pre_load ) }
|
29
|
+
let( :scenario_with_stable ) { DirectoryWatcherSpecs::Scenario.new( directory_watcher_with_stable ) }
|
30
|
+
let( :scenario_with_glob ) { DirectoryWatcherSpecs::Scenario.new( directory_watcher_with_glob ) }
|
31
|
+
let( :scenario_with_persist ) { DirectoryWatcherSpecs::Scenario.new( directory_watcher_with_persist ) }
|
32
|
+
|
33
|
+
it_should_behave_like 'Scanner'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/spec/paths_spec.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
shared_examples_for "Scanner" do
|
2
|
+
context "Event Types"do
|
3
|
+
it "sends added events" do
|
4
|
+
|
5
|
+
scenario.run_and_wait_for_event_count(1) do
|
6
|
+
touch( scratch_path( 'added' ) )
|
7
|
+
end.stop
|
8
|
+
|
9
|
+
scenario.events.should be_events_like( [[ :added, 'added' ]] )
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sends modified events for file size modifications" do
|
13
|
+
|
14
|
+
modified_file = scratch_path( 'modified' )
|
15
|
+
scenario.run_and_wait_for_event_count(1) do
|
16
|
+
touch( modified_file )
|
17
|
+
end.run_and_wait_for_event_count(1) do
|
18
|
+
append_to( modified_file )
|
19
|
+
end.stop
|
20
|
+
|
21
|
+
scenario.events.should be_events_like( [[ :added, 'modified'], [ :modified, 'modified']] )
|
22
|
+
end
|
23
|
+
|
24
|
+
it "sends modified events for mtime modifications" do
|
25
|
+
modified_file = scratch_path( 'modified' )
|
26
|
+
|
27
|
+
scenario.run_and_wait_for_event_count(1) do
|
28
|
+
touch( modified_file, Time.now - 5 )
|
29
|
+
end.run_and_wait_for_event_count(1) do
|
30
|
+
touch( modified_file )
|
31
|
+
end.stop
|
32
|
+
|
33
|
+
scenario.events.should be_events_like( [[ :added, 'modified'], [ :modified, 'modified']] )
|
34
|
+
end
|
35
|
+
|
36
|
+
it "sends removed events" do
|
37
|
+
removed_file = scratch_path( 'removed' )
|
38
|
+
scenario.run_and_wait_for_event_count(1) do
|
39
|
+
touch( removed_file, Time.now )
|
40
|
+
end.run_and_wait_for_event_count(1) do
|
41
|
+
File.unlink( removed_file )
|
42
|
+
end.stop
|
43
|
+
|
44
|
+
scenario.events.should be_events_like [ [:added, 'removed'], [:removed, 'removed'] ]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "sends stable events" do
|
48
|
+
stable_file = scratch_path( 'stable' )
|
49
|
+
scenario_with_stable.run_and_wait_for_event_count(2) do |s|
|
50
|
+
touch( stable_file )
|
51
|
+
# do nothing wait for the stable event.
|
52
|
+
end.stop
|
53
|
+
|
54
|
+
scenario_with_stable.events.should be_events_like [ [:added, 'stable'], [:stable, 'stable'] ]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "only sends stable events once" do
|
58
|
+
stable_file = scratch_path( 'stable' )
|
59
|
+
scenario_with_stable.run_and_wait_for_scan_count(5) do |s|
|
60
|
+
touch( stable_file )
|
61
|
+
# do nothing
|
62
|
+
end.stop
|
63
|
+
|
64
|
+
scenario_with_stable.events.size.should == 2
|
65
|
+
end
|
66
|
+
|
67
|
+
it "events are not sent for directory creation" do
|
68
|
+
a_dir = scratch_path( 'subdir' )
|
69
|
+
|
70
|
+
scenario.run_and_wait_for_scan_count(2) do
|
71
|
+
Dir.mkdir( a_dir )
|
72
|
+
end.stop
|
73
|
+
|
74
|
+
scenario.events.should be_empty
|
75
|
+
end
|
76
|
+
|
77
|
+
it "sends events for files in sub directories" do
|
78
|
+
a_dir = scratch_path( 'subdir' )
|
79
|
+
|
80
|
+
scenario.run_and_wait_for_event_count(1) do
|
81
|
+
Dir.mkdir( a_dir )
|
82
|
+
subfile = File.join( a_dir, 'subfile' )
|
83
|
+
touch( subfile )
|
84
|
+
end.stop
|
85
|
+
|
86
|
+
scenario.events.should be_events_like [ [:added, 'subfile'] ]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "run_once" do
|
91
|
+
it "can be run on command via 'run_once'" do
|
92
|
+
one_shot_file = scratch_path( "run_once" )
|
93
|
+
scenario.run_once_and_wait_for_event_count(1) do
|
94
|
+
touch( one_shot_file )
|
95
|
+
end.stop
|
96
|
+
scenario.events.should be_events_like [ [:added, 'run_once'] ]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "pre_load option " do
|
101
|
+
it "skips initial add events" do
|
102
|
+
modified_file = scratch_path( 'modified' )
|
103
|
+
touch( modified_file, Time.now - 5 )
|
104
|
+
|
105
|
+
scenario_with_pre_load.run_and_wait_for_event_count(1) do
|
106
|
+
touch( modified_file )
|
107
|
+
end.stop
|
108
|
+
|
109
|
+
scenario_with_pre_load.events.should be_events_like( [[ :modified, 'modified']] )
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "globbing" do
|
114
|
+
it "only sends events for files that match" do
|
115
|
+
non_matching = scratch_path( 'no-match' )
|
116
|
+
matching = scratch_path( 'match.42' )
|
117
|
+
|
118
|
+
scenario_with_glob.run_and_wait_for_event_count(1) do
|
119
|
+
touch( non_matching )
|
120
|
+
touch( matching, Time.now - 5 )
|
121
|
+
end.run_and_wait_for_event_count(1) do
|
122
|
+
touch( matching )
|
123
|
+
end.stop
|
124
|
+
|
125
|
+
scenario_with_glob.events.should be_events_like( [[ :added, 'match.42' ], [ :modified, 'match.42' ]] )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "running?" do
|
130
|
+
it "is true when the watcher is running" do
|
131
|
+
directory_watcher.start
|
132
|
+
directory_watcher.running?.should be_true
|
133
|
+
directory_watcher.stop
|
134
|
+
end
|
135
|
+
|
136
|
+
it "is false when the watcher is not running" do
|
137
|
+
directory_watcher.running?.should be_false
|
138
|
+
directory_watcher.start
|
139
|
+
directory_watcher.running?.should be_true
|
140
|
+
directory_watcher.stop
|
141
|
+
directory_watcher.running?.should be_false
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "persistence" do
|
146
|
+
it "saves the current state of the system when the watcher is stopped" do
|
147
|
+
modified_file = scratch_path( 'modified' )
|
148
|
+
scenario_with_persist.run_and_wait_for_event_count(1) do
|
149
|
+
touch( modified_file, Time.now - 20 )
|
150
|
+
end.run_and_wait_for_event_count(1) do
|
151
|
+
touch( modified_file, Time.now - 10 )
|
152
|
+
end.stop
|
153
|
+
|
154
|
+
scenario_with_persist.events.should be_events_like( [[ :added, 'modified'], [ :modified, 'modified' ]] )
|
155
|
+
|
156
|
+
scenario_with_persist.reset
|
157
|
+
scenario_with_persist.resume
|
158
|
+
Thread.pass until scenario_with_persist.events.size >= 1
|
159
|
+
scenario_with_persist.pause
|
160
|
+
|
161
|
+
scenario_with_persist.run_and_wait_for_event_count(1) do
|
162
|
+
touch( modified_file )
|
163
|
+
end.stop
|
164
|
+
|
165
|
+
scenario_with_persist.events.should be_events_like( [[:added, 'persist.yml'], [ :modified, 'modified' ]] )
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "sorting" do
|
170
|
+
[:ascending, :descending].each do |ordering|
|
171
|
+
context "#{ordering}" do
|
172
|
+
context "file name" do
|
173
|
+
let( :filenames ) { ('a'..'z').sort_by {rand} }
|
174
|
+
let( :options ) { default_options.merge( :order_by => ordering ) }
|
175
|
+
before do
|
176
|
+
filenames.each do |p|
|
177
|
+
touch( scratch_path( p ))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
it "#{ordering}" do
|
182
|
+
scenario.run_and_wait_for_event_count(filenames.size) do
|
183
|
+
# wait
|
184
|
+
end
|
185
|
+
final_events = filenames.sort.map { |p| [:added, p] }
|
186
|
+
final_events.reverse! if ordering == :descending
|
187
|
+
scenario.events.should be_events_like( final_events )
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "mtime" do
|
192
|
+
let( :current_time ) { Time.now }
|
193
|
+
let( :basenames ) { ('a'..'z').to_a }
|
194
|
+
let( :delta_times ) { unique_integer_list( basenames.size, 5000 ) }
|
195
|
+
let( :filenames ) { basenames.inject({}) { |h,k| h[k] = current_time - delta_times.shift; h } }
|
196
|
+
let( :options ) { default_options.merge( :sort_by => :mtime, :order_by => ordering ) }
|
197
|
+
|
198
|
+
before do
|
199
|
+
filenames.keys.sort_by{ rand }.each do |p|
|
200
|
+
touch( scratch_path(p), filenames[p] )
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it "#{ordering}" do
|
205
|
+
scenario.run_and_wait_for_event_count(filenames.size) { nil }
|
206
|
+
sorted_fnames = filenames.to_a.sort_by { |v| v[1] }
|
207
|
+
final_events = sorted_fnames.map { |fn,ts| [:added, fn] }
|
208
|
+
final_events.reverse! if ordering == :descending
|
209
|
+
scenario.events.should be_events_like( final_events )
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "size" do
|
214
|
+
let( :basenames ) { ('a'..'z').to_a }
|
215
|
+
let( :file_sizes ) { unique_integer_list( basenames.size, 1000 ) }
|
216
|
+
let( :filenames ) { basenames.inject({}) { |h,k| h[k] = file_sizes.shift; h } }
|
217
|
+
let( :options ) { default_options.merge( :sort_by => :size, :order_by => ordering ) }
|
218
|
+
|
219
|
+
before do
|
220
|
+
filenames.keys.sort_by{ rand }.each do |p|
|
221
|
+
append_to( scratch_path(p), filenames[p] )
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it "#{ordering}" do
|
226
|
+
scenario.run_and_wait_for_event_count(filenames.size) { nil }
|
227
|
+
sorted_fnames = filenames.to_a.sort_by { |v| v[1] }
|
228
|
+
final_events = sorted_fnames.map { |fn,ts| [:added, fn] }
|
229
|
+
final_events.reverse! if ordering == :descending
|
230
|
+
scenario.events.should be_events_like( final_events )
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|