listen 2.7.6 → 2.7.7

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 962fc43324f716a5abe1d8acd8ea5df7c87d3d28
4
- data.tar.gz: 84b801baff1f5026d64b15df0c9de5fceac572b7
3
+ metadata.gz: e6d5e368c0d42f2b677a88efcd2b983eb1551278
4
+ data.tar.gz: 656135661cb4e2c2bd82c9acdbf9d0779a1c707a
5
5
  SHA512:
6
- metadata.gz: d95876a7d2bd3a39a8130685e2dfa40745ee6ca947646aa303a42b0ab1c4d8bb92347adf2b7e817209840c0a973a813ee5b3d90aafe0db6541361f2fa0d6b5a3
7
- data.tar.gz: 3cf94f221c2c4debd84f14a4545ba6caecb45bf6c1d3097ab5482d4ce9e907858aa80d736cc66d54d74e9e100423aca970515ccc0debf89dfb5990f16d169752
6
+ metadata.gz: d3b07e9530ffa906cf9cc1413dba58b990dc5e720e7c42f4364ac84790e47c43d10bfa5aa5afcbe43e360e1f6c459a5f7ffb15f7dc20ef8467127c6d1c8c93de
7
+ data.tar.gz: c6e078178e2879ec66de6c54a52542aa973d9de372edda0c8e74356d7fd1377330f569ef8ae73bf61b4f09962fe341d53dec6201bfdd4261275163fcdfb1a035
data/.gitignore CHANGED
File without changes
data/.rspec CHANGED
File without changes
data/.rubocop.yml CHANGED
File without changes
data/.travis.yml CHANGED
File without changes
data/.yardopts CHANGED
File without changes
data/CHANGELOG.md CHANGED
File without changes
data/CONTRIBUTING.md CHANGED
File without changes
data/Gemfile CHANGED
@@ -38,6 +38,8 @@ group :tool do
38
38
  gem 'yard', require: false
39
39
  gem 'guard-rspec', require: false
40
40
  gem 'guard-rubocop'
41
+ gem 'pry-rescue'
42
+ gem 'pry-stack_explorer'
41
43
  end
42
44
 
43
45
  group :test do
data/Guardfile CHANGED
@@ -1,3 +1,5 @@
1
+ ignore(%r{spec/\.fixtures/})
2
+
1
3
  guard :rspec, cmd: 'bundle exec rspec', failed_mode: :keep do
2
4
  watch(%r{^spec/.+_spec\.rb$})
3
5
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
data/LICENSE.txt CHANGED
File without changes
data/README.md CHANGED
File without changes
data/Rakefile CHANGED
File without changes
data/lib/listen.rb CHANGED
File without changes
File without changes
@@ -1,19 +1,58 @@
1
+ require 'listen/options'
2
+
1
3
  module Listen
2
4
  module Adapter
3
5
  class Base
4
6
  include Celluloid
5
7
 
6
- attr_accessor :listener
8
+ attr_reader :options
9
+
10
+ # TODO: only used by tests
11
+ DEFAULTS = {}
12
+
13
+ def initialize(opts)
14
+ @configured = nil
15
+ options = opts.dup
16
+ @mq = options.delete(:mq)
17
+ @directories = options.delete(:directories)
18
+
19
+ Array(@directories).each do |dir|
20
+ next if dir.is_a?(Pathname)
21
+ fail ArgumentError, "not a Pathname: #{dir.inspect}"
22
+ end
23
+
24
+ # TODO: actually use this in every adapter
25
+ @recursion = options.delete(:recursion)
26
+ @recursion = true if @recursion.nil?
7
27
 
8
- def initialize(listener)
9
- @listener = listener
28
+ defaults = self.class.const_get('DEFAULTS')
29
+ @options = Listen::Options.new(options, defaults)
10
30
  rescue
11
31
  _log :error, "adapter config failed: #{$!}:#{$@.join("\n")}"
12
32
  raise
13
33
  end
14
34
 
