listen 2.7.2 → 2.7.3

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 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