listen 2.7.5 → 2.7.6

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -0
  3. data/.rspec +0 -0
  4. data/.rubocop.yml +0 -0
  5. data/.travis.yml +0 -1
  6. data/.yardopts +0 -0
  7. data/CHANGELOG.md +0 -0
  8. data/CONTRIBUTING.md +0 -0
  9. data/Gemfile +25 -4
  10. data/Guardfile +0 -0
  11. data/LICENSE.txt +0 -0
  12. data/README.md +18 -10
  13. data/Rakefile +0 -0
  14. data/lib/listen.rb +2 -4
  15. data/lib/listen/adapter.rb +13 -4
  16. data/lib/listen/adapter/base.rb +33 -16
  17. data/lib/listen/adapter/bsd.rb +21 -38
  18. data/lib/listen/adapter/darwin.rb +17 -25
  19. data/lib/listen/adapter/linux.rb +34 -52
  20. data/lib/listen/adapter/polling.rb +9 -25
  21. data/lib/listen/adapter/tcp.rb +27 -14
  22. data/lib/listen/adapter/windows.rb +67 -23
  23. data/lib/listen/change.rb +26 -23
  24. data/lib/listen/cli.rb +0 -0
  25. data/lib/listen/directory.rb +47 -58
  26. data/lib/listen/file.rb +66 -101
  27. data/lib/listen/listener.rb +214 -155
  28. data/lib/listen/queue_optimizer.rb +104 -0
  29. data/lib/listen/record.rb +15 -5
  30. data/lib/listen/silencer.rb +14 -10
  31. data/lib/listen/tcp.rb +0 -1
  32. data/lib/listen/tcp/broadcaster.rb +31 -26
  33. data/lib/listen/tcp/message.rb +2 -2
  34. data/lib/listen/version.rb +1 -1
  35. data/listen.gemspec +1 -1
  36. data/spec/acceptance/listen_spec.rb +151 -239
  37. data/spec/acceptance/tcp_spec.rb +125 -134
  38. data/spec/lib/listen/adapter/base_spec.rb +13 -30
  39. data/spec/lib/listen/adapter/bsd_spec.rb +7 -35
  40. data/spec/lib/listen/adapter/darwin_spec.rb +18 -30
  41. data/spec/lib/listen/adapter/linux_spec.rb +49 -55
  42. data/spec/lib/listen/adapter/polling_spec.rb +20 -35
  43. data/spec/lib/listen/adapter/tcp_spec.rb +25 -27
  44. data/spec/lib/listen/adapter/windows_spec.rb +7 -33
  45. data/spec/lib/listen/adapter_spec.rb +10 -10
  46. data/spec/lib/listen/change_spec.rb +55 -57
  47. data/spec/lib/listen/directory_spec.rb +105 -155
  48. data/spec/lib/listen/file_spec.rb +186 -73
  49. data/spec/lib/listen/listener_spec.rb +233 -216
  50. data/spec/lib/listen/record_spec.rb +60 -22
  51. data/spec/lib/listen/silencer_spec.rb +48 -75
  52. data/spec/lib/listen/tcp/broadcaster_spec.rb +78 -69
  53. data/spec/lib/listen/tcp/listener_spec.rb +28 -71
  54. data/spec/lib/listen/tcp/message_spec.rb +48 -14
  55. data/spec/lib/listen_spec.rb +3 -3
  56. data/spec/spec_helper.rb +6 -3
  57. data/spec/support/acceptance_helper.rb +250 -31
  58. data/spec/support/fixtures_helper.rb +6 -4
  59. data/spec/support/platform_helper.rb +2 -2
  60. metadata +5 -5
  61. data/lib/listen/tcp/listener.rb +0 -108
@@ -1,45 +1,39 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Listen::TCP::Listener do
3
+ require 'listen/tcp/message'
4
+ require 'listen/tcp/broadcaster'
5
+
6
+ describe Listen::Listener do
4
7
 
5
8
  let(:host) { '10.0.0.2' }
6
9
  let(:port) { 4000 }
7
10
 
8
11
  subject { described_class.new("#{host}:#{port}", :recipient, options) }
9
12
  let(:options) { {} }
10
- let(:registry) { double(Celluloid::Registry, :[]= => true) }
13
+ let(:registry) { instance_double(Celluloid::Registry, :[]= => true) }
11
14
 
