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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +2 -2
- data/README.md +220 -70
- data/bin/listen +3 -4
- data/lib/listen.rb +15 -20
- data/lib/listen/adapter.rb +9 -11
- data/lib/listen/adapter/base.rb +22 -34
- data/lib/listen/adapter/bsd.rb +6 -5
- data/lib/listen/adapter/config.rb +3 -4
- data/lib/listen/adapter/darwin.rb +11 -13
- data/lib/listen/adapter/linux.rb +14 -9
- data/lib/listen/adapter/polling.rb +8 -5
- data/lib/listen/adapter/windows.rb +15 -17
- data/lib/listen/backend.rb +2 -0
- data/lib/listen/change.rb +14 -21
- data/lib/listen/cli.rb +6 -6
- data/lib/listen/directory.rb +8 -8
- data/lib/listen/error.rb +10 -0
- data/lib/listen/event/config.rb +9 -24
- data/lib/listen/event/loop.rb +44 -67
- data/lib/listen/event/processor.rb +41 -37
- data/lib/listen/event/queue.rb +12 -13
- data/lib/listen/file.rb +15 -2
- data/lib/listen/fsm.rb +72 -71
- data/lib/listen/listener.rb +26 -23
- data/lib/listen/listener/config.rb +4 -4
- data/lib/listen/logger.rb +24 -20
- data/lib/listen/monotonic_time.rb +27 -0
- data/lib/listen/options.rb +11 -8
- data/lib/listen/queue_optimizer.rb +9 -12
- data/lib/listen/record.rb +30 -31
- data/lib/listen/record/entry.rb +4 -2
- data/lib/listen/record/symlink_detector.rb +10 -8
- data/lib/listen/silencer.rb +12 -8
- data/lib/listen/silencer/controller.rb +2 -0
- data/lib/listen/thread.rb +54 -0
- data/lib/listen/version.rb +3 -1
- metadata +13 -22
- data/lib/listen/internals/thread_pool.rb +0 -29
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
|
18
|
-
Listen
|
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
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
data/lib/listen/adapter.rb
CHANGED
@@ -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.'
|
14
|
+
'Learn more at https://github.com/guard/listen#listen-adapters.'
|
13
15
|
|
14
16
|
class << self
|
15
17
|
def select(options = {})
|
16
|
-
|
18
|
+
Listen.logger.debug 'Adapter: considering polling ...'
|
17
19
|
return Polling if options[:force_polling]
|
18
|
-
|
20
|
+
Listen.logger.debug 'Adapter: considering optimized backend...'
|
19
21
|
return _usable_adapter_class if _usable_adapter_class
|
20
|
-
|
22
|
+
Listen.logger.debug 'Adapter: falling back to polling...'
|
21
23
|
_warn_polling_fallback(options)
|
22
24
|
Polling
|
23
25
|
rescue
|
24
|
-
|
25
|
-
|
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.
|
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
|
data/lib/listen/adapter/base.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
69
|
+
Listen.logger.warn('Adapter already started!')
|
67
70
|
return
|
68
71
|
end
|
69
72
|
|
70
73
|
@started = true
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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 =
|
96
|
+
start = MonotonicTime.now
|
103
97
|
yield
|
104
|
-
diff =
|
105
|
-
Listen
|
98
|
+
diff = MonotonicTime.now - start
|
99
|
+
Listen.logger.info format('%s: %.05f seconds', title, diff)
|
106
100
|
rescue
|
107
|
-
Listen
|
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
|
-
|
119
|
+
Listen.logger.error(formatted)
|
130
120
|
end
|
131
121
|
|
132
122
|
class << self
|
133
|
-
|
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
|
data/lib/listen/adapter/bsd.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
2
|
-
|
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
|
-
|
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::
|
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
|
-
|
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
|
71
|
-
|
72
|
-
|
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
|
data/lib/listen/adapter/linux.rb
CHANGED
@@ -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
|
-
|
22
|
-
'/
|
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
|
27
|
+
INOTIFY_LIMIT_MESSAGE = <<-EOS
|
25
28
|
FATAL: Listen error: unable to monitor directories for changes.
|
26
|
-
Visit #{
|
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
|
-
|
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
|
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 =
|
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 =
|
24
|
+
start = MonotonicTime.now
|
23
25
|
@polling_callbacks.each do |callback|
|
24
26
|
callback.call(nil)
|
25
|
-
nap_time = options.latency - (
|
26
|
-
|
27
|
-
|
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
|