listen 2.7.6 → 2.7.7

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 -0
  6. data/.yardopts +0 -0
  7. data/CHANGELOG.md +0 -0
  8. data/CONTRIBUTING.md +0 -0
  9. data/Gemfile +2 -0
  10. data/Guardfile +2 -0
  11. data/LICENSE.txt +0 -0
  12. data/README.md +0 -0
  13. data/Rakefile +0 -0
  14. data/lib/listen.rb +0 -0
  15. data/lib/listen/adapter.rb +0 -0
  16. data/lib/listen/adapter/base.rb +47 -21
  17. data/lib/listen/adapter/bsd.rb +31 -25
  18. data/lib/listen/adapter/darwin.rb +13 -12
  19. data/lib/listen/adapter/linux.rb +45 -36
  20. data/lib/listen/adapter/polling.rb +12 -7
  21. data/lib/listen/adapter/tcp.rb +9 -4
  22. data/lib/listen/adapter/windows.rb +46 -58
  23. data/lib/listen/change.rb +12 -8
  24. data/lib/listen/cli.rb +0 -0
  25. data/lib/listen/directory.rb +30 -22
  26. data/lib/listen/file.rb +9 -8
  27. data/lib/listen/listener.rb +35 -12
  28. data/lib/listen/options.rb +23 -0
  29. data/lib/listen/queue_optimizer.rb +23 -13
  30. data/lib/listen/record.rb +98 -21
  31. data/lib/listen/silencer.rb +21 -40
  32. data/lib/listen/tcp.rb +0 -0
  33. data/lib/listen/tcp/broadcaster.rb +0 -0
  34. data/lib/listen/tcp/message.rb +0 -0
  35. data/lib/listen/version.rb +1 -1
  36. data/listen.gemspec +0 -0
  37. data/spec/acceptance/listen_spec.rb +0 -0
  38. data/spec/acceptance/tcp_spec.rb +0 -0
  39. data/spec/lib/listen/adapter/base_spec.rb +17 -16
  40. data/spec/lib/listen/adapter/bsd_spec.rb +0 -0
  41. data/spec/lib/listen/adapter/darwin_spec.rb +11 -4
  42. data/spec/lib/listen/adapter/linux_spec.rb +20 -29
  43. data/spec/lib/listen/adapter/polling_spec.rb +15 -13
  44. data/spec/lib/listen/adapter/tcp_spec.rb +6 -3
  45. data/spec/lib/listen/adapter/windows_spec.rb +0 -0
  46. data/spec/lib/listen/adapter_spec.rb +0 -0
  47. data/spec/lib/listen/change_spec.rb +21 -27
  48. data/spec/lib/listen/directory_spec.rb +60 -42
  49. data/spec/lib/listen/file_spec.rb +16 -20
  50. data/spec/lib/listen/listener_spec.rb +136 -99
  51. data/spec/lib/listen/record_spec.rb +205 -62
  52. data/spec/lib/listen/silencer_spec.rb +44 -114
  53. data/spec/lib/listen/tcp/broadcaster_spec.rb +0 -0
  54. data/spec/lib/listen/tcp/listener_spec.rb +8 -5
  55. data/spec/lib/listen/tcp/message_spec.rb +0 -0
  56. data/spec/lib/listen_spec.rb +0 -0
  57. data/spec/spec_helper.rb +0 -0
  58. data/spec/support/acceptance_helper.rb +15 -4
  59. data/spec/support/fixtures_helper.rb +0 -0
  60. data/spec/support/platform_helper.rb +0 -0
  61. metadata +3 -2
@@ -25,14 +25,21 @@ module Listen
25
25
 
26
26
  private
27
27
 
28
- def _configure
28
+ def _configure(dir, &callback)
29
+ require 'wdm'
29
30
  _log :debug, 'wdm - starting...'
30
- @worker = WDM::Monitor.new
31
- _directories.each do |path|
32
- @worker.watch_recursively(path.to_s, :files, &_file_callback)
33
- @worker.watch_recursively(path.to_s, :directories, &_dir_callback)
34
- @worker.watch_recursively(path.to_s, :attributes, :last_write,
35
- &_attr_callback)
31
+ @worker ||= WDM::Monitor.new
32
+ @worker.watch_recursively(dir.to_s, :files) do |change|
33
+ callback.call([:file, change])
34
+ end
35
+
36
+ @worker.watch_recursively(dir.to_s, :directories) do |change|
37
+ callback.call([:dir, change])
38
+ end
39
+
40
+ events = [:attributes, :last_write]
41
+ @worker.watch_recursively(dir.to_s, *events) do |change|
42
+ callback.call([:attr, change])
36
43
  end
