listen 3.2.1 → 3.4.1

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.
data/lib/listen.rb CHANGED
@@ -1,23 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
4
+ require 'weakref'
2
5
  require 'listen/logger'
3
6
  require 'listen/listener'
4
7
 
5
- require 'listen/internals/thread_pool'
6
-
7
- # Always set up logging by default first time file is required
8
- #
9
- # NOTE: If you need to clear the logger completely, do so *after*
10
- # requiring this file. If you need to set a custom logger,
11
- # require the listen/logger file and set the logger before requiring
12
- # this file.
13
- Listen.setup_default_logger_if_unset
14
-
15
8
  # Won't print anything by default because of level - unless you've set
16
9
  # LISTEN_GEM_DEBUGGING or provided your own logger with a high enough level
17
- Listen::Logger.info "Listen loglevel set to: #{Listen.logger.level}"
18
- Listen::Logger.info "Listen version: #{Listen::VERSION}"
10
+ Listen.logger.info "Listen loglevel set to: #{Listen.logger.level}"
11
+ Listen.logger.info "Listen version: #{Listen::VERSION}"
19
12
 
20
13
  module Listen
14
+ @listeners = Queue.new
15
+
21
16
  class << self
22
17
  # Listens to file system modifications on a either single directory or
23
18
  # multiple directories.
@@ -32,21 +27,21 @@ module Listen
32
27
  # @return [Listen::Listener] the listener
33
28
  #
34
29
  def to(*args, &block)
35
- @listeners ||= []
36
30
  Listener.new(*args, &block).tap do |listener|
37
- @listeners << listener
31
+ @listeners.enq(WeakRef.new(listener))
38
32
  end
39
33
  end
40
34
 
41
35
  # This is used by the `listen` binary to handle Ctrl-C
42
36
  #
43
37
  def stop
44
- Internals::ThreadPool.stop
45
- @listeners ||= []
46
-
47
- # TODO: should use a mutex for this
48
- @listeners.each(&:stop)
49
- @listeners = nil
38
+ while (listener = @listeners.deq(true))
39
+ begin
40
+ listener.stop
41
+ rescue WeakRef::RefError
42
+ end
43
+ end
44
+ rescue ThreadError
50
45
  end
51
46
  end
52
47
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'listen/adapter/base'
2
4
  require 'listen/adapter/bsd'
3
5
  require 'listen/adapter/darwin'
@@ -9,37 +11,33 @@ module Listen
9
11
  module Adapter
10
12
  OPTIMIZED_ADAPTERS = [Darwin, Linux, BSD, Windows].freeze
11
13
  POLLING_FALLBACK_MESSAGE = 'Listen will be polling for changes.'\
12
- 'Learn more at https://github.com/guard/listen#listen-adapters.'.freeze
14
+ 'Learn more at https://github.com/guard/listen#listen-adapters.'
13
15
 
14
16
  class << self
15
17
  def select(options = {})
16
- _log :debug, 'Adapter: considering polling ...'
18
+ Listen.logger.debug 'Adapter: considering polling ...'
17
19
  return Polling if options[:force_polling]
18
- _log :debug, 'Adapter: considering optimized backend...'
20
+ Listen.logger.debug 'Adapter: considering optimized backend...'
19
21
  return _usable_adapter_class if _usable_adapter_class
20
- _log :debug, 'Adapter: falling back to polling...'
22
+ Listen.logger.debug 'Adapter: falling back to polling...'
21
23
  _warn_polling_fallback(options)
22
24
  Polling
23
25
  rescue
24
- _log :warn, format('Adapter: failed: %s:%s', $ERROR_POSITION.inspect,
25
- $ERROR_POSITION * "\n")
26
+ Listen.logger.warn format('Adapter: failed: %s:%s', $ERROR_POSITION.inspect,
27
+ $ERROR_POSITION * "\n")
26
28
  raise
27
29
  end
28
30
 
29
31
  private
30
32
 
31
33
  def _usable_adapter_class
32
- OPTIMIZED_ADAPTERS.detect(&:usable?)
34
+ OPTIMIZED_ADAPTERS.find(&:usable?)
33
35
  end
34
36
 
35
37
  def _warn_polling_fallback(options)
36
38
  msg = options.fetch(:polling_fallback_message, POLLING_FALLBACK_MESSAGE)