12
15
  let(:supervisor) do
13
- double(Celluloid::SupervisionGroup, add: true, pool: true)
16
+ instance_double(Celluloid::SupervisionGroup, add: true, pool: true)
14
17
  end
15
18
 
16
- let(:record) { double(Listen::Record, terminate: true, build: true) }
17
- let(:silencer) { double(Listen::Silencer, terminate: true) }
18
- let(:adapter) { double(Listen::Adapter::Base) }
19
- let(:broadcaster) { double(Listen::TCP::Broadcaster) }
20
- let(:change_pool) { double(Listen::Change, terminate: true) }
21
- let(:change_pool_async) { double('ChangePoolAsync') }
19
+ let(:record) { instance_double(Listen::Record, terminate: true, build: true) }
20
+ let(:silencer) { instance_double(Listen::Silencer, terminate: true) }
21
+ let(:adapter) { instance_double(Listen::Adapter::Base) }
22
+ let(:async) { instance_double(Listen::TCP::Broadcaster, broadcast: true) }
23
+ let(:broadcaster) { instance_double(Listen::TCP::Broadcaster, async: async) }
24
+ let(:change_pool) { instance_double(Listen::Change, terminate: true) }
25
+ let(:change_pool_async) { instance_double('ChangePoolAsync') }
22
26
  before do
23
- Celluloid::Registry.stub(:new) { registry }
24
- Celluloid::SupervisionGroup.stub(:run!) { supervisor }
25
- registry.stub(:[]).with(:silencer) { silencer }
26
- registry.stub(:[]).with(:adapter) { adapter }
27
- registry.stub(:[]).with(:record) { record }
28
- registry.stub(:[]).with(:change_pool) { change_pool }
29
- registry.stub(:[]).with(:broadcaster) { broadcaster }
27
+ allow(Celluloid::Registry).to receive(:new) { registry }
28
+ allow(Celluloid::SupervisionGroup).to receive(:run!) { supervisor }
29
+ allow(registry).to receive(:[]).with(:silencer) { silencer }
30
+ allow(registry).to receive(:[]).with(:adapter) { adapter }
31
+ allow(registry).to receive(:[]).with(:record) { record }
32
+ allow(registry).to receive(:[]).with(:change_pool) { change_pool }
33
+ allow(registry).to receive(:[]).with(:broadcaster) { broadcaster }
30
34
  end
31
35
 
32
36
  describe '#initialize' do
33
- its(:mode) { should be :recipient }
34
- its(:host) { should eq host }
35
- its(:port) { should eq port }
36
-
37
- it 'raises on invalid mode' do
38
- expect do
39
- described_class.new(port, :foo)
40
- end.to raise_error ArgumentError
41
- end
42
-
43
37
  it 'raises on omitted target' do
44
38
  expect do
45
39
  described_class.new(nil, :recipient)
@@ -50,21 +44,14 @@ describe Listen::TCP::Listener do
50
44
  context 'when broadcaster' do
51
45
  subject { described_class.new(port, :broadcaster) }
52
46
 
53
- it { should be_a_broadcaster }
54
- it { should_not be_a_recipient }
55
-
56
47
  it 'does not force TCP adapter through options' do
57
48
  expect(subject.options).not_to include(force_tcp: true)
58
49
  end
59
50
 
60
- context 'when host is omitted' do
61
- its(:host) { should be_nil }
62
- end
63
-
64
51
  describe '#start' do
65
52
  before do
66
- adapter.stub_chain(:async, :start)
67
- broadcaster.stub(:start)
53
+ allow(subject).to receive(:_start_adapter)
54
+ allow(broadcaster).to receive(:start)
68
55
  end
69
56
 
70
57
  it 'registers broadcaster' do
@@ -79,49 +66,27 @@ describe Listen::TCP::Listener do
79
66
  end
80
67
  end
81
68
 
82
- describe '#block' do
83
- let(:async) { double('TCP broadcaster async', broadcast: true) }
84
- let(:callback) { double(call: true) }
85
- let(:changes) do
86
- { modified: ['/foo'], added: [], removed: [] }
87
- end
88
-
69
+ describe 'queue' do
89
70
  before do