37
44
  end
38
45
 
@@ -40,62 +47,43 @@ module Listen
40
47
  @worker.run!
41
48
  end
42
49
 
43
- def _file_callback
44
- lambda do |change|
45
- begin
46
- path = _path(change.path)
47
- _log :debug, "wdm - FILE callback: #{change.inspect}"
48
- options = { change: _change(change.type) }
49
- _notify_change(:file, path, options)
50
- rescue
51
- _log :error, "wdm - callback failed: #{$!}:#{$@.join("\n")}"
52
- raise
53
- end
54
- end
55
- end
50
+ def _process_event(dir, event)
51
+ _log :debug, "wdm - callback: #{event.inspect}"
56
52
 
57
- def _attr_callback
58
- lambda do |change|
59
- begin
60
- path = _path(change.path)
61
- return if path.directory?
53
+ type, change = event
62
54
 
63
- _log :debug, "wdm - ATTR callback: #{change.inspect}"
64
- options = { change: _change(change.type) }
65
- _notify_change(:file, _path(change.path), options)
66
- rescue
67
- _log :error, "wdm - callback failed: #{$!}:#{$@.join("\n")}"
68
- raise
69
- end
70
- end
71
- end
55
+ full_path = Pathname(change.path)
56
+
57
+ rel_path = full_path.relative_path_from(dir).to_s
72
58
 
73
- def _dir_callback
74
- lambda do |change|
75
- begin
76
- path = _path(change.path)
77
- _log :debug, "wdm - DIR callback: #{change.inspect}"
78
- if change.type == :removed
79
- _notify_change(:dir, path.dirname)
80
- elsif change.type == :added
81
- _notify_change(:dir, path)
82
- else
83
- # do nothing - changed directory means either:
84
- # - removed subdirs (handled above)
85
- # - added subdirs (handled above)
86
- # - removed files (handled by _file_callback)
87
- # - added files (handled by _file_callback)
88
- # so what's left?
89
- end
90
- rescue
91
- _log :error, "wdm - callback failed: #{$!}:#{$@.join("\n")}"
92
- raise
59
+ options = { change: _change(change.type) }
60
+
61
+ case type
62
+ when :file
63
+ _queue_change(:file, dir, rel_path, options)
64
+ when :attr
65
+ unless full_path.directory?
66
+ _queue_change(:file, dir, rel_path, options)
67
+ end
68
+ when :dir
69
+ if change.type == :removed
70
+ # TODO: check if watched dir?
71
+ _queue_change(:dir, dir, Pathname(rel_path).dirname.to_s, {})
72
+ elsif change.type == :added
73
+ _queue_change(:dir, dir, rel_path, {})
74
+ else
75
+ # do nothing - changed directory means either:
76
+ # - removed subdirs (handled above)
77
+ # - added subdirs (handled above)
78
+ # - removed files (handled by _file_callback)
79
+ # - added files (handled by _file_callback)
80
+ # so what's left?
93
81
  end
94
82
  end
95
- end
96
-
97
- def _path(path)
98
- Pathname.new(path)
83
+ rescue
84
+ details = event.inspect
85
+ _log :error, "wdm - callback (#{details}): #{$!}:#{$@.join("\n")}"
86
+ raise
99
87
  end
100
88
 
101
89
  def _change(type)
data/lib/listen/change.rb CHANGED
@@ -11,31 +11,35 @@ module Listen
11
11
  @listener = listener
12
12
  end
13
13
 
14
- def change(type, path, options = {})
14
+ def change(type, watched_dir, rel_path, options = {})
15
15
  change = options[:change]
16
16
  cookie = options[:cookie]
17
17
 
18
- if !cookie && listener.silencer.silenced?(path, type)
19
- _log :debug, "(silenced): #{path.inspect}"
18
+ if !cookie && listener.silencer.silenced?(Pathname(rel_path), type)
19
+ _log :debug, "(silenced): #{rel_path.inspect}"
20
20
  return