37
39
  Kernel.warn "[Listen warning]:\n #{msg}" if msg
38
40
  end
39
-
40
- def _log(type, message)
41
- Listen::Logger.send(type, message)
42
- end
43
41
  end
44
42
  end
45
43
  end
@@ -1,17 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'listen/options'
2
4
  require 'listen/record'
3
5
  require 'listen/change'
6
+ require 'listen/thread'
4
7
 
5
8
  module Listen
6
9
  module Adapter
7
10
  class Base
8
- attr_reader :options
11
+ attr_reader :options, :config
9
12
 
10
13
  # TODO: only used by tests
11
14
  DEFAULTS = {}.freeze
12
15
 
13
- attr_reader :config
14
-
15
16
  def initialize(config)
16
17
  @started = false
17
18
  @config = config
@@ -28,9 +29,10 @@ module Listen
28
29
  end
29
30
 
30
31
  # TODO: it's a separate method as a temporary workaround for tests
32
+ # rubocop:disable Metrics/MethodLength
31
33
  def configure
32
34
  if @configured
33
- _log(:warn, 'Adapter already configured!')
35
+ Listen.logger.warn('Adapter already configured!')
34
36
  return
35
37
  end
36
38
 
@@ -54,6 +56,7 @@ module Listen
54
56
  @snapshots[dir] = snapshot
55
57
  end
56
58
  end
59
+ # rubocop:enable Metrics/MethodLength
57
60
 
58
61
  def started?
59
62
  @started
@@ -63,48 +66,39 @@ module Listen
63
66
  configure
64
67
 
65
68
  if started?
66
- _log(:warn, 'Adapter already started!')
69
+ Listen.logger.warn('Adapter already started!')
67
70
  return
68
71
  end
69
72
 
70
73
  @started = true
71
74
 
72
- calling_stack = caller.dup
73
- Listen::Internals::ThreadPool.add do
74
- begin
75
- @snapshots.values.each do |snapshot|
76
- _timed('Record.build()') { snapshot.record.build }
77
- end
78
- _run
79
- rescue
80
- msg = 'run() in thread failed: %s:\n'\
81
- ' %s\n\ncalled from:\n %s'
82
- _log_exception(msg, calling_stack)
83
- raise # for unit tests mostly
75
+ @run_thread = Listen::Thread.new("run_thread") do
76
+ @snapshots.each_value do |snapshot|
77
+ _timed('Record.build()') { snapshot.record.build }
84
78
  end
79
+ _run
85
80
  end
86
81
  end
87
82
 
88
83
  def stop
89
84
  _stop
90
- end
91
-
92
- def self.usable?
93
- const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
85
+ config.queue.close # this causes queue.pop to return `nil` to the front-end
94
86
  end
95
87
 
96
88
  private
97
89
 
98
90
  def _stop
91
+ @run_thread&.kill
92
+ @run_thread = nil
99
93
  end
100
94
 
101
95
  def _timed(title)
102
- start = Time.now.to_f
96
+ start = MonotonicTime.now
103
97
  yield
104
- diff = Time.now.to_f - start
105
- Listen::Logger.info format('%s: %.05f seconds', title, diff)
98
+ diff = MonotonicTime.now - start
99
+ Listen.logger.info format('%s: %.05f seconds', title, diff)
106
100
  rescue
107
- Listen::Logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
101
+ Listen.logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
108
102
  raise
109
103
  end
110
104
 
@@ -114,10 +108,6 @@ module Listen
114
108
  @snapshots[dir].invalidate(type, rel_path, options)
115
109
  end
116
110
 
117
- def _log(*args, &block)
118
- self.class.send(:_log, *args, &block)
119
- end
120
-
121
111
  def _log_exception(msg, caller_stack)
122
112
  formatted = format(
123
113
  msg,
@@ -126,14 +116,12 @@ module Listen
126
116
  caller_stack * "\n"
127
117
  )
128
118
 
129
- _log(:error, formatted)
119
+ Listen.logger.error(formatted)
130
120
  end
131
121
 
132
122
  class << self
133
- private
134
-
135
- def _log(*args, &block)
136
- Listen::Logger.send(*args, &block)
123
+ def usable?
124
+ const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
137
125
  end
138
126
  end
139
127
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Listener implementation for BSD's `kqueue`.
2
4
  # @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
3
5
  # @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
@@ -5,7 +7,7 @@
5
7
  module Listen