35
+ # TODO: it's a separate method as a temporary workaround for tests
36
+ def configure
37
+ return if @configured
38
+ @configured = true
39
+
40
+ @callbacks ||= {}
41
+ @directories.each do |dir|
42
+ unless dir.is_a?(Pathname)
43
+ fail ArgumentError, "not a Pathname: #{dir.inspect}"
44
+ end
45
+
46
+ callback = @callbacks[dir] || lambda do |event|
47
+ _process_event(dir, event)
48
+ end
49
+ @callbacks[dir] = callback
50
+ _configure(dir, &callback)
51
+ end
52
+ end
53
+
15
54
  def start
16
- _configure
55
+ configure
17
56
  Thread.new do
18
57
  begin
19
58
  _run
@@ -34,23 +73,10 @@ module Listen
34
73
 
35
74
  private
36
75
 
37
- def _configure
38
- end
39
-
40
- def _directories
41
- listener.directories
42
- end
43
-
44
- def _notify_change(type, path, options = {})
45
- unless (worker = listener.async(:change_pool))
46
- _log :warn, 'Failed to allocate worker from change pool'
47
- return
48
- end
49
-
50
- worker.change(type, path, options)
51
- rescue RuntimeError
52
- _log :error, "_notify_change crashed: #{$!}:#{$@.join("\n")}"
53
- raise
76
+ def _queue_change(type, dir, rel_path, options)
77
+ # TODO: temporary workaround to remove dependency on Change through
78
+ # Celluloid in tests
79
+ @mq.send(:_queue_raw_change, type, dir, rel_path, options)
54
80
  end
55
81
 
56
82
  def _log(type, message)
@@ -7,7 +7,16 @@ module Listen
7
7
  class BSD < Base
8
8
  OS_REGEXP = /bsd|dragonfly/i
9
9
 
10
- EVENTS = [:delete, :write, :extend, :attrib, :rename] # :link, :revoke
10
+ DEFAULTS = {
11
+ events: [
12
+ :delete,
13
+ :write,
14
+ :extend,
15
+ :attrib,
16
+ :rename
17
+ # :link, :revoke
18
+ ]
19
+ }
11
20
 
12
21
  BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
13
22
  Please add the following to your Gemfile to avoid polling for changes:
@@ -47,36 +56,33 @@ module Listen
47
56
 
48
57
  private
49
58
 
50
- def _configure
51
- @worker = KQueue::Queue.new
52
- _directories.each do |path|
53
- # use Record to make a snapshot of dir, so we
54
- # can detect new files
55
- _find(path.to_s) { |file_path| _watch_file(file_path, @worker) }
56
- end
59
+ def _configure(directory, &_callback)
60
+ @worker ||= KQueue::Queue.new
61
+ # use Record to make a snapshot of dir, so we
62
+ # can detect new files
63
+ _find(directory.to_s) { |path| _watch_file(path, @worker) }
57
64
  end
58
65
 
59
66
  def _run
60
67
  @worker.run
61
68
  end
62
69
 
63
- def _worker_callback
64
- lambda do |event|
65
- path = _event_path(event)
66
- if path.directory?
67
- # Force dir content tracking to kick in, or we won't have
68
- # names of added files
69
- _notify_change(:dir, path, recursive: true)
70
- else
71
- _notify_change(:file, path, change: _change(event.flags))
72
- end
73
-
74
- # If it is a directory, and it has a write flag, it means a
75
- # file has been added so find out which and deal with it.
76
- # No need to check for removed files, kqueue will forget them
77
- # when the vfs does.
78
- _watch_for_new_file(event) if path.directory?
70
+ def _process_event(dir, event)
71
+ full_path = _event_path(event)
72
+ if full_path.directory?
73
+ # Force dir content tracking to kick in, or we won't have
74
+ # names of added files
75
+ _queue_change(:dir, dir, '.', recursive: true)
76
+ else
77
+ path = full_path.relative_path_from(dir)
78
+ _queue_change(:file, dir, path, change: _change(event.flags))
79
79
  end
80
+
81
+ # If it is a directory, and it has a write flag, it means a
82
+ # file has been added so find out which and deal with it.
83
+ # No need to check for removed files, kqueue will forget them
84
+ # when the vfs does.
85
+ _watch_for_new_file(event) if path.directory?
80
86
  end
81
87
 
82
88
  def _change(event_flags)
@@ -103,7 +109,7 @@ module Listen
103
109
  end
104
110
 
105
111
  def _watch_file(path, queue)
106
- queue.watch_file(path, *EVENTS, &_worker_callback)
112
+ queue.watch_file(path, *options.events, &_worker_callback)
107
113
  end