21
21
  end
22
22
 
23
+ path = watched_dir + rel_path
24
+
23
25
  log_details = options[:silence] && 'recording' || change || 'unknown'
24
26
  _log :debug, "#{log_details}: #{type}:#{path} (#{options.inspect})"
25
27
 
26
28
  if change
27
- listener.queue(type, change, path, cookie ? { cookie: cookie } : {})
29
+ # TODO: move this to Listener to avoid Celluloid overhead
30
+ # from caller
31
+ options = cookie ? { cookie: cookie } : {}
32
+ listener.queue(type, change, watched_dir, rel_path, options)
28
33
  else
29
34
  return unless (record = listener.sync(:record))
30
- record.async.still_building! if options[:build]
31
35
 
32
36
  if type == :dir
33
37
  return unless (change_queue = listener.async(:change_pool))
34
- Directory.scan(change_queue, record, path, options)
38
+ Directory.scan(change_queue, record, watched_dir, rel_path, options)
35
39
  else
36
- change = File.change(record, path)
40
+ change = File.change(record, watched_dir, rel_path)
37
41
  return if !change || options[:silence]
38
- listener.queue(:file, change, path)
42
+ listener.queue(:file, change, watched_dir, rel_path)
39
43
  end
40
44
  end
41
45
  rescue Celluloid::Task::TerminatedError
data/lib/listen/cli.rb CHANGED
File without changes
@@ -2,61 +2,69 @@ require 'set'
2
2
 
3
3
  module Listen
4
4
  class Directory
5
- def self.scan(queue, sync_record, path, options = {})
5
+ def self.scan(queue, sync_record, dir, rel_path, options)
6
6
  return unless (record = sync_record.async)
7
7
 
8
- previous = sync_record.dir_entries(path)
8
+ previous = sync_record.dir_entries(dir, rel_path)
9
9
 
10
- record.set_path(:dir, path)
10
+ record.add_dir(dir, rel_path)
11
+
12
+ # TODO: use children(with_directory: false)
13
+ path = dir + rel_path
11
14
  current = Set.new(path.children)
12
15
 
13
16
  if options[:silence]
14
- _log :debug, "Recording: #{path}: #{options.inspect}"\
17
+ _log :debug, "Recording: #{rel_path}: #{options.inspect}"\
15
18
  " [#{previous.inspect}] -> (#{current.inspect})"
16
19
  else
17
- _log :debug, "Scanning: #{path}: #{options.inspect}"\
20
+ _log :debug, "Scanning: #{rel_path}: #{options.inspect}"\
18
21
  " [#{previous.inspect}] -> (#{current.inspect})"
19
22
  end
20
23
 
21
24
  current.each do |full_path|
22
- if full_path.directory?
23
- _change(queue, :dir, full_path, options)
24
- else
25
- _change(queue, :file, full_path, options)
26
- end
25
+ type = full_path.directory? ? :dir : :file
26
+ item_rel_path = full_path.relative_path_from(dir).to_s
27
+ _change(queue, type, dir, item_rel_path, options)
27
28
  end
28
29
 
30
+ # TODO: this is not tested properly
29
31
  previous.reject! { |entry, _| current.include? path + entry }
30
- _async_changes(path, queue, previous, options)
32
+
33
+ _async_changes(dir, rel_path, queue, previous, options)
31
34
 
32
35
  rescue Errno::ENOENT
33
- record.unset_path(path)
34
- _async_changes(path, queue, previous, options)
36
+ record.unset_path(dir, rel_path)
37
+ _async_changes(dir, rel_path, queue, previous, options)
35
38
 
36
39
  rescue Errno::ENOTDIR
37
40
  # TODO: path not tested
38
- record.unset_path(path)
39
- _async_changes(path, queue, previous, options)
40
- _change(queue, :file, path, options)
41
+ record.unset_path(dir, rel_path)
42
+ _async_changes(dir, path, queue, previous, options)
43
+ _change(queue, :file, dir, rel_path, options)
41
44
  rescue
42
45
  _log :warn, "scanning DIED: #{$!}:#{$@.join("\n")}"
43
46
  raise
44
47
  end
45
48
 
