listen 3.1.3 → 3.3.0.pre.2

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  module Adapter
3
5
  # @see https://github.com/nex3/rb-inotify
@@ -35,9 +37,7 @@ module Listen
35
37
  end
36
38
 
37
39
  def _run
38
- Thread.current[:listen_blocking_read_thread] = true
39
40
  @worker.run
40
- Thread.current[:listen_blocking_read_thread] = false
41
41
  end
42
42
 
43
43
  def _process_event(dir, event)
@@ -47,7 +47,7 @@ module Listen
47
47
  path = Pathname.new(event.watcher.path) + event.name
48
48
  rel_path = path.relative_path_from(dir).to_s
49
49
 
50
- _log(:debug) { "inotify: #{rel_path} (#{event.flags.inspect})" }
50
+ Listen.logger.debug { "inotify: #{rel_path} (#{event.flags.inspect})" }
51
51
 
52
52
  if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
53
53
  if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
@@ -99,7 +99,9 @@ module Listen
99
99
  end
100
100
 
101
101
  def _stop
102
- @worker.close
102
+ @worker&.close
103
+
104
+ super
103
105
  end
104
106
  end
105
107
  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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  module Adapter
3
5
  # Adapter implementation for Windows `wdm`.
@@ -15,7 +17,7 @@ module Listen
15
17
  require 'wdm'
16
18
  true
17
19
  rescue LoadError