6
8
  module Adapter
7
9
  class BSD < Base
8
- OS_REGEXP = /bsd|dragonfly/i
10
+ OS_REGEXP = /bsd|dragonfly/i.freeze
9
11
 
10
12
  DEFAULTS = {
11
13
  events: [
@@ -71,8 +73,7 @@ module Listen
71
73
  def _change(event_flags)
72
74
  { modified: [:attrib, :extend],
73
75
  added: [:write],
74
- removed: [:rename, :delete]
75
- }.each do |change, flags|
76
+ removed: [:rename, :delete] }.each do |change, flags|
76
77
  return change unless (flags & event_flags).empty?
77
78
  end
78
79
  nil
@@ -85,7 +86,7 @@ module Listen
85
86
  def _watch_for_new_file(event)
86
87
  queue = event.watcher.queue
87
88
  _find(_event_path(event).to_s) do |file_path|
88
- unless queue.watchers.detect { |_, v| v.path == file_path.to_s }
89
+ unless queue.watchers.find { |_, v| v.path == file_path.to_s }
89
90
  _watch_file(file_path, queue)
90
91
  end
91
92
  end
@@ -94,7 +95,7 @@ module Listen
94
95
  def _watch_file(path, queue)
95
96
  queue.watch_file(path, *options.events, &@callback)
96
97
  rescue Errno::ENOENT => e
97
- _log :warn, "kqueue: watch file failed: #{e.message}"
98
+ Listen.logger.warn "kqueue: watch file failed: #{e.message}"
98
99
  end
99
100
 
100
101
  # Quick rubocop workaround
@@ -1,12 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
 
3
5
  module Listen
4
6
  module Adapter
5
7
  class Config
6
- attr_reader :directories
7
- attr_reader :silencer
8
- attr_reader :queue
9
- attr_reader :adapter_options
8
+ attr_reader :directories, :silencer, :queue, :adapter_options
10
9
 
11
10
  def initialize(directories, queue, silencer, adapter_options)
12
11
  # Default to current directory if no directories are supplied
@@ -1,12 +1,13 @@
1
- require 'thread'
2
- require 'listen/internals/thread_pool'
1
+ # frozen_string_literal: true
2
+
3
+ require 'listen/thread'
3
4
 
4
5
  module Listen
5
6
  module Adapter
6
7
  # Adapter implementation for Mac OS X `FSEvents`.
7
8
  #
8
9
  class Darwin < Base
9
- OS_REGEXP = /darwin(?<major_version>1\d+)/i
10
+ OS_REGEXP = /darwin(?<major_version>(1|2)\d+)/i.freeze
10
11
 
11
12
  # The default delay between checking for changes.
12
13
  DEFAULTS = { latency: 0.1 }.freeze
@@ -43,14 +44,14 @@ module Listen
43
44
  require 'rb-fsevent'
44
45
  worker = FSEvent.new
45
46
  dirs_to_watch = @callbacks.keys.map(&:to_s)
46
- _log(:info) { "fsevent: watching: #{dirs_to_watch.inspect}" }
47
+ Listen.logger.info { "fsevent: watching: #{dirs_to_watch.inspect}" }
47
48
  worker.watch(dirs_to_watch, { latency: options.latency }, &method(:_process_changes))
48
- Listen::Internals::ThreadPool.add { _run_worker(worker) }
49
+ @worker_thread = Listen::Thread.new("worker_thread") { worker.run }
49
50
  end
50
51
 
51
52
  def _process_changes(dirs)
52
53
  dirs.each do |dir|
53
- dir = Pathname.new(dir.sub(%r{\/$}, ''))
54
+ dir = Pathname.new(dir.sub(%r{/$}, ''))
54
55
 
55
56
  @callbacks.each do |watched_dir, callback|
56
57
  if watched_dir.eql?(dir) || Listen::Directory.ascendant_of?(watched_dir, dir)
@@ -61,18 +62,15 @@ module Listen
61
62
  end
62
63
 
63
64
  def _process_event(dir, path)
64
- _log(:debug) { "fsevent: processing path: #{path.inspect}" }
65
+ Listen.logger.debug { "fsevent: processing path: #{path.inspect}" }
65
66
  # TODO: does this preserve symlinks?
66
67
  rel_path = path.relative_path_from(dir).to_s
67
68
  _queue_change(:dir, dir, rel_path, recursive: true)
68
69
  end
69
70
 
70
- def _run_worker(worker)
71
- _log(:debug) { "fsevent: running worker: #{worker.inspect}" }
72
- worker.run
73
- rescue
74
- format_string = 'fsevent: running worker failed: %s:%s called from: %s'
75
- _log_exception format_string, caller
71
+ def _stop
72
+ @worker_thread&.kill
73
+ super
76
74
  end
77
75
  end
78
76
  end
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  module Adapter
3
5
  # @see https://github.com/nex3/rb-inotify
4
6
  class Linux < Base
5
- OS_REGEXP = /linux/i
7
+ OS_REGEXP = /linux/i.freeze
6
8
 
7
9
  DEFAULTS = {
8
10
  events: [
9
11
  :recursive,
10
12
  :attrib,
11
13
  :create,
14
+ :modify,
12
15
  :delete,
13
16
  :move,
14
17
  :close_write
@@ -18,12 +21,12 @@ module Listen
18
21
 
19
22
  private
20
23
 
21
- WIKI_URL = 'https://github.com/guard/listen'\
22
- '/wiki/Increasing-the-amount-of-inotify-watchers'.freeze
24
+ README_URL = 'https://github.com/guard/listen'\
25
+ '/blob/master/README.md#increasing-the-amount-of-inotify-watchers'
23
26
 
24
- INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
27
+ INOTIFY_LIMIT_MESSAGE = <<-EOS
25
28
  FATAL: Listen error: unable to monitor directories for changes.
26
- Visit #{WIKI_URL} for info on how to fix this.
29
+ Visit #{README_URL} for info on how to fix this.
27
30
  EOS
28
31
 
29
32
  def _configure(directory, &callback)
@@ -35,11 +38,10 @@ module Listen
35
38
  end
36
39
 
37
40
  def _run
38
- Thread.current[:listen_blocking_read_thread] = true
39
41
  @worker.run
40
- Thread.current[:listen_blocking_read_thread] = false
41
42
  end
42
43
 
44
+ # rubocop:disable Metrics/MethodLength
43
45
  def _process_event(dir, event)
44
46
  # NOTE: avoid using event.absolute_name since new API
45
47
  # will need to have a custom recursion implemented
@@ -47,7 +49,7 @@ module Listen
47
49
  path = Pathname.new(event.watcher.path) + event.name
48
50
  rel_path = path.relative_path_from(dir).to_s
49
51
 
50
- _log(:debug) { "inotify: #{rel_path} (#{event.flags.inspect})" }
52
+ Listen.logger.debug { "inotify: #{rel_path} (#{event.flags.inspect})" }
51
53
 
52
54
  if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
53
55
  if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
@@ -72,6 +74,7 @@ module Listen
72
74
 
73
75
  _queue_change(:file, dir, rel_path, params)
74
76
  end
77
+ # rubocop:enable Metrics/MethodLength
75
78
 
76
79
  def _skip_event?(event)
77
80
  # Event on root directory
@@ -99,7 +102,9 @@ module Listen
99
102
  end
100
103
 
101
104
  def _stop
102
- @worker && @worker.close
105
+ @worker&.close
106
+
107
+ super
103
108
  end
104
109
  end
105
110
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  module Adapter
3
5
  # Polling Adapter that works cross-platform and
@@ -6,7 +8,7 @@ module Listen
6
8
  # file IO than the other implementations.
7
9
  #
8
10
  class Polling < Base
9
- OS_REGEXP = // # match every OS
11
+ OS_REGEXP = //.freeze # match every OS
10
12
 
11
13
  DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 }.freeze
12
14
 
@@ -19,12 +21,13 @@ module Listen
19
21
 
20
22
  def _run
21
23
  loop do
22
- start = Time.now.to_f
24
+ start = MonotonicTime.now
23
25
  @polling_callbacks.each do |callback|
24
26
  callback.call(nil)
25
- nap_time = options.latency - (Time.now.to_f - start)
26
- # TODO: warn if nap_time is negative (polling too slow)
27
- sleep(nap_time) if nap_time > 0
27
+ if (nap_time = options.latency - (MonotonicTime.now - start)) > 0
28
+ # TODO: warn if nap_time is negative (polling too slow)
29
+ sleep(nap_time)
30
+ end
28
31
  end
29
32
  end
30
33
  end