90
- broadcaster.stub(:async).and_return async
91
- end
92
-
93
- after do
94
- subject.block.call changes.values
95
- end
96
-
97
- context 'when paused' do
98
- it 'honours paused state and does nothing' do
99
- subject.pause
100
- expect(broadcaster).not_to receive(:async)
101
- expect(callback).not_to receive(:call)
102
- end
71
+ allow(broadcaster).to receive(:async).and_return async
103
72
  end
104
73
 
105
74
  context 'when stopped' do
106
75
  it 'honours stopped state and does nothing' do
107
76
  allow(subject).to receive(:supervisor) do
108
- double('SupervisionGroup', terminate: true)
77
+ instance_double(Celluloid::SupervisionGroup, terminate: true)
109
78
  end
110
79
 
111
80
  subject.stop
81
+ subject.queue(:file, :modified, 'foo')
112
82
  expect(broadcaster).not_to receive(:async)
113
- expect(callback).not_to receive(:call)
114
83
  end
115
84
  end
116
85
 
117
86
  it 'broadcasts changes asynchronously' do
118
- message = Listen::TCP::Message.new changes
87
+ message = Listen::TCP::Message.new(:file, :modified, 'foo', {})
119
88
  expect(async).to receive(:broadcast).with message.payload
120
- end
121
-
122
- it 'invokes original callback block' do
123
- subject.block = callback
124
- expect(callback).to receive(:call).with(*changes.values)
89
+ subject.queue(:file, :modified, 'foo')
125
90
  end
126
91
  end
127
92
  end
@@ -132,13 +97,5 @@ describe Listen::TCP::Listener do
132
97
  it 'forces TCP adapter through options' do
133
98
  expect(subject.options).to include(force_tcp: true)
134
99
  end
135
-
136
- it { should_not be_a_broadcaster }
137
- it { should be_a_recipient }
138
-
139
- context 'when host is omitted' do
140
- its(:host) { should eq described_class::DEFAULT_HOST }
141
- end
142
100
  end
143
-
144
101
  end
@@ -11,31 +11,65 @@ describe Listen::TCP::Message do
11
11
 
12
12
  describe '#initialize' do
13
13
  it 'initializes with an object' do
14
- message = described_class.new(object)
15
- expect(message.object).to be object
14
+ message = described_class.new(1, 2, 3)
15
+ expect(message.object).to eq [1, 2, 3]
16
16
  end
17
17
  end
18
18
 
19
19
  describe '#object=' do
20
- before do
21
- subject.object = object
20
+ subject do
21
+ described_class.new(object).tap do |message|
22
+ message.object = object
23
+ end
24
+ end
25
+
26
+ describe '#object' do
27
+ subject { super().object }
28
+ it { is_expected.to be object }
29
+ end
30
+
31
+ describe '#body' do
32
+ subject { super().body }
33
+ it { is_expected.to eq body }
34
+ end
35
+
36
+ describe '#size' do
37
+ subject { super().size }
38
+ it { is_expected.to eq size }
22
39
  end
23
40
 
24
- its(:object) { should be object }
25
- its(:body) { should eq body }
26
- its(:size) { should eq size }
27
- its(:payload) { should eq payload }
41
+ describe '#payload' do
42
+ subject { super().payload }
43
+ it { is_expected.to eq payload }
44
+ end
28
45
  end
29
46
 
30
47
  describe '#payload=' do
31
- before do
32
- subject.payload = payload
48
+ subject do
49
+ described_class.new(object).tap do |message|
50
+ message.payload = payload
51
+ end
52
+ end
53
+
54
+ describe '#object' do
55
+ subject { super().object }
56
+ it { is_expected.to eq object }
57
+ end
58
+
59
+ describe '#body' do
60
+ subject { super().body }
61
+ it { is_expected.to eq body }
62
+ end
63
+
64
+ describe '#size' do
65
+ subject { super().size }
66
+ it { is_expected.to eq size }
33
67
  end
34
68
 
35
- its(:object) { should eq object }
36
- its(:body) { should eq body }
37
- its(:size) { should eq size }
38
- its(:payload) { should be payload }
69
+ describe '#payload' do
70
+ subject { super().payload }
71
+ it { is_expected.to be payload }
72
+ end
39
73
  end
40
74
 
41
75
  describe '.from_buffer' do
@@ -9,7 +9,7 @@ describe Listen do
9
9
 
10
10
  context 'when using :forward_to option' do
11
11
  it 'initializes TCP-listener in broadcast-mode' do
