listen 3.3.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.
@@ -30,13 +30,16 @@ module Listen
30
30
  end
31
31
 
32
32
  # Invalidate some part of the snapshot/record (dir, file, subtree, etc.)
33
+ # rubocop:disable Metrics/MethodLength
34
+ # rubocop:disable Metrics/CyclomaticComplexity
35
+ # rubocop:disable Metrics/PerceivedComplexity
33
36
  def invalidate(type, rel_path, options)
34
37
  watched_dir = Pathname.new(record.root)
35
38
 
36
39
  change = options[:change]
37
40
  cookie = options[:cookie]
38
41
 
39
- if !cookie && config.silenced?(rel_path, type)
42
+ if !cookie && @config.silenced?(rel_path, type)
40
43
  Listen.logger.debug { "(silenced): #{rel_path.inspect}" }
41
44
  return
42
45
  end
@@ -50,29 +53,17 @@ module Listen
50
53
 
51
54
  if change
52
55
  options = cookie ? { cookie: cookie } : {}
53
- config.queue(type, change, watched_dir, rel_path, options)
56
+ @config.queue(type, change, watched_dir, rel_path, options)
54
57
  elsif type == :dir
55
58
  # NOTE: POSSIBLE RECURSION
56
59
  # TODO: fix - use a queue instead
57
60
  Directory.scan(self, rel_path, options)
58
- else
59
- change = File.change(record, rel_path)
60
- return if !change || options[:silence]
61
- config.queue(:file, change, watched_dir, rel_path)
61
+ elsif (change = File.change(record, rel_path)) && !options[:silence]
62
+ @config.queue(:file, change, watched_dir, rel_path)
62
63
  end
63
- rescue RuntimeError => ex
64
- msg = format(
65
- '%s#%s crashed %s:%s',
66
- self.class,
67
- __method__,
68
- exinspect,
69
- ex.backtrace * "\n")
70
- Listen.logger.error(msg)
71
- raise
72
64
  end
73
-
74
- private
75
-
76
- attr_reader :config
65
+ # rubocop:enable Metrics/MethodLength
66
+ # rubocop:enable Metrics/CyclomaticComplexity
67
+ # rubocop:enable Metrics/PerceivedComplexity
77
68
  end
78
69
  end
@@ -35,6 +35,7 @@ module Listen
35
35
 
36
36
  class Forwarder
37
37
  attr_reader :logger
38
+
38
39
  def initialize(options)
39
40
  @options = options
40
41
  @logger = ::Logger.new(STDOUT, level: ::Logger::INFO)
@@ -43,6 +44,7 @@ module Listen
43
44
 
44
45
  def start
45
46
  logger.info 'Starting listen...'
47
+
46
48
  directory = @options[:directory]
47
49
  relative = @options[:relative]
48
50
  callback = proc do |modified, added, removed|
@@ -53,10 +55,7 @@ module Listen
53
55
  end
54
56
  end
55
57
 
56
- listener = Listen.to(
57
- directory,
58
- relative: relative,
59
- &callback)
58
+ listener = Listen.to(directory, relative: relative, &callback)
60
59
 
61
60
  listener.start
62
61
 
@@ -5,6 +5,7 @@ require 'set'
5
5
  module Listen
6
6
  # TODO: refactor (turn it into a normal object, cache the stat, etc)
7
7
  class Directory
8
+ # rubocop:disable Metrics/MethodLength
8
9
  def self.scan(snapshot, rel_path, options)
9
10
  record = snapshot.record
10
11
  dir = Pathname.new(record.root)
@@ -38,22 +39,19 @@ module Listen
38
39
  previous = previous.reject { |entry, _| current.include? path + entry }
39
40
 
40
41
  _async_changes(snapshot, Pathname.new(rel_path), previous, options)
41
-
42
42
  rescue Errno::ENOENT, Errno::EHOSTDOWN
43
43
  record.unset_path(rel_path)
44
44
  _async_changes(snapshot, Pathname.new(rel_path), previous, options)
45
-
46
45
  rescue Errno::ENOTDIR
47
46
  # TODO: path not tested
48
47
  record.unset_path(rel_path)
49
48
  _async_changes(snapshot, path, previous, options)
50
49
  _change(snapshot, :file, rel_path, options)
51
50
  rescue
