listen 2.7.2 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2b82792fadddb794945b7796d42a8c35d3dabfc
4
- data.tar.gz: e5f725bb1c8446f51fd54285c0c48f26d0740a67
3
+ metadata.gz: 28cb6340a990a1236af6107a77e1d2315f4b0a6d
4
+ data.tar.gz: 7c84f25f243f6c6dbabfa2744484896a9e5b3f8d
5
5
  SHA512:
6
- metadata.gz: 7441734e63b119c9139b4272c512a7bd34a6ab7e507dfdf99c7483a70108959fafc96c1e0b489dafe9648b41f77b58141efbb3c91c14889291590d03298319d3
7
- data.tar.gz: 0d00f5195e985526a4697dda89d1e1015f2b39f0e2c4c9b776a91f92ecfae13f8ce4e207b0f17ee80bd5e024c3ff75d5b816a6877324d9c6d6f96d8aa9590e6b
6
+ metadata.gz: 86e66ba1d8d19640984a28c0d5aeeea4fd7cfa06fb35f7815b83e3396882b092cb8ae65ddc8af53eae2f5858810aadea128f4fea95dc92357cc98142b83f6be1
7
+ data.tar.gz: d6c2c1afe48406ef8db1b15f4d5f52741318728f77a07c7acbf70fdc0b348efb10db1760846edb73e052cb17c32a86c2be537eee779f478640b7571c86371d54
data/README.md CHANGED
@@ -148,6 +148,9 @@ debug: true # Enable Celluloid logger
148
148
  # default: false
