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.
- checksums.yaml +5 -5
- data/CONTRIBUTING.md +8 -1
- data/README.md +29 -11
- data/lib/listen.rb +15 -25
- data/lib/listen/adapter.rb +6 -8
- data/lib/listen/adapter/base.rb +17 -29
- data/lib/listen/adapter/bsd.rb +3 -1
- data/lib/listen/adapter/config.rb +2 -0
- data/lib/listen/adapter/darwin.rb +29 -44
- data/lib/listen/adapter/linux.rb +6 -4
- data/lib/listen/adapter/polling.rb +2 -0
- data/lib/listen/adapter/windows.rb +6 -4
- data/lib/listen/backend.rb +2 -0
- data/lib/listen/change.rb +5 -3
- data/lib/listen/cli.rb +3 -2
- data/lib/listen/directory.rb +10 -2
- data/lib/listen/event/config.rb +8 -18
- data/lib/listen/event/loop.rb +43 -64
- data/lib/listen/event/processor.rb +26 -24
- data/lib/listen/event/queue.rb +4 -5
- data/lib/listen/file.rb +9 -2
- data/lib/listen/fsm.rb +69 -71
- data/lib/listen/listener.rb +25 -24
- data/lib/listen/listener/config.rb +2 -0
- data/lib/listen/logger.rb +27 -24
- data/lib/listen/options.rb +3 -1
- data/lib/listen/queue_optimizer.rb +6 -4
- data/lib/listen/record.rb +16 -2
- data/lib/listen/record/entry.rb +3 -1
- data/lib/listen/record/symlink_detector.rb +2 -0
- data/lib/listen/silencer.rb +5 -0
- data/lib/listen/silencer/controller.rb +2 -0
- data/lib/listen/thread.rb +47 -0
- data/lib/listen/version.rb +3 -1
- metadata +16 -48
- data/lib/listen/internals/thread_pool.rb +0 -29
data/lib/listen/adapter/linux.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
# Adapter implementation for Windows `wdm`.
|
@@ -15,7 +17,7 @@ module Listen
|
|
15
17
|
require 'wdm'
|
16
18
|
true
|
17
19
|
rescue LoadError
|
18
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
85
|
+
Listen.logger.error format('wdm - callback (%s): %s:%s', details, $ERROR_INFO,
|
84
86
|
$ERROR_POSITION * "\n")
|
85
87
|
raise
|
86
88
|
end
|
data/lib/listen/backend.rb
CHANGED
data/lib/listen/change.rb
CHANGED
@@ -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
|
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
|
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
|
70
|
+
Listen.logger.error(msg)
|
69
71
|
raise
|
70
72
|
end
|
71
73
|
|
data/lib/listen/cli.rb
CHANGED
@@ -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
|
|
data/lib/listen/directory.rb
CHANGED
@@ -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
|
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
|
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|
|
data/lib/listen/event/config.rb
CHANGED
@@ -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(
|
19
|
-
Kernel.sleep(
|
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
|
data/lib/listen/event/loop.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
_wakeup(:event)
|
32
|
+
if started? && @wait_thread&.alive?
|
33
|
+
_wakeup(:event)
|
34
|
+
end
|
26
35
|
end
|
27
36
|
|
28
|
-
def
|
29
|
-
|
37
|
+
def started?
|
38
|
+
state == :started
|
30
39
|
end
|
31
40
|
|
32
|
-
|
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
|
43
|
+
def start
|
39
44
|
# TODO: use a Fiber instead?
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
46
|
-
Timeout.timeout(5) { q.pop }
|
47
|
-
end
|
53
|
+
Listen.logger.debug("Waiting for processing to start...")
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
61
|
-
return
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
77
|
+
state == :stopped
|
71
78
|
end
|
72
79
|
|
73
80
|
private
|
74
81
|
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
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
|
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(
|
45
|
+
_sleep(diff)
|
42
46
|
end
|
43
47
|
end
|
44
48
|
|
45
49
|
def _wait_until_no_longer_paused
|
46
|
-
|
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
|
54
|
+
return unless @listener.stopped?
|
52
55
|
|
53
56
|
_flush_wakeup_reasons
|
54
57
|
raise Stopped
|
55
58
|
end
|
56
59
|
|
57
|
-
def _sleep(
|
60
|
+
def _sleep(seconds)
|
58
61
|
_check_stopped
|
59
|
-
|
62
|
+
config.sleep(seconds)
|
60
63
|
_check_stopped
|
61
64
|
|
62
65
|
_flush_wakeup_reasons do |reason|
|
63
|
-
|
64
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
90
|
-
|
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
|
-
|
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
|
118
|
+
Listen.logger.debug "Callback took #{_timestamp - block_start} sec"
|
117
119
|
end
|
118
120
|
|
119
121
|
attr_reader :config
|