46
- def self._async_changes(path, queue, previous, options)
49
+ def self._async_changes(dir, path, queue, previous, options)
47
50
  previous.each do |entry, data|
48
- _change(queue, data[:type], path + entry, options)
51
+ # TODO: this is a hack with insufficient testing
52
+ type = data.key?(:mtime) ? :file : :dir
53
+ _change(queue, type, dir, (Pathname(path) + entry).to_s, options)
49
54
  end
50
55
  end
51
56
 
52
- def self._change(queue, type, full_path, options)
53
- return queue.change(type, full_path, options) if type == :dir
57
+ def self._change(queue, type, dir, path, options)
58
+ return queue.change(type, dir, path, options) if type == :dir
59
+
60
+ # Minor param cleanup for tests
61
+ # TODO: use a dedicated Event class
54
62
  opts = options.dup
55
63
  opts.delete(:recursive)
56
64
  if opts.empty?
57
- queue.change(type, full_path)
65
+ queue.change(type, dir, path)
58
66
  else
59
- queue.change(type, full_path, opts)
67
+ queue.change(type, dir, path, opts)
60
68
  end
61
69
  end
62
70
 
data/lib/listen/file.rb CHANGED
@@ -1,24 +1,25 @@
1
1
  module Listen
2
2
  class File
3
- def self.change(record, path)
3
+ def self.change(record, dir, rel_path)
4
+ path = dir + rel_path
4
5
  lstat = path.lstat
5
6
 
6
7
  data = { mtime: lstat.mtime.to_f, mode: lstat.mode }
7
8
 
8
- record_data = record.file_data(path)
9
+ record_data = record.file_data(dir, rel_path)
9
10
 
10
11
  if record_data.empty?
11
- record.async.set_path(:file, path, data)
12
+ record.async.update_file(dir, rel_path, data)
12
13
  return :added
13
14
  end
14
15
 
15
16
  if data[:mode] != record_data[:mode]
16
- record.async.set_path(:file, path, data)
17
+ record.async.update_file(dir, rel_path, data)
17
18
  return :modified
18
19
  end
19
20
 
20
21
  if data[:mtime] != record_data[:mtime]
21
- record.async.set_path(:file, path, data)
22
+ record.async.update_file(dir, rel_path, data)
22
23
  return :modified
23
24
  end
24
25
 
@@ -54,7 +55,7 @@ module Listen
54
55
  if data[:mtime].to_i + 2 > Time.now.to_f
55
56
  begin
56
57
  md5 = Digest::MD5.file(path).digest
57
- record.async.set_path(:file, path, data.merge(md5: md5))
58
+ record.async.update_file(dir, rel_path, data.merge(md5: md5))
58
59
  :modified if record_data[:md5] && md5 != record_data[:md5]
59
60
 
60
61
  rescue SystemCallError
@@ -64,10 +65,10 @@ module Listen
64
65
  end
65
66
  end
66
67
  rescue SystemCallError
67
- record.async.unset_path(path)
68
+ record.async.unset_path(dir, rel_path)
68
69
  :removed
69
70
  rescue
70
- Celluloid::Logger.debug "lstat failed for: #{path} (#{$!})"
71
+ Celluloid::Logger.debug "lstat failed for: #{rel_path} (#{$!})"
71
72
  raise
72
73
  end
73
74
 
@@ -13,6 +13,8 @@ module Listen
13
13
 
14
14
  attr_accessor :block
15
15
 
16
+ attr_reader :silencer
17
+
16
18
  # TODO: deprecate
17
19
  attr_reader :options, :directories
18
20
  attr_reader :registry, :supervisor
@@ -35,6 +37,9 @@ module Listen
35
37
  Celluloid.logger.level = _debug_level
36
38
  _log :info, "Celluloid loglevel set to: #{Celluloid.logger.level}"
37
39
 
40
+ @silencer = Silencer.new
41
+ _reconfigure_silencer({})
42
+
38
43
  @tcp_mode = nil
39
44
  if [:recipient, :broadcaster].include? args[1]
40
45
  target = args.shift
@@ -145,24 +150,21 @@ module Listen
145
150
  @registry[type]
146
151
  end
147
152
 
148
- def queue(type, change, path, options = {})
153
+ def queue(type, change, dir, path, options = {})
149
154
  fail "Invalid type: #{type.inspect}" unless [:dir, :file].include? type