12
- expect(Listen::TCP::Listener).to receive(:new).
12
+ expect(Listen::Listener).to receive(:new).
13
13
  with(4000, :broadcaster, '/path', {})
14
14
  described_class.to('/path', forward_to: 4000)
15
15
  end
@@ -18,7 +18,7 @@ describe Listen do
18
18
  it 'sets stopping at false' do
19
19
  allow(Listen::Listener).to receive(:new)
20
20
  Listen.to('/path')
21
- expect(Listen.stopping).to be_false
21
+ expect(Listen.stopping).to be_falsey
22
22
  end
23
23
  end
24
24
 
@@ -41,7 +41,7 @@ describe Listen do
41
41
 
42
42
  describe '.on' do
43
43
  it 'initializes TCP-listener in recipient-mode' do
44
- expect(Listen::TCP::Listener).to receive(:new).
44
+ expect(Listen::Listener).to receive(:new).
45
45
  with(4000, :recipient, '/path')
46
46
  described_class.on(4000, '/path')
47
47
  end
@@ -16,15 +16,18 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
16
16
 
17
17
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
18
18
  RSpec.configure do |config|
19
- config.color_enabled = true
20
19
  config.order = :random
21
20
  config.filter_run focus: true
22
- config.treat_symbols_as_metadata_keys_with_true_values = true
23
21
  config.run_all_when_everything_filtered = true
24
- config.fail_fast = !ci?
22
+ # config.fail_fast = !ci?
25
23
  config.expect_with :rspec do |c|
26
24
  c.syntax = :expect
27
25
  end
26
+
27
+ config.mock_with :rspec do |mocks|
28
+ mocks.verify_doubled_constant_names = true
29
+ mocks.verify_partial_doubles = true
30
+ end
28
31
  end
29
32
 
30
33
  require 'rspec/retry'
@@ -1,47 +1,266 @@
1
- def listen(lag = 0.5)
2
- sleep lag # wait for changes
3
- sleep_until_next_second
4
- reset_changes
5
- yield
6
- sleep lag # wait for changes
7
- @changes
8
- end
1
+ {
2
+ modification: :modified,
3
+ addition: :added,
4
+ removal: :removed,
5
+ queued_modification: :modified,
6
+ queued_addition: :added,
7
+ }.each do |description, type|
9
8
 
10
- def setup_listener(options, callback)
11
- reset_changes
12
- Listen.to(paths, options, &callback)
13
- end
9
+ RSpec::Matchers.define "process_#{description}_of".to_sym do |expected|
10
+ match do |actual|
11
+ # Use cases:
12
+ # 1. reset the changes so they don't have leftovers
13
+ # 2. keep the queue if we're testing for existing accumulated changes
14
14
 
15
- def reset_changes
16
- @changes = { modified: [], added: [], removed: [] }
17
- end
15
+ # if were testing the queue (e.g. after unpause), don't reset
16
+ check_already_queued = /queued_/ =~ description
17
+ reset_queue = !check_already_queued
18
+
19
+ actual.listen(reset_queue) do
20
+ change_fs(type, expected) unless check_already_queued
21
+ end
22
+ actual.changes[type].include? expected
23
+ end
18
24
 
19
- def add_changes(type, changes)
20
- @changes[type] += relative_path(changes)
21
- @changes[type].uniq!
22
- @changes[type].sort!
25
+ failure_message do |actual|
26
+ result = actual.changes.inspect
27
+ "expected #{result} to include #{description} of #{expected}"
28
+ end
29
+
30
+ failure_message_when_negated do |actual|
31
+ result = actual.changes.inspect
32
+ "expected #{result} to not include #{description} of #{expected}"
33
+ end
34
+ end
23
35
  end
24
36
 
