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.
- checksums.yaml +4 -4
- data/.gitignore +0 -0
- data/.rspec +0 -0
- data/.rubocop.yml +0 -0
- data/.travis.yml +0 -1
- data/.yardopts +0 -0
- data/CHANGELOG.md +0 -0
- data/CONTRIBUTING.md +0 -0
- data/Gemfile +25 -4
- data/Guardfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +18 -10
- data/Rakefile +0 -0
- data/lib/listen.rb +2 -4
- data/lib/listen/adapter.rb +13 -4
- data/lib/listen/adapter/base.rb +33 -16
- data/lib/listen/adapter/bsd.rb +21 -38
- data/lib/listen/adapter/darwin.rb +17 -25
- data/lib/listen/adapter/linux.rb +34 -52
- data/lib/listen/adapter/polling.rb +9 -25
- data/lib/listen/adapter/tcp.rb +27 -14
- data/lib/listen/adapter/windows.rb +67 -23
- data/lib/listen/change.rb +26 -23
- data/lib/listen/cli.rb +0 -0
- data/lib/listen/directory.rb +47 -58
- data/lib/listen/file.rb +66 -101
- data/lib/listen/listener.rb +214 -155
- data/lib/listen/queue_optimizer.rb +104 -0
- data/lib/listen/record.rb +15 -5
- data/lib/listen/silencer.rb +14 -10
- data/lib/listen/tcp.rb +0 -1
- data/lib/listen/tcp/broadcaster.rb +31 -26
- data/lib/listen/tcp/message.rb +2 -2
- data/lib/listen/version.rb +1 -1
- data/listen.gemspec +1 -1
- data/spec/acceptance/listen_spec.rb +151 -239
- data/spec/acceptance/tcp_spec.rb +125 -134
- data/spec/lib/listen/adapter/base_spec.rb +13 -30
- data/spec/lib/listen/adapter/bsd_spec.rb +7 -35
- data/spec/lib/listen/adapter/darwin_spec.rb +18 -30
- data/spec/lib/listen/adapter/linux_spec.rb +49 -55
- data/spec/lib/listen/adapter/polling_spec.rb +20 -35
- data/spec/lib/listen/adapter/tcp_spec.rb +25 -27
- data/spec/lib/listen/adapter/windows_spec.rb +7 -33
- data/spec/lib/listen/adapter_spec.rb +10 -10
- data/spec/lib/listen/change_spec.rb +55 -57
- data/spec/lib/listen/directory_spec.rb +105 -155
- data/spec/lib/listen/file_spec.rb +186 -73
- data/spec/lib/listen/listener_spec.rb +233 -216
- data/spec/lib/listen/record_spec.rb +60 -22
- data/spec/lib/listen/silencer_spec.rb +48 -75
- data/spec/lib/listen/tcp/broadcaster_spec.rb +78 -69
- data/spec/lib/listen/tcp/listener_spec.rb +28 -71
- data/spec/lib/listen/tcp/message_spec.rb +48 -14
- data/spec/lib/listen_spec.rb +3 -3
- data/spec/spec_helper.rb +6 -3
- data/spec/support/acceptance_helper.rb +250 -31
- data/spec/support/fixtures_helper.rb +6 -4
- data/spec/support/platform_helper.rb +2 -2
- metadata +5 -5
- data/lib/listen/tcp/listener.rb +0 -108
@@ -1,45 +1,39 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
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) {
|
13
|
+
let(:registry) { instance_double(Celluloid::Registry, :[]= => true) }
|
11
14
|
|
12
15
|
let(:supervisor) do
|
13
|
-
|
16
|
+
instance_double(Celluloid::SupervisionGroup, add: true, pool: true)
|
14
17
|
end
|
15
18
|
|
16
|
-
let(:record) {
|
17
|
-
let(:silencer) {
|
18
|
-
let(:adapter) {
|
19
|
-
let(:
|
20
|
-
let(:
|
21
|
-
let(:
|
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.
|
24
|
-
Celluloid::SupervisionGroup.
|
25
|
-
registry.
|
26
|
-
registry.
|
27
|
-
registry.
|
28
|
-
registry.
|
29
|
-
registry.
|
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
|
-
|
67
|
-
broadcaster.
|
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 '
|
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.
|
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
|
-
|
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
|
87
|
+
message = Listen::TCP::Message.new(:file, :modified, 'foo', {})
|
119
88
|
expect(async).to receive(:broadcast).with message.payload
|
120
|
-
|
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(
|
15
|
-
expect(message.object).to
|
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
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
data/spec/lib/listen_spec.rb
CHANGED
@@ -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::
|
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
|
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::
|
44
|
+
expect(Listen::Listener).to receive(:new).
|
45
45
|
with(4000, :recipient, '/path')
|
46
46
|
described_class.on(4000, '/path')
|
47
47
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
#
|
34
|
-
#
|
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
|
-
#
|
37
|
-
#
|
38
|
-
#
|
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
|
-
|
41
|
-
|
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
|