108
114
 
109
115
  # Quick rubocop workaround
@@ -6,28 +6,29 @@ module Listen
6
6
  OS_REGEXP = /darwin(1.+)?$/i
7
7
 
8
8
  # The default delay between checking for changes.
9
- DEFAULT_LATENCY = 0.1
9
+ DEFAULTS = { latency: 0.1 }
10
10
 
11
11
  private
12
12
 
13
- def _configure
13
+ def _configure(dir, &callback)
14
14
  require 'rb-fsevent'
15
- @worker = FSEvent.new
16
- @worker.watch(_directories.map(&:to_s), latency: _latency) do |changes|
17
- changes.each do |path|
18
- new_path = Pathname.new(path.sub(/\/$/, ''))
19
- _log :debug, "fsevent: #{new_path}"
20
- _notify_change(:dir, new_path)
21
- end
22
- end
15
+ @worker ||= FSEvent.new
16
+ opts = { latency: options.latency }
17
+ @worker.watch(dir.to_s, opts, &callback)
23
18
  end
24
19
 
25
20
  def _run
26
21
  @worker.run
27
22
  end
28
23
 
29
- def _latency
30
- listener.options[:latency] || DEFAULT_LATENCY
24
+ def _process_event(dir, event)
25
+ event.each do |path|
26
+ new_path = Pathname.new(path.sub(/\/$/, ''))
27
+ _log :debug, "fsevent: #{new_path}"
28
+ # TODO: does this preserve symlinks?
29
+ rel_path = new_path.relative_path_from(dir).to_s
30
+ _queue_change(:dir, dir, rel_path, recursive: true)
31
+ end
31
32
  end
32
33
  end
33
34
  end
@@ -1,31 +1,34 @@
1
1
  module Listen
2
2
  module Adapter
3
- # Listener implementation for Linux `inotify`.
4
3
  # @see https://github.com/nex3/rb-inotify
5
- #
6
4
  class Linux < Base
7
5
  OS_REGEXP = /linux/i
8
6
 
9
- EVENTS = [:recursive, :attrib, :create, :delete, :move, :close_write]
7
+ DEFAULTS = {
8
+ events: [
9
+ :recursive,
10
+ :attrib,
11
+ :create,
12
+ :delete,
13
+ :move,
14
+ :close_write
15
+ ]
16
+ }
17
+
18
+ private
10
19
 
11
20
  WIKI_URL = 'https://github.com/guard/listen'\
12
21
  '/wiki/Increasing-the-amount-of-inotify-watchers'
13
22
 
14
23
  INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
15
24
  FATAL: Listen error: unable to monitor directories for changes.
16
-
17
- Please head to #{WIKI_URL}
18
- for information on how to solve this issue.
25
+ Visit #{WIKI_URL} for info on how to fix this.
19
26
  EOS
20
27
 
21
- private
22
-
23
- def _configure
28
+ def _configure(directory, &callback)
24
29
  require 'rb-inotify'
25
- @worker = INotify::Notifier.new
26
- _directories.each do |path|
27
- @worker.watch(path.to_s, *EVENTS, &_callback)
28
- end
30
+ @worker ||= INotify::Notifier.new
31
+ @worker.watch(directory.to_s, *options.events, &callback)
29
32
  rescue Errno::ENOSPC
30
33
  # workaround - Celluloid catches abort and prints nothing
31
34
  STDERR.puts INOTIFY_LIMIT_MESSAGE
@@ -37,32 +40,38 @@ module Listen
37
40
  @worker.run
38
41
  end
39
42
 
40
- def _callback
41
- lambda do |event|
42
- # NOTE: avoid using event.absolute_name since new API
43
- # will need to have a custom recursion implemented
44
- # to properly match events to configured directories
45
- path = Pathname.new(event.watcher.path) + event.name
46
-
47
- _log :debug, "inotify: #{event.name} #{path} (#{event.flags.inspect})"
48
-
49
- if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
50
- if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
51
- _notify_change(:dir, path.dirname)
52
- else
53
- _notify_change(:dir, path)
54
- end
43
+ def _process_event(dir, event)
44
+ # NOTE: avoid using event.absolute_name since new API
45
+ # will need to have a custom recursion implemented
46
+ # to properly match events to configured directories
47
+ path = Pathname.new(event.watcher.path) + event.name
48
+ rel_path = path.relative_path_from(dir).to_s
49
+
50
+ _log :debug, "inotify: #{rel_path} (#{event.flags.inspect})"
51
+
52
+ if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
53
+ if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
54
+ _queue_change(:dir, dir, Pathname(rel_path).dirname, {})
55
55
  else