18
- _log :debug, format('wdm - load failed: %s:%s', $ERROR_INFO,
20
+ Listen.logger.debug format('wdm - load failed: %s:%s', $ERROR_INFO,
19
21
  $ERROR_POSITION * "\n")
20
22
 
21
23
  Kernel.warn BUNDLER_DECLARE_GEM
@@ -26,7 +28,7 @@ module Listen
26
28
 
27
29
  def _configure(dir)
28
30
  require 'wdm'
29
- _log :debug, 'wdm - starting...'
31
+ Listen.logger.debug 'wdm - starting...'
30
32
  @worker ||= WDM::Monitor.new
31
33
  @worker.watch_recursively(dir.to_s, :files) do |change|
32
34
  yield([:file, change])
@@ -47,7 +49,7 @@ module Listen
47
49
  end
48
50
 
49
51
  def _process_event(dir, event)
50
- _log :debug, "wdm - callback: #{event.inspect}"
52
+ Listen.logger.debug "wdm - callback: #{event.inspect}"
51
53
 
52
54
  type, change = event
53
55
 
@@ -80,7 +82,7 @@ module Listen
80
82
  end
81
83
  rescue
82
84
  details = event.inspect
83
- _log :error, format('wdm - callback (%s): %s:%s', details, $ERROR_INFO,
85
+ Listen.logger.error format('wdm - callback (%s): %s:%s', details, $ERROR_INFO,
84
86
  $ERROR_POSITION * "\n")
85
87
  raise
86
88
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'listen/adapter'
2
4
  require 'listen/adapter/base'
3
5
  require 'listen/adapter/config'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'listen/file'
2
4
  require 'listen/directory'
3
5
 
@@ -35,13 +37,13 @@ module Listen
35
37
  cookie = options[:cookie]
36
38
 
37
39
  if !cookie && config.silenced?(rel_path, type)
38
- Listen::Logger.debug { "(silenced): #{rel_path.inspect}" }
40
+ Listen.logger.debug { "(silenced): #{rel_path.inspect}" }
39
41
  return
40
42
  end
41
43
 
42
44
  path = watched_dir + rel_path
43
45
 
44
- Listen::Logger.debug do
46
+ Listen.logger.debug do
45
47
  log_details = options[:silence] && 'recording' || change || 'unknown'
46
48
  "#{log_details}: #{type}:#{path} (#{options.inspect})"
47
49
  end
@@ -65,7 +67,7 @@ module Listen
65
67
  __method__,
66
68
  exinspect,
67
69
  ex.backtrace * "\n")
68
- Listen::Logger.error(msg)
70
+ Listen.logger.error(msg)
69
71
  raise
70
72
  end
71
73
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'listen'
3
5
  require 'logger'
@@ -35,8 +37,7 @@ module Listen
35
37
  attr_reader :logger
36
38
  def initialize(options)
37
39
  @options = options
38
- @logger = ::Logger.new(STDOUT)
39
- @logger.level = ::Logger::INFO
40
+ @logger = ::Logger.new(STDOUT, level: ::Logger::INFO)
40
41
  @logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
41
42
  end
42
43
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module Listen
@@ -14,7 +16,7 @@ module Listen
14
16
  path = dir + rel_path
15
17
  current = Set.new(_children(path))
16
18
 
17
- Listen::Logger.debug do
19
+ Listen.logger.debug do
18
20
  format('%s: %s(%s): %s -> %s',
19
21
  (options[:silence] ? 'Recording' : 'Scanning'),
20
22
  rel_path, options.inspect, previous.inspect, current.inspect)
@@ -47,12 +49,18 @@ module Listen
47
49
  _async_changes(snapshot, path, previous, options)
48
50
  _change(snapshot, :file, rel_path, options)
49
51
  rescue
50
- Listen::Logger.warn do
52
+ Listen.logger.warn do
51
53
  format('scan DIED: %s:%s', $ERROR_INFO, $ERROR_POSITION * "\n")
52
54
  end
53
55
  raise
54
56
  end
55
57
 
58
+ def self.ascendant_of?(base, other)
59
+ other.ascend do |ascendant|
60
+ break true if base == ascendant
61
+ end
62
+ end
63
+
56
64
  def self._async_changes(snapshot, path, previous, options)
57
65
  fail "Not a Pathname: #{path.inspect}" unless path.respond_to?(:children)
58
66
  previous.each do |entry, data|
@@ -1,6 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  module Event
3
5
  class Config
6
+ attr_reader :listener
7
+ attr_reader :event_queue
8
+ attr_reader :min_delay_between_events
9
+
4
10
  def initialize(
5
11
  listener,
6
12
  event_queue,
@@ -15,8 +21,8 @@ module Listen
15
21
  @block = block
16
22
  end
17
23
 
18
- def sleep(*args)
19
- Kernel.sleep(*args)
24
+ def sleep(seconds)
25
+ Kernel.sleep(seconds)
20
26
  end
21
27
 
22
28
  def call(*args)
@@ -27,8 +33,6 @@ module Listen
27
33
  Time.now.to_f
28
34
  end
29
35
 
30
- attr_reader :event_queue
31
-
32
36
  def callable?
33
37
  @block
34
38
  end
@@ -36,20 +40,6 @@ module Listen
36
40
  def optimize_changes(changes)
37
41
  @queue_optimizer.smoosh_changes(changes)
38
42
  end
39
-
40
- attr_reader :min_delay_between_events
41
-
42
- def stopped?
43
- listener.state == :stopped
44
- end
45
-
46
- def paused?
47
- listener.state == :paused
48
- end
49
-
50
- private
51
-
52
- attr_reader :listener
53
43
  end
54
44
  end
55
45
  end
@@ -1,55 +1,61 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
 
3
5
  require 'timeout'
4
6
  require 'listen/event/processor'
7
+ require 'listen/thread'
5
8
 
6
9
  module Listen
7
10
  module Event
8
11
  class Loop
12
+ include Listen::FSM
13
+
9
14
  class Error < RuntimeError
10
- class NotStarted < Error
11
- end
15
+ class NotStarted < Error; end
12
16
  end
13
17
 
18
+ start_state :pre_start
19
+ state :pre_start
20
+ state :starting
21
+ state :started
22
+ state :stopped
23
+
14
24
  def initialize(config)
15
25
  @config = config
16
26
  @wait_thread = nil
17
- @state = :paused
18
27
  @reasons = ::Queue.new
28
+ initialize_fsm
19
29
  end
20
30
 
21
31
  def wakeup_on_event
22
- return if stopped?
23
- return unless processing?
24
- return unless wait_thread.alive?
25
- _wakeup(:event)
32
+ if started? && @wait_thread&.alive?
33
+ _wakeup(:event)
34
+ end
26
35
  end
27
36
 
28
- def paused?
29
- wait_thread && state == :paused
37
+ def started?
38
+ state == :started
30
39
  end
31
40
 
32
- def processing?
33
- return false if stopped?
34
- return false if paused?
35
- state == :processing
36
- end
41
+ MAX_STARTUP_SECONDS = 5.0
37
42
 
38
- def setup
43
+ def start
39
44
  # TODO: use a Fiber instead?
40
- q = ::Queue.new
41
- @wait_thread = Internals::ThreadPool.add do
42
- _wait_for_changes(q, config)
45
+ return unless state == :pre_start
46
+
47
+ transition! :starting
48
+
49
+ @wait_thread = Listen::Thread.new("wait_thread") do
50
+ _process_changes
43
51
  end
44
52
 
45
- Listen::Logger.debug('Waiting for processing to start...')
46
- Timeout.timeout(5) { q.pop }
47
- end
53
+ Listen.logger.debug("Waiting for processing to start...")
48
54
 
49
- def resume
50
- fail Error::NotStarted if stopped?
51
- return unless wait_thread
52
- _wakeup(:resume)
55
+ wait_for_state(:started, MAX_STARTUP_SECONDS) or
56
+ raise Error::NotStarted, "thread didn't start in #{MAX_STARTUP_SECONDS} seconds (in state: #{state.inspect})"
57
+
58
+ Listen.logger.debug('Processing started.')
53
59
  end
54
60
 
55
61
  def pause
@@ -57,60 +63,33 @@ module Listen
57
63
  # fail NotImplementedError
58
64
  end
59
65
 
60
- def teardown
61
- return unless wait_thread
62
- if wait_thread.alive?
63
- _wakeup(:teardown)
64
- wait_thread.join
66
+ def stop
67
+ return if stopped?
68
+ transition! :stopped
69
+
70
+ if @wait_thread.alive?
71
+ @wait_thread.join
65
72
  end
66
73
  @wait_thread = nil
67
74
  end
68
75
 
69
76
  def stopped?
70
- !wait_thread
77
+ state == :stopped
71
78
  end
72
79
 
73
80
  private
74
81
 
75
- attr_reader :config
76
- attr_reader :wait_thread
77
-
78
- attr_accessor :state
79
-
80
- def _wait_for_changes(ready_queue, config)
81
- processor = Event::Processor.new(config, @reasons)
82
+ def _process_changes
83
+ processor = Event::Processor.new(@config, @reasons)
82
84
 
83
- _wait_until_resumed(ready_queue)
84
- processor.loop_for(config.min_delay_between_events)
85
- rescue StandardError => ex
86
- _nice_error(ex)
87
- end
88
-
89
- def _sleep(*args)
90
- Kernel.sleep(*args)
91
- end
92
-
93
- def _wait_until_resumed(ready_queue)
94
- self.state = :paused
95
- ready_queue << :ready
96
- sleep
97
- self.state = :processing
98
- end
85
+ transition! :started
99
86
 
100
- def _nice_error(ex)
101
- indent = "\n -- "
102
- msg = format(
103
- 'exception while processing events: %s Backtrace:%s%s',
104
- ex,
105
- indent,
106
- ex.backtrace * indent
107
- )
108
- Listen::Logger.error(msg)
87
+ processor.loop_for(@config.min_delay_between_events)
109
88
  end
110
89
 
111
90
  def _wakeup(reason)
112
91
  @reasons << reason
113
- wait_thread.wakeup
92
+ @wait_thread.wakeup
114
93
  end
115
94
  end
116
95
  end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Listen
2
4
  module Event
3
5
  class Processor
4
6
  def initialize(config, reasons)
5
7
  @config = config
8
+ @listener = config.listener
6
9
  @reasons = reasons
7
10
  _reset_no_unprocessed_events
8
11
  end
@@ -13,13 +16,14 @@ module Listen
13
16
  @latency = latency
14
17
 
15
18
  loop do
16
- _wait_until_events
19
+ event = _wait_until_events
20
+ _check_stopped
17
21
  _wait_until_events_calm_down
18
22
  _wait_until_no_longer_paused
19
- _process_changes
23
+ _process_changes(event)
20
24
  end
21
25
  rescue Stopped
22
- Listen::Logger.debug('Processing stopped')
26
+ Listen.logger.debug('Processing stopped')
23
27
  end
24
28
 
25
29
  private
@@ -38,33 +42,31 @@ module Listen
38
42
 
39
43
  # give events a bit of time to accumulate so they can be
40
44
  # compressed/optimized
41
- _sleep(:waiting_until_latency, diff)
45
+ _sleep(diff)
42
46
  end
43
47
  end
44
48
 
45
49
  def _wait_until_no_longer_paused
46
- # TODO: may not be a good idea?
47
- _sleep(:waiting_for_unpause) while config.paused?
50
+ @listener.wait_for_state(*(Listener.states.keys - [:paused]))
48
51
  end
49
52
 
50
53
  def _check_stopped
51
- return unless config.stopped?
54
+ return unless @listener.stopped?
52
55
 
53
56
  _flush_wakeup_reasons
54
57
  raise Stopped
55
58
  end
56
59
 
57
- def _sleep(_local_reason, *args)
60
+ def _sleep(seconds)
58
61
  _check_stopped
59
- sleep_duration = config.sleep(*args)
62
+ config.sleep(seconds)
60
63
  _check_stopped
61
64
 
62
65
  _flush_wakeup_reasons do |reason|
63
- next unless reason == :event
64
- _remember_time_of_first_unprocessed_event unless config.paused?
66
+ if reason == :event && !@listener.paused?
67
+ _remember_time_of_first_unprocessed_event
68
+ end
65
69
  end
66
-
67
- sleep_duration
68
70
  end
69
71
 
70
72
  def _remember_time_of_first_unprocessed_event
@@ -79,16 +81,17 @@ module Listen
79
81
  @first_unprocessed_event_time + @latency
80
82
  end
81
83
 
84
+ # blocks until event is popped
85
+ # returns the event or `nil` when the event_queue is closed
82
86
  def _wait_until_events
83
- # TODO: long sleep may not be a good idea?
84
- _sleep(:waiting_for_events) while config.event_queue.empty?
85
- @first_unprocessed_event_time ||= _timestamp
87
+ config.event_queue.pop.tap do |_event|
88
+ @first_unprocessed_event_time ||= _timestamp
89
+ end
86
90
  end
87
91
 
88
92
  def _flush_wakeup_reasons
89
- reasons = @reasons
90
- until reasons.empty?
91
- reason = reasons.pop
93
+ until @reasons.empty?
94
+ reason = @reasons.pop
92
95
  yield reason if block_given?
93
96
  end
94
97
  end
@@ -98,14 +101,13 @@ module Listen
98
101
  end
99
102
 
100
103
  # for easier testing without sleep loop
101
- def _process_changes
104
+ def _process_changes(event)
102
105
  _reset_no_unprocessed_events
103
106
 
104
- changes = []
107
+ changes = [event]
105
108
  changes << config.event_queue.pop until config.event_queue.empty?
106
109
 
107
- callable = config.callable?
108
- return unless callable
110
+ return unless config.callable?
109
111
 
110
112
  hash = config.optimize_changes(changes)
111
113
  result = [hash[:modified], hash[:added], hash[:removed]]
@@ -113,7 +115,7 @@ module Listen
113
115
 
114
116
  block_start = _timestamp
115
117
  config.call(*result)
116
- Listen::Logger.debug "Callback took #{_timestamp - block_start} sec"
118
+ Listen.logger.debug "Callback took #{_timestamp - block_start} sec"
117
119
  end
118
120
 
119
121
  attr_reader :config