150
155
  fail "Invalid change: #{change.inspect}" unless change.is_a?(Symbol)
151
- @queue << [type, change, path, options]
156
+ fail "Invalid path: #{path.inspect}" unless path.is_a?(String)
157
+ @queue << [type, change, dir, path, options]
152
158
 
153
159
  @last_queue_event_time = Time.now.to_f
154
160
  _wakeup_wait_thread unless state == :paused
155
161
 
156
162
  return unless @tcp_mode == :broadcaster
157
163
 
158
- message = TCP::Message.new(type, change, path, options)
164
+ message = TCP::Message.new(type, change, dir, path, options)
159
165
  registry[:broadcaster].async.broadcast(message.payload)
160
166
  end
161
167
 
162
- def silencer
163
- @registry[:silencer]
164
- end
165
-
166
168
  private
167
169
 
168
170
  def _init_options(options = {})
@@ -191,7 +193,6 @@ module Listen
191
193
 
192
194
  def _init_actors
193
195
  @supervisor = Celluloid::SupervisionGroup.run!(registry)
194
- supervisor.add(Silencer, as: :silencer, args: self)
195
196
  supervisor.add(Record, as: :record, args: self)
196
197
  supervisor.pool(Change, as: :change_pool, args: self)
197
198
 
@@ -205,7 +206,8 @@ module Listen
205
206
  registry[:broadcaster].start
206
207
  end
207
208
 
208
- supervisor.add(_adapter_class, as: :adapter, args: self)
209
+ options = [mq: self, directories: directories]
210
+ supervisor.add(_adapter_class, as: :adapter, args: options)
209
211
  end
210
212
 
211
213
  def _wait_for_changes
@@ -236,12 +238,13 @@ module Listen
236
238
  end
237
239
 
238
240
  def _silenced?(path, type)
239
- sync(:silencer).silenced?(path, type)
241
+ @silencer.silenced?(path, type)
240
242
  end
241
243
 
242
244
  def _start_adapter
243
245
  # Don't run async, because configuration has to finish first
244
- sync(:adapter).start
246
+ adapter = sync(:adapter)
247
+ adapter.start
245
248
  end
246
249
 
247
250
  def _log(type, message)
@@ -294,7 +297,13 @@ module Listen
294
297
 
295
298
  def _reconfigure_silencer(extra_options)
296
299
  @options.merge!(extra_options)
297
- registry[:silencer] = Silencer.new(self)
300
+
301
+ # TODO: this should be directory specific
302
+ rules = [:only, :ignore, :ignore!].map do |option|
303
+ [option, @options[option]] if @options.key? option
304
+ end
305
+
306
+ @silencer.configure(Hash[rules.compact])
298
307
  end
299
308
 
300
309
  def _start_wait_thread
@@ -313,5 +322,19 @@ module Listen
313
322
  end
314
323
  @wait_thread = nil
315
324
  end
325
+
326
+ def _queue_raw_change(type, dir, rel_path, options)
327
+ _log :debug, "raw queue: #{[type, dir, rel_path, options].inspect}"
328
+
329
+ unless (worker = async(:change_pool))
330
+ _log :warn, 'Failed to allocate worker from change pool'
331
+ return
332
+ end
333
+
334
+ worker.change(type, dir, rel_path, options)
335
+ rescue RuntimeError
336
+ _log :error, "#{__method__} crashed: #{$!}:#{$@.join("\n")}"
337
+ raise
338
+ end
316
339
  end
317
340
  end
@@ -0,0 +1,23 @@
1
+ module Listen
2
+ class Options
3
+ def initialize(opts, defaults)
4
+ @options = {}
5
+ given_options = opts.dup
6
+ defaults.keys.each do |key|
7
+ @options[key] = given_options.delete(key) || defaults[key]
8
+ end
9
+
10
+ return if given_options.empty?
11
+
12
+ msg = "Unknown options: #{given_options.inspect}"
13
+ Celluloid::Logger.warn msg
14
+ fail msg
15
+ end
16
+
17
+ def method_missing(name, *_)
18
+ return @options[name] if @options.key?(name)
19
+ msg = "Bad option: #{name.inspect} (valid:#{@options.keys.inspect})"
20
+ fail NameError, msg
21
+ end
22
+ end
23
+ end