52
- Listen.logger.warn do
53
- format('scan DIED: %s:%s', $ERROR_INFO, $ERROR_POSITION * "\n")
54
- end
51
+ Listen.logger.warn { format('scan DIED: %s:%s', $ERROR_INFO, $ERROR_POSITION * "\n") }
55
52
  raise
56
53
  end
54
+ # rubocop:enable Metrics/MethodLength
57
55
 
58
56
  def self.ascendant_of?(base, other)
59
57
  other.ascend do |ascendant|
@@ -88,8 +86,8 @@ module Listen
88
86
  # https://github.com/jruby/jruby/issues/3840
89
87
  exists = path.exist?
90
88
  directory = path.directory?
91
- return path.children unless exists && !directory
92
- raise Errno::ENOTDIR, path.to_s
89
+ exists && !directory and raise Errno::ENOTDIR, path.to_s
90
+ path.children
93
91
  end
94
92
  end
95
93
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Besides programming error exceptions like ArgumentError,
4
+ # all public interface exceptions should be declared here and inherit from Listen::Error.
5
+ module Listen
6
+ class Error < RuntimeError
7
+ class NotStarted < Error; end
8
+ class SymlinkLoop < Error; end
9
+ end
10
+ end
@@ -3,16 +3,15 @@
3
3
  module Listen
4
4
  module Event
5
5
  class Config
6
- attr_reader :listener
7
- attr_reader :event_queue
8
- attr_reader :min_delay_between_events
6
+ attr_reader :listener, :event_queue, :min_delay_between_events
9
7
 
10
8
  def initialize(
11
9
  listener,
12
10
  event_queue,
13
11
  queue_optimizer,
14
12
  wait_for_delay,
15
- &block)
13
+ &block
14
+ )
16
15
 
17
16
  @listener = listener
18
17
  @event_queue = event_queue
@@ -26,11 +25,7 @@ module Listen
26
25
  end
27
26
 
28
27
  def call(*args)
29
- @block.call(*args) if @block
30
- end
31
-
32
- def timestamp
33
- Time.now.to_f
28
+ @block&.call(*args)
34
29
  end
35
30
 
36
31
  def callable?
@@ -5,15 +5,15 @@ require 'thread'
5
5
  require 'timeout'
6
6
  require 'listen/event/processor'
7
7
  require 'listen/thread'
8
+ require 'listen/error'
8
9
 
9
10
  module Listen
10
11
  module Event
11
12
  class Loop
12
13
  include Listen::FSM
13
14
 
14
- class Error < RuntimeError
15
- class NotStarted < Error; end
16
- end
15
+ Error = ::Listen::Error
16
+ NotStarted = ::Listen::Error::NotStarted # for backward compatibility
17
17
 
18
18
  start_state :pre_start
19
19
  state :pre_start
@@ -40,6 +40,7 @@ module Listen
40
40
 
41
41
  MAX_STARTUP_SECONDS = 5.0
42
42
 
43
+ # @raises Error::NotStarted if background thread hasn't started in MAX_STARTUP_SECONDS
43
44
  def start
44
45
  # TODO: use a Fiber instead?
45
46
  return unless state == :pre_start
@@ -52,7 +53,7 @@ module Listen
52
53
 
53
54
  Listen.logger.debug("Waiting for processing to start...")
54
55
 
55
- wait_for_state(:started, MAX_STARTUP_SECONDS) or
56
+ wait_for_state(:started, timeout: MAX_STARTUP_SECONDS) or
56
57
  raise Error::NotStarted, "thread didn't start in #{MAX_STARTUP_SECONDS} seconds (in state: #{state.inspect})"
57
58
 
58
59
  Listen.logger.debug('Processing started.')
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'listen/monotonic_time'
4
+
3
5
  module Listen
4
6
  module Event
5
7
  class Processor
@@ -33,7 +35,7 @@ module Listen
33
35
 
34
36
  def _wait_until_events_calm_down
35
37
  loop do
36
- now = _timestamp
38
+ now = MonotonicTime.now
37
39
 
38
40
  # Assure there's at least latency between callbacks to allow
39
41
  # for accumulating changes
@@ -51,10 +53,10 @@ module Listen
51
53
  end
52
54
 
53
55
  def _check_stopped