56
- next if _skip_event?(event)
57
- cookie_opts = event.cookie.zero? ? {} : { cookie: event.cookie }
58
- if _dir_event?(event)
59
- _notify_change(:dir, path, cookie_opts)
60
- else
61
- options = { change: _change(event.flags) }
62
- _notify_change(:file, path, options.merge(cookie_opts))
63
- end
56
+ _queue_change(:dir, dir, rel_path, {})
64
57
  end
58
+ return
65
59
  end
60
+
61
+ return if _skip_event?(event)
62
+
63
+ cookie_params = event.cookie.zero? ? {} : { cookie: event.cookie }
64
+
65
+ # Note: don't pass options to force rescanning the directory, so we can
66
+ # detect moving/deleting a whole tree
67
+ if _dir_event?(event)
68
+ _queue_change(:dir, dir, rel_path, cookie_params)
69
+ return
70
+ end
71
+
72
+ params = cookie_params.merge(change: _change(event.flags))
73
+
74
+ _queue_change(:file, dir, rel_path, params)
66
75
  end
67
76
 
68
77
  def _skip_event?(event)
@@ -6,26 +6,31 @@ module Listen
6
6
  # file IO than the other implementations.
7
7
  #
8
8
  class Polling < Base
9
- OS_REGEXP = // # match any
9
+ OS_REGEXP = // # match every OS
10
10
 
11
- DEFAULT_POLLING_LATENCY = 1.0
11
+ DEFAULTS = { latency: 1.0 }
12
12
 
13
13
  private
14
14
 
15
- def _latency
16
- listener.options[:latency] || DEFAULT_POLLING_LATENCY
15
+ def _configure(_, &callback)
16
+ @polling_callbacks ||= []
17
+ @polling_callbacks << callback
17
18
  end
18
19
 
19
20
  def _run
20
21
  loop do
21
22
  start = Time.now.to_f
22
- _directories.each do |path|
23
- _notify_change(:dir, path, recursive: true)
24
- nap_time = _latency - (Time.now.to_f - start)
23
+ @polling_callbacks.each do |callback|
24
+ callback.call(nil)
25
+ nap_time = options.latency - (Time.now.to_f - start)
25
26
  sleep(nap_time) if nap_time > 0
26
27
  end
27
28
  end
28
29
  end
30
+
31
+ def _process_event(dir, _)
32
+ _queue_change(:dir, dir, '.', recursive: true)
33
+ end
29
34
  end
30
35
  end
31
36
  end
@@ -9,15 +9,19 @@ module Listen
9
9
  OS_REGEXP = // # match any
10
10
 
11
11
  include Celluloid::IO
12
-
13
12
  finalizer :finalize
14
13
 
14
+ DEFAULTS = {
15
+ host: 'localhost',
16
+ port: '4000'
17
+ }
18
+
15
19
  attr_reader :buffer, :socket
16
20
 
17
21
  # Initializes and starts a Celluloid::IO-powered TCP-recipient
18
22
  def start
19
23
  attempts = 3
20
- @socket = TCPSocket.new(listener.host, listener.port)
24
+ @socket = TCPSocket.new(options.host, options.port)
21
25
  @buffer = ''
22
26
  async.run
23
27
  rescue Celluloid::Task::TerminatedError
@@ -66,8 +70,9 @@ module Listen
66
70
 
67
71
  # Handles incoming message by notifying of path changes
68
72
  def handle_message(message)
69
- type, modification, path, _ = message.object
70
- _notify_change(type.to_sym, path, change: modification.to_sym)
73
+ type, change, dir, path, _ = message.object
74
+ _log :debug, "TCP message: #{[type, change, dir, path].inspect}"
75
+ _queue_change(type.to_sym, Pathname(dir), path, change: change.to_sym)
71
76
  end
72
77
 
73
78
  def self.local_fs?