25
- def relative_path(changes)
26
- changes.map do |change|
27
- unfrozen_copy = change.dup
28
- [paths].flatten.each { |path| unfrozen_copy.gsub!(/#{path.to_s}\//, '') }
29
- unfrozen_copy
37
+ def change_fs(type, path)
38
+ case type
39
+ when :modified
40
+ unless File.exist?(path)
41
+ fail "Bad test: cannot modify #{path.inspect} (it doesn't exist)"
42
+ end
43
+
44
+ # wait until full second, because this might be followed by a modification
45
+ # event (which otherwise may not be detected every time)
46
+ _sleep_until_next_second(Pathname.pwd)
47
+
48
+ open(path, 'a') { |f| f.write('foo') }
49
+
50
+ # separate it from upcoming modifications"
51
+ _sleep_to_separate_events
52
+ when :added
53
+ if File.exist?(path)
54
+ fail "Bad test: cannot add #{path.inspect} (it already exists)"
55
+ end
56
+
57
+ # wait until full second, because this might be followed by a modification
58
+ # event (which otherwise may not be detected every time)
59
+ _sleep_until_next_second(Pathname.pwd)
60
+
61
+ open(path, 'w') { |f| f.write('foo') }
62
+
63
+ # separate it from upcoming modifications"
64
+ _sleep_to_separate_events
65
+ when :removed
66
+ unless File.exist?(path)
67
+ fail "Bad test: cannot remove #{path.inspect} (it doesn't exist)"
68
+ end
69
+ File.unlink(path)
70
+ else
71
+ fail "bad test: unknown type: #{type.inspect}"
30
72
  end
31
73
  end
32
74
 
33
- # Generates a small time difference before performing a time sensitive
34
- # task (like comparing mtimes of files).
75
+ # Used by change_fs() above so that the FS change (e.g. file created) happens
76
+ # as close to the start of a new second (time) as possible.
35
77
  #
36
- # @note Modification time for files only includes the milliseconds on Linux
37
- # with MRI > 1.9.2 and platform that support it (OS X 10.8 not included),
38
- # that's why we generate a difference that's greater than 1 second.
78
+ # E.g. if file is created at 1234567.999 (unix time), it's mtime on some
79
+ # filesystems is rounded, so it becomes 1234567.0, but if the change
80
+ # notification happens a little while later, e.g. at 1234568.111, now the file
81
+ # mtime and the current time in seconds are different (1234567 vs 1234568), and
82
+ # so the MD5 test won't kick in (see file.rb) - the file will not be considered
83
+ # for content checking (md5), so File.change will consider the file unmodified.
39
84
  #
40
- def sleep_until_next_second
41
- return unless darwin?
85
+ # This means, that if a file is added at 1234567.888 (and updated in Record),
86
+ # and then its content is modified at 1234567.999, and checking for changes
87
+ # happens at 1234568.111, the modification won't be detected.
88
+ # (because Record mtime is 1234567.0, current FS mtime from stat() is the
89
+ # same, and the checking happens in another second - 1234568).
90
+ #
91
+ # So basically, adding a file and detecting its later modification should all
92
+ # happen within 1 second (which makes testing and debugging difficult).
93
+ #
94
+ def _sleep_until_next_second(path)
95
+ Listen::File.inaccurate_mac_time?(path)
42
96
 
43
97
  t = Time.now
44
98
  diff = t.to_f - t.to_i
45
99
 
46
100
  sleep(1.05 - diff)
47
101
  end
102
+
103
+ # Special class to only allow changes within a specific time window
104
+
105
+ class TimedChanges
106
+ attr_reader :changes
107
+
108
+ def initialize
109
+ # Set to non-nil, because changes can immediately come after unpausing
110
+ # listener in an Rspec 'before()' block
111
+ @changes = { modified: [], added: [], removed: [] }
112
+ end
113
+
114
+ def change_offset
115
+ Time.now.to_f - @yield_time
116
+ end
117
+
118
+ def freeze_offset
119
+ result = @freeze_time - @yield_time
120
+ # Make an "almost zero" value more readable
121
+ result < 1e-4 ? 1e-4 : result
122
+ end
123
+
124
+ # Allow changes only during specific time wine
125
+ def allow_changes(reset_queue = true)
126
+ @freeze_time = nil
127
+ if reset_queue
128
+ # Clear to prepare for collecting new FS events
129
+ @changes = { modified: [], added: [], removed: [] }
130
+ else
131
+ # Since we're testing the queue and the listener callback is adding
132
+ # changes to the same hash (e.g. after a pause), copy the existing data
133
+ # to a new, unfrozen hash
134
+ @changes = @changes.dup if @changes.frozen?
135
+ @changes ||= { modified: [], added: [], removed: [] }
136
+ end
137
+
138
+ @yield_time = Time.now.to_f
139
+ yield
140
+ # Prevent recording changes after timeout
141
+ @changes.freeze
142
+ @freeze_time = Time.now.to_f
143
+ end
144
+ end
145
+
146
+ # Conveniently wrap a Listener instance for testing
147
+ class ListenerWrapper
148
+ attr_reader :listener, :changes
149
+ attr_accessor :lag
150
+
151
+ def initialize(callback, paths, *args)
152
+ # Lag depends mostly on wait_for_delay On Linux desktop, it's 0.06 - 0.11
153
+ #
154
+ # On Travis it used to be > 0.5, but that was before broadcaster sent
155
+ # changes immediately, so 0.2-0.4 might be enough for Travis, but we set it
156
+ # to 0.6
157
+ #
158
+ # The value should be 2-3 x wait_for_delay + time between fs operation and
159
+ # notification, which for polling and FSEvent means the configured latency
160
+ @lag = 0.6
161
+
162
+ @paths = paths
163
+
164
+ # Isolate collected changes between tests/listener instances
165
+ @timed_changes = TimedChanges.new
166
+
167
+ if callback
168
+ @listener = Listen.send(*args) do |modified, added, removed|
169
+ # Add changes to trigger frozen Hash error, making sure lag is enough
170
+ _add_changes(:modified, modified, changes)
171
+ _add_changes(:added, added, changes)
172
+ _add_changes(:removed, removed, changes)
173
+
174
+ unless callback == :track_changes
175
+ callback.call(modified, added, removed)
176
+ end
177
+ end
178
+ else
179
+ @listener = Listen.send(*args)
180
+ end
181
+ end
182
+
183
+ def changes
184
+ @timed_changes.changes
185
+ end
186
+
187
+ def listen(reset_queue = true)
188
+ @timed_changes.allow_changes(reset_queue) do
189
+
190
+ # give events time to be received, queued and processed
191
+ sleep lag
192
+
193
+ yield
194
+
195
+ sleep lag # wait for changes
196
+ end
197
+
198
+ # Keep this to detect a lag too small (changes during this sleep
199
+ # will trigger "frozen hash" error caught below (and displaying timeout
200
+ # details)
201
+ sleep 1
202
+
203
+ changes
204
+ end
205
+
206
+ private
207
+
208
+ def _add_changes(type, changes, dst)
209
+ dst[type] += _relative_path(changes)
210
+ dst[type].uniq!
211
+ dst[type].sort!
212
+
213
+ rescue RuntimeError => e
214
+ raise unless e.message == "can't modify frozen Hash"
215
+
216
+ # Show how by much the changes missed the timeout
217
+ change_offset = @timed_changes.change_offset
218
+ freeze_offset = @timed_changes.freeze_offset
219
+
220
+ msg = "Changes took #{change_offset}s (allowed lag: #{freeze_offset})s"
221
+
222
+ # Use STDERR (workaround for Celluloid, since it catches abort)
223
+ STDERR.puts msg
224
+ abort(msg)
225
+ end
226
+
227
+ def _relative_path(changes)
228
+ changes.map do |change|
229
+ unfrozen_copy = change.dup
230
+ [@paths].flatten.each do |path|
231
+ sub = path.sub(/\/$/, '').to_s
232
+ unfrozen_copy.gsub!(/^#{sub}\//, '')
233
+ end
234
+ unfrozen_copy
235
+ end
236
+ end
237
+ end
238
+
239
+ def setup_listener(options, callback = nil)
240
+ ListenerWrapper.new(callback, paths, :to, paths, options)
241
+ end
242
+
243
+ def setup_recipient(port, callback = nil)
244
+ ListenerWrapper.new(callback, paths, :on, port)
245
+ end
246
+
247
+ def _sleep_to_separate_events
248
+ # separate the events or Darwin and Polling
249
+ # will detect only the :added event
250
+ #
251
+ # (This is because both use directory scanning
252
+ # through Celluloid tasks, which may not kick in
253
+ # time before the next filesystem change)
254
+ #
255
+ # The minimum for this is the time it takes between a syscall
256
+ # changing the filesystem ... and ... an async
257
+ # Listen::File.scan to finish comparing the file with the
258
+ # Record
259
+ #
260
+ # This necessary for:
261
+ # - Darwin Adapter
262
+ # - Polling Adapter
263
+ # - Linux Adapter in FSEvent emulation mode
264
+ # - maybe Windows adapter (probably not)
265
+ sleep 0.4
266
+ end