54
- return unless @listener.stopped?
55
-
56
- _flush_wakeup_reasons
57
- raise Stopped
56
+ if @listener.stopped?
57
+ _flush_wakeup_reasons
58
+ raise Stopped
59
+ end
58
60
  end
59
61
 
60
62
  def _sleep(seconds)
@@ -70,22 +72,22 @@ module Listen
70
72
  end
71
73
 
72
74
  def _remember_time_of_first_unprocessed_event
73
- @first_unprocessed_event_time ||= _timestamp
75
+ @_remember_time_of_first_unprocessed_event ||= MonotonicTime.now
74
76
  end
75
77
 
76
78
  def _reset_no_unprocessed_events
77
- @first_unprocessed_event_time = nil
79
+ @_remember_time_of_first_unprocessed_event = nil
78
80
  end
79
81
 
80
82
  def _deadline
81
- @first_unprocessed_event_time + @latency
83
+ @_remember_time_of_first_unprocessed_event + @latency
82
84
  end
83
85
 
84
86
  # blocks until event is popped
85
87
  # returns the event or `nil` when the event_queue is closed
86
88
  def _wait_until_events
87
89
  config.event_queue.pop.tap do |_event|
88
- @first_unprocessed_event_time ||= _timestamp
90
+ @_remember_time_of_first_unprocessed_event ||= MonotonicTime.now
89
91
  end
90
92
  end
91
93
 
@@ -96,10 +98,6 @@ module Listen
96
98
  end
97
99
  end
98
100
 
99
- def _timestamp
100
- config.timestamp
101
- end
102
-
103
101
  # for easier testing without sleep loop
104
102
  def _process_changes(event)
105
103
  _reset_no_unprocessed_events
@@ -113,13 +111,13 @@ module Listen
113
111
  result = [hash[:modified], hash[:added], hash[:removed]]
114
112
  return if result.all?(&:empty?)
115
113
 
116
- block_start = _timestamp
114
+ block_start = MonotonicTime.now
117
115
  exception_note = " (exception)"
118
116
  ::Listen::Thread.rescue_and_log('_process_changes') do
119
117
  config.call(*result)
120
118
  exception_note = nil
121
119
  end
122
- Listen.logger.debug "Callback#{exception_note} took #{_timestamp - block_start} sec"
120
+ Listen.logger.debug "Callback#{exception_note} took #{MonotonicTime.now - block_start} sec"
123
121
  end
124
122
 
125
123
  attr_reader :config
@@ -30,21 +30,21 @@ module Listen
30
30
  fail "Invalid change: #{change.inspect}" unless change.is_a?(Symbol)
31
31
  fail "Invalid path: #{path.inspect}" unless path.is_a?(String)
32
32
 
33
- dir = _safe_relative_from_cwd(dir)
34
- event_queue.public_send(:<<, [type, change, dir, path, options])
33
+ dir = if @config.relative?
34
+ _safe_relative_from_cwd(dir)
35
+ else
36
+ dir
37
+ end
38
+ @event_queue << [type, change, dir, path, options]
35
39
  end
36
40
 
37
- delegate empty?: :event_queue
38
- delegate pop: :event_queue
39
- delegate close: :event_queue
41
+ delegate empty?: :@event_queue
42
+ delegate pop: :@event_queue
43
+ delegate close: :@event_queue
40
44
 
41
45
  private
42
46
 
43
- attr_reader :event_queue
44
- attr_reader :config
45
-
46
47
  def _safe_relative_from_cwd(dir)
47
- return dir unless config.relative?
48
48
  dir.relative_path_from(Pathname.pwd)
49
49
  rescue ArgumentError
50
50
  dir
@@ -4,6 +4,9 @@ require 'digest/md5'
4
4
 
5
5
  module Listen
6
6
  class File
7
+ # rubocop:disable Metrics/MethodLength
8
+ # rubocop:disable Metrics/CyclomaticComplexity
9
+ # rubocop:disable Metrics/PerceivedComplexity
7
10
  def self.change(record, rel_path)
8
11
  path = Pathname.new(record.root) + rel_path
9
12
  lstat = path.lstat
@@ -74,6 +77,9 @@ module Listen
74
77
  Listen.logger.debug "lstat failed for: #{rel_path} (#{$ERROR_INFO})"
75
78
  raise
76
79
  end
80
+ # rubocop:enable Metrics/MethodLength
81
+ # rubocop:enable Metrics/CyclomaticComplexity
82
+ # rubocop:enable Metrics/PerceivedComplexity
77
83
 
78
84
  def self.inaccurate_mac_time?(stat)
79
85
  # 'mac' means Modified/Accessed/Created
@@ -53,6 +53,9 @@ module Listen
53
53
  # if not already, waits for a state change (up to timeout seconds--`nil` means infinite)
54
54
  # returns truthy iff the transition to one of the desired state has occurred
55
55
  def wait_for_state(*wait_for_states, timeout: nil)
56
+ wait_for_states.each do |state|
57
+ state.is_a?(Symbol) or raise ArgumentError, "states must be symbols (got #{state.inspect})"
58
+ end
56
59
  @mutex.synchronize do
57
60
  if !wait_for_states.include?(@state)
58
61
  @state_changed.wait(@mutex, timeout)
@@ -113,8 +116,8 @@ module Listen
113
116
  @name = name
114
117
  @block = block
115
118
  @transitions = if transitions
116
- Array(transitions).map(&:to_sym)
117
- end
119
+ Array(transitions).map(&:to_sym)
120
+ end
118
121
  end
119
122
 
120
123
  def call(obj)
@@ -33,6 +33,7 @@ module Listen
33
33
  # @yieldparam [Array<String>] added the list of added files
34
34
  # @yieldparam [Array<String>] removed the list of removed files
35
35
  #
36
+ # rubocop:disable Metrics/MethodLength
36
37
  def initialize(*dirs, &block)
37
38
  options = dirs.last.is_a?(Hash) ? dirs.pop : {}
38
39
 
@@ -60,6 +61,7 @@ module Listen
60
61
 
61
62
  initialize_fsm
62
63
  end
64
+ # rubocop:enable Metrics/MethodLength
63
65
 
64
66
  start_state :initializing
65
67
 
@@ -25,9 +25,7 @@ module Listen
25
25
  @relative
26
26
  end
27
27
 
28
- attr_reader :min_delay_between_events
29
-
30
- attr_reader :silencer_rules
28
+ attr_reader :min_delay_between_events, :silencer_rules
31
29
 
32
30
  def adapter_instance_options(klass)
33
31
  valid_keys = klass.const_get('DEFAULTS').keys
@@ -35,7 +33,7 @@ module Listen
35
33
  end
36
34
 
37
35
  def adapter_select_options
38
- valid_keys = %w(force_polling polling_fallback_message).map(&:to_sym)
36
+ valid_keys = %w[force_polling polling_fallback_message].map(&:to_sym)
39
37
  Hash[@options.select { |key, _| valid_keys.include?(key) }]
40
38
  end
41
39
  end
@@ -16,18 +16,19 @@ module Listen
16
16
  private
17
17
 
18
18
  def default_logger
19
- level = case ENV['LISTEN_GEM_DEBUGGING'].to_s
20
- when /debug|2/i
21
- ::Logger::DEBUG
22
- when /info|true|yes|1/i
23
- ::Logger::INFO
24
- when /warn/i
25
- ::Logger::WARN
26
- when /fatal/i
27
- ::Logger::FATAL
28
- else
29
- ::Logger::ERROR
30
- end
19
+ level =
20
+ case ENV['LISTEN_GEM_DEBUGGING'].to_s
21
+ when /debug|2/i
22
+ ::Logger::DEBUG
23
+ when /info|true|yes|1/i
24
+ ::Logger::INFO
25
+ when /warn/i
26
+ ::Logger::WARN
27
+ when /fatal/i
28
+ ::Logger::FATAL
29
+ else
30
+ ::Logger::ERROR
31
+ end
31
32
 
32
33
  ::Logger.new(STDERR, level: level)
33
34
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Listen
4
+ module MonotonicTime
5
+ class << self
6
+ if defined?(Process::CLOCK_MONOTONIC)
7
+
8
+ def now
9
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
10
+ end
11
+
12
+ elsif defined?(Process::CLOCK_MONOTONIC_RAW)
13
+
14
+ def now
15
+ Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW)
16
+ end
17
+
18
+ else
19
+
20
+ def now
21
+ Time.now.to_f
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end