149
149
  ```
150
150
 
151
+ Also, setting the environment variable `LISTEN_GEM_DEBUGGING=1` does the same as `debug: true` above.
152
+
153
+
151
154
  ## Listen adapters
152
155
 
153
156
  The Listen gem has a set of adapters to notify it when there are changes.
@@ -200,6 +203,8 @@ Here are some things you could try to avoid forcing polling.
200
203
 
201
204
  If your application keeps using the polling-adapter and you can't figure out why, feel free to [open an issue](https://github.com/guard/listen/issues/new) (and be sure to [give all the details](https://github.com/guard/listen/blob/master/CONTRIBUTING.md)).
202
205
 
206
+ Also, if you have problems related to receiving the wrong events, too many events or none at all, be sure set the environment variable `LISTEN_GEM_DEBUGGING=1` and include the output when reporting a new issue.
207
+
203
208
  ## Forwarding file events over TCP
204
209
 
205
210
  Listen is capable of forwarding file events over the network using a messaging protocol. This can be useful for virtualized development environments when file events are unavailable, as is the case with [Vagrant](https://github.com/mitchellh/vagrant).
@@ -55,10 +55,15 @@ module Listen
55
55
  lambda do |event|
56
56
  next if _skip_event?(event)
57
57
 
58
+ path = _event_path(event)
59
+ cookie_opts = event.cookie.zero? ? {} : { cookie: event.cookie }
60
+
61
+ Celluloid.logger.info "listen: inotify event: #{event.flags.inspect}: #{event.name}"
62
+
58
63
  if _dir_event?(event)
59
- _notify_change(_event_path(event), type: 'Dir')
64
+ _notify_change(path, { type: 'Dir'}.merge(cookie_opts))
60
65
  else
61
- _notify_change(_event_path(event), type: 'File', change: _change(event.flags))
66
+ _notify_change(path, { type: 'File', change: _change(event.flags)}.merge(cookie_opts))
62
67
  end
63
68
  end
64
69
  end
@@ -74,9 +79,11 @@ module Listen
74
79
  end
75
80
 
76
81
  def _change(event_flags)
77
- { modified: [:attrib],
78
- added: [:moved_to, :create],
79
- removed: [:moved_from, :delete] }.each do |change, flags|
82
+ { modified: [:attrib, :close_write],
83
+ moved_to: [:moved_to],
84
+ moved_from: [:moved_from],
85
+ added: [:create],
86
+ removed: [:delete] }.each do |change, flags|
80
87
  return change unless (flags & event_flags).empty?
81
88
  end
82
89
  nil
data/lib/listen/change.rb CHANGED
@@ -12,10 +12,16 @@ module Listen
12
12
  end
13
13
 
14
14
  def change(path, options)
15
- return if _silencer.silenced?(path, options[:type])
15
+ change = options[:change]
16
+ cookie = options[:cookie]
16
17
 
17
- if change = options[:change]
18
- _notify_listener(change, path)
18
+ unless cookie
19
+ #TODO: remove silencing here (it's done later)
20
+ return if _silencer.silenced?(path, options[:type])
21
+ end
22
+
23
+ if change
24
+ _notify_listener(change, path, cookie ? { cookie: cookie } : {})
19
25
  else
20
26
  send("_#{options[:type].downcase}_change", path, options)
21
27
  end
@@ -34,8 +40,8 @@ module Listen
34
40
  Directory.new(listener, path, options).scan
35
41
  end
36
42
 
37
- def _notify_listener(change, path)
38
- listener.changes << { change => path }
43
+ def _notify_listener(change, path, options = {})
44
+ listener.changes << { change => path }.merge(options)
39
45
  end
40
46
 
41
47
  def _silencer
@@ -117,10 +117,10 @@ module Listen
117
117
  end
118
118
 
119
119
  def _init_debug
120
- if options[:debug]
120
+ if options[:debug] || ENV['LISTEN_GEM_DEBUGGING'] =~ /true|1/i
121
121
  Celluloid.logger.level = Logger::INFO
122
122
  else
123
- Celluloid.logger = nil
123
+ Celluloid.logger.level = Logger::FATAL
124
124
  end
125
125
  end
126
126
 
@@ -156,15 +156,97 @@ module Listen
156
156
 
157
157
  def _pop_changes
158
158
  popped = []
159
- popped << @changes.pop until @changes.empty?
159
+ popped << @changes.shift until @changes.empty?
160
160
  popped
161
161
  end
162
162
 
163
163
  def _smoosh_changes(changes)
164
- smooshed = { modified: [], added: [], removed: [] }
165
- changes.each { |h| type = h.keys.first; smooshed[type] << h[type].to_s }
166
- smooshed.each { |_, v| v.uniq! }
167
- smooshed
164
+ if _local_fs?
165
+ cookies = changes.group_by { |x| x[:cookie] }
166
+ _squash_changes(_reinterpret_related_changes(cookies))
167
+ else
168
+ smooshed = { modified: [], added: [], removed: [] }
169
+ changes.each { |h| type = h.keys.first; smooshed[type] << h[type].to_s }
170
+ smooshed.each { |_, v| v.uniq! }
171
+ smooshed
172
+ end
173
+ end
174
+
175
+ def _local_fs?
176
+ !registry[:adapter].is_a?(Adapter::TCP)
177
+ end
178
+
179
+ def _squash_changes(changes)
180
+ actions = changes.group_by(&:last).map do |path, action_list|
181
+ [_logical_action_for(path, action_list.map(&:first)), path.to_s]
182
+ end
183
+ Celluloid.logger.info "listen: raw changes: #{actions.inspect}"
184
+
185
+ { modified: [], added: [], removed: [] }.tap do |squashed|
186
+ actions.each do |type, path|
187
+ squashed[type] << path unless type.nil?
188
+ end
189
+ Celluloid.logger.info "listen: final changes: #{squashed.inspect}"
190
+ end
191
+ end
192
+
193
+ def _logical_action_for(path, actions)
194
+ actions << :added if actions.delete(:moved_to)
195
+ actions << :removed if actions.delete(:moved_from)
196
+
197
+ modified = actions.find { |x| x == :modified }
198
+ _calculate_add_remove_difference(actions, path, modified)
199
+ end
200
+
201
+ def _calculate_add_remove_difference(actions, path, default_if_exists)
202
+ added = actions.count { |x| x == :added }
203
+ removed = actions.count { |x| x == :removed }
204
+ diff = added - removed
205
+
206
+ if path.exist?
207
+ if diff > 0
208
+ :added
209
+ elsif diff.zero? && added > 0
210
+ :modified
211
+ else
212
+ default_if_exists
213
+ end
214
+ else
215
+ diff < 0 ? :removed : nil
216
+ end
217
+ end
218
+
219
+ # remove extraneous rb-inotify events, keeping them only if it's a possible
220
+ # editor rename() call (e.g. Kate and Sublime)
221
+ def _reinterpret_related_changes(cookies)
222
+ table = { moved_to: :added, moved_from: :removed }
223
+ cookies.map do |cookie, changes|
224
+ file = _detect_possible_editor_save(changes)
225
+ if file
226
+ [[:modified, file]]
227
+ else
228
+ changes.map(&:first).reject do |type, path|
229
+ _silenced?(path)
230
+ end.map { |type, path| [table.fetch(type, type), path] }
231
+ end
232
+ end.flatten(1)
233
+ end
234
+
235
+ def _detect_possible_editor_save(changes)
236
+ return unless changes.size == 2
237
+
238
+ from, to = changes.sort { |x,y| x.keys.first <=> y.keys.first }
239
+ from, to = from[:moved_from], to[:moved_to]
240
+ return unless from and to
241
+
242
+ # Expect an ignored moved_from and non-ignored moved_to
243
+ # to qualify as an "editor modify"
244
+ _silenced?(from) && !_silenced?(to) ? to : nil
245
+ end
246
+
247
+ def _silenced?(path)
248
+ type = path.directory? ? 'Dir' : 'File'
249
+ registry[:silencer].silenced?(path, type)
168
250
  end
169
251
  end
170
252
  end
@@ -1,3 +1,3 @@
1
1
  module Listen
2
- VERSION = '2.7.2'
2
+ VERSION = '2.7.3'
3
3
  end
@@ -20,7 +20,7 @@ describe Listen::Adapter::Linux do
20
20
 
21
21
  # workaround: Celluloid ignores SystemExit exception messages
22
22
  describe "inotify limit message" do
23
- let(:adapter) { described_class.new(listener) }
23
+ let!(:adapter) { described_class.new(listener) }
24
24
  let(:expected_message) { described_class.const_get('INOTIFY_LIMIT_MESSAGE') }
25
25
 
26
26
  before do
@@ -37,6 +37,40 @@ describe Listen::Adapter::Linux do
37
37
  expect{adapter.start}.to raise_error RuntimeError, expected_message
38
38
  end
39
39
  end
40
+
41
+ describe '_worker_callback' do
42
+
43
+ let(:expect_change) {
44
+ ->(change) {
45
+ allow_any_instance_of(Listen::Adapter::Base).to receive(:_notify_change).with(Pathname.new('path/foo.txt'), type: 'File', change: change, cookie: 123)
46
+ }
47
+ }
48
+
49
+ let(:event_callback) {
50
+ ->(flags) {
51
+ callback = adapter.send(:_worker_callback)
52
+ callback.call double(:event, name: 'foo.txt', flags: flags, absolute_name: 'path/foo.txt', cookie: 123)
53
+ }
54
+ }
55
+
56
+ # use case: close_write is the only way to detect changes
57
+ # on ecryptfs
58
+ it 'recognizes close_write as modify' do
59
+ expect_change.(:modified)
60
+ event_callback.([:close_write])
61
+ end
62
+
63
+ it 'recognizes moved_to as moved_to' do
64
+ expect_change.(:moved_to)
65
+ event_callback.([:moved_to])
66
+ end
67
+
68
+ it 'recognizes moved_from as moved_from' do
69
+ expect_change.(:moved_from)
70
+ event_callback.([:moved_from])
71
+ end
72
+ end
73
+
40
74
  end
41
75
 
42
76
  if darwin?
@@ -16,8 +16,8 @@ describe Listen::Change do
16
16
  context "file path" do
17
17
  context "with known change" do
18
18
  it "notifies change directly to listener" do
19
- expect(listener_changes).to receive(:<<).with(modified: 'file_path')
20
- change.change('file_path', type: 'File', change: :modified)
19
+ expect(listener_changes).to receive(:<<).with(modified: Pathname.new('file_path'))
20
+ change.change(Pathname.new('file_path'), type: 'File', change: :modified)
21
21
  end
22
22
 
23
23
  it "doesn't notify to listener if path is silenced" do
@@ -25,7 +25,7 @@ describe Listen::Change do
25
25
  expect(silencer).to receive(:silenced?).and_return(true)
26
26
  expect(listener_changes).to_not receive(:<<)
27
27
 
28
- change.change('file_path', type: 'File', change: :modified)
28
+ change.change(Pathname.new('file_path'), type: 'File', change: :modified)
29
29
  end
30
30
  end
31
31
 
@@ -34,16 +34,16 @@ describe Listen::Change do
34
34
  before { Listen::File.stub(:new) { file } }
35
35
 
36
36
  it "calls Listen::File#change" do
37
- expect(Listen::File).to receive(:new).with(listener, 'file_path') { file }
37
+ expect(Listen::File).to receive(:new).with(listener, Pathname.new('file_path')) { file }
38
38
  expect(file).to receive(:change)
39
- change.change('file_path', type: 'File')
39
+ change.change(Pathname.new('file_path'), type: 'File')
40
40
  end
41
41
 
42
42
  it "doesn't call Listen::File#change if path is silenced" do
43
- expect(silencer).to receive(:silenced?).with('file_path', 'File').and_return(true)
43
+ expect(silencer).to receive(:silenced?).with(Pathname.new('file_path'), 'File').and_return(true)
44
44
  expect(Listen::File).to_not receive(:new)
45
45
 
46
- change.change('file_path', type: 'File')
46
+ change.change(Pathname.new('file_path'), type: 'File')
47
47
  end
48
48
 
49
49
  context "that returns a change" do
@@ -53,14 +53,15 @@ describe Listen::Change do
53
53
  before { listener.stub(:listen?) { true } }
54
54
 
55
55
  it "notifies change to listener" do
56
- expect(listener_changes).to receive(:<<).with(modified: 'file_path')
57
- change.change('file_path', type: 'File')
56
+ file_path = double(Pathname, to_s: 'file_path', exist?: true)
57
+ expect(listener_changes).to receive(:<<).with(modified: file_path)
58
+ change.change(file_path, type: 'File')
58
59
  end
59
60
 
60
61
  context "silence option" do
61
62
  it "notifies change to listener" do
62
63
  expect(listener_changes).to_not receive(:<<)
63
- change.change('file_path', type: 'File', silence: true)
64
+ change.change(Pathname.new('file_path'), type: 'File', silence: true)
64
65
  end
65
66
  end
66
67
  end
@@ -70,7 +71,7 @@ describe Listen::Change do
70
71
 
71
72
  it "notifies change to listener" do
72
73
  expect(listener_changes).to_not receive(:<<)
73
- change.change('file_path', type: 'File')
74
+ change.change(Pathname.new('file_path'), type: 'File')
74
75
  end
75
76
  end
76
77
  end
@@ -80,7 +81,7 @@ describe Listen::Change do
80
81
 
81
82
  it "doesn't notifies no change" do
82
83
  expect(listener_changes).to_not receive(:<<)
83
- change.change('file_path', type: 'File')
84
+ change.change(Pathname.new('file_path'), type: 'File')
84
85
  end
85
86
  end
86
87
  end
@@ -92,9 +93,9 @@ describe Listen::Change do
92
93
  before { Listen::Directory.stub(:new) { dir } }
93
94
 
94
95
  it "calls Listen::Directory#scan" do
95
- expect(Listen::Directory).to receive(:new).with(listener, 'dir_path', dir_options) { dir }
96
+ expect(Listen::Directory).to receive(:new).with(listener, Pathname.new('dir_path'), dir_options) { dir }
96
97
  expect(dir).to receive(:scan)
97
- change.change('dir_path', dir_options)
98
+ change.change(Pathname.new('dir_path'), dir_options)
98
99
  end
99
100
  end
100
101
  end
@@ -61,6 +61,7 @@ describe Listen::Listener do
61
61
  describe "#start" do
62
62
  before {
63
63
  adapter.stub_chain(:async, :start)
64
+ silencer.stub(:silenced?) { false }
64
65
  }
65
66
 
66
67
  it "registers silencer" do
@@ -109,7 +110,8 @@ describe Listen::Listener do
109
110
  end
110
111
 
111
112
  it "calls block on changes" do
112
- listener.changes = [{ modified: 'foo' }]
113
+ foo = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
114
+ listener.changes = [{ modified: foo }]
113
115
  block_stub = double('block')
114
116
  listener.block = block_stub
115
117
  expect(block_stub).to receive(:call).with(['foo'], [], [])
@@ -270,6 +272,7 @@ describe Listen::Listener do
270
272
 
271
273
  describe '_wait_for_changes' do
272
274
  it 'gets two changes and calls the block once' do
275
+ silencer.stub(:silenced?) { false }
273
276
 
274
277
  fake_time = 0
275
278
  listener.stub(:sleep) { |sec| fake_time += sec; listener.stopping = true if fake_time > 1 }
@@ -279,6 +282,9 @@ describe Listen::Listener do
279
282
  expect(added).to eql(['bar.txt'])
280
283
  }
281
284
 
285
+ foo = double(Pathname, to_s: 'foo.txt', exist?: true, directory?: false)
286
+ bar = double(Pathname, to_s: 'bar.txt', exist?: true, directory?: false)
287
+
282
288
  i = 0
283
289
  listener.stub(:_pop_changes) do
284
290
  i+=1
@@ -286,9 +292,9 @@ describe Listen::Listener do
286
292
  when 1
287
293
  []
288
294
  when 2
289
- [{modified: 'foo.txt'}]
295
+ [{modified: foo}]
290
296
  when 3
291
- [{added: 'bar.txt'}]
297
+ [{added: bar}]
292
298
  else
293
299
  []
294
300
  end
@@ -298,4 +304,95 @@ describe Listen::Listener do
298
304
  end
299
305
  end
300
306
 
307
+ describe '_smoosh_changes' do
308
+ it 'recognizes rename from temp file' do
309
+ path = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
310
+ changes = [
311
+ { modified: path },
312
+ { removed: path },
313
+ { added: path },
314
+ { modified: path }
315
+ ]
316
+ silencer.stub(:silenced?) { false }
317
+ smooshed = listener.send :_smoosh_changes, changes
318
+ expect(smooshed).to eq(modified: ['foo'], added: [], removed: [])
319
+ end
320
+
321
+ it 'recognizes deleted temp file' do
322
+ path = double(Pathname, to_s: 'foo', exist?: false, directory?: false)
323
+ changes = [
324
+ { added: path },
325
+ { modified: path },
326
+ { removed: path },
327
+ { modified: path }
328
+ ]
329
+ silencer.stub(:silenced?) { false }
330
+ smooshed = listener.send :_smoosh_changes, changes
331
+ expect(smooshed).to eq(modified: [], added: [], removed: [])
332
+ end
333
+
334
+ it 'recognizes double move as modification' do
335
+ # e.g. "mv foo x && mv x foo" is like "touch foo"
336
+ path = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
337
+ changes = [
338
+ { removed: path },
339
+ { added: path }
340
+ ]
341
+ silencer.stub(:silenced?) { false }
342
+ smooshed = listener.send :_smoosh_changes, changes
343
+ expect(smooshed).to eq(modified: ['foo'], added: [], removed: [])
344
+ end
345
+
346
+ context "with cookie" do
347
+
348
+ it 'recognizes single moved_to as add' do
349
+ foo = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
350
+ changes = [
351
+ { moved_to: foo , cookie: 4321 },
352
+ ]
353
+ expect(silencer).to receive(:silenced?).with(foo, 'File').and_return(false)
354
+ smooshed = listener.send :_smoosh_changes, changes
355
+ expect(smooshed).to eq(modified: [], added: ['foo'], removed: [])
356
+ end
357
+
358
+ it 'recognizes related moved_to as add' do
359
+ foo = double(:foo, to_s: 'foo', exist?: true, directory?: false)
360
+ bar = double(:bar, to_s: 'bar', exist?: true, directory?: false)
361
+ changes = [
362
+ { moved_from: foo , cookie: 4321 },
363
+ { moved_to: bar, cookie: 4321 },
364
+ ]
365
+ expect(silencer).to receive(:silenced?).twice.with(foo, 'File').and_return(false)
366
+ expect(silencer).to receive(:silenced?).with(bar, 'File').and_return(false)
367
+ smooshed = listener.send :_smoosh_changes, changes
368
+ expect(smooshed).to eq(modified: [], added: ['bar'], removed: [])
369
+ end
370
+
371
+ # Scenario with workaround for editors using rename()
372
+ it 'recognizes related moved_to with ignored moved_from as modify' do
373
+ ignored = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
374
+ foo = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
375
+ changes = [
376
+ { moved_from: ignored, cookie: 4321 },
377
+ { moved_to: foo , cookie: 4321 },
378
+ ]
379
+ expect(silencer).to receive(:silenced?).with(ignored, 'File').and_return(true)
380
+ expect(silencer).to receive(:silenced?).with(foo, 'File').and_return(false)
381
+ smooshed = listener.send :_smoosh_changes, changes
382
+ expect(smooshed).to eq(modified: ['foo'], added: [], removed: [])
383
+ end
384
+ end
385
+
386
+ context "with no cookie" do
387
+ it 'recognizes properly ignores files' do
388
+ ignored = double(Pathname, to_s: 'foo', exist?: true, directory?: false)
389
+ changes = [
390
+ { modified: ignored },
391
+ ]
392
+ expect(silencer).to receive(:silenced?).with(ignored, 'File') { true }
393
+ smooshed = listener.send :_smoosh_changes, changes
394
+ expect(smooshed).to eq(modified: [], added: [], removed: [])
395
+ end
396
+ end
397
+ end
301
398
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: listen
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.2
4
+ version: 2.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thibaud Guillaume-Gentil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-25 00:00:00.000000000 Z
11
+ date: 2014-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: celluloid