listen 2.7.6 → 2.7.7
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/.gitignore +0 -0
- data/.rspec +0 -0
- data/.rubocop.yml +0 -0
- data/.travis.yml +0 -0
- data/.yardopts +0 -0
- data/CHANGELOG.md +0 -0
- data/CONTRIBUTING.md +0 -0
- data/Gemfile +2 -0
- data/Guardfile +2 -0
- data/LICENSE.txt +0 -0
- data/README.md +0 -0
- data/Rakefile +0 -0
- data/lib/listen.rb +0 -0
- data/lib/listen/adapter.rb +0 -0
- data/lib/listen/adapter/base.rb +47 -21
- data/lib/listen/adapter/bsd.rb +31 -25
- data/lib/listen/adapter/darwin.rb +13 -12
- data/lib/listen/adapter/linux.rb +45 -36
- data/lib/listen/adapter/polling.rb +12 -7
- data/lib/listen/adapter/tcp.rb +9 -4
- data/lib/listen/adapter/windows.rb +46 -58
- data/lib/listen/change.rb +12 -8
- data/lib/listen/cli.rb +0 -0
- data/lib/listen/directory.rb +30 -22
- data/lib/listen/file.rb +9 -8
- data/lib/listen/listener.rb +35 -12
- data/lib/listen/options.rb +23 -0
- data/lib/listen/queue_optimizer.rb +23 -13
- data/lib/listen/record.rb +98 -21
- data/lib/listen/silencer.rb +21 -40
- data/lib/listen/tcp.rb +0 -0
- data/lib/listen/tcp/broadcaster.rb +0 -0
- data/lib/listen/tcp/message.rb +0 -0
- data/lib/listen/version.rb +1 -1
- data/listen.gemspec +0 -0
- data/spec/acceptance/listen_spec.rb +0 -0
- data/spec/acceptance/tcp_spec.rb +0 -0
- data/spec/lib/listen/adapter/base_spec.rb +17 -16
- data/spec/lib/listen/adapter/bsd_spec.rb +0 -0
- data/spec/lib/listen/adapter/darwin_spec.rb +11 -4
- data/spec/lib/listen/adapter/linux_spec.rb +20 -29
- data/spec/lib/listen/adapter/polling_spec.rb +15 -13
- data/spec/lib/listen/adapter/tcp_spec.rb +6 -3
- data/spec/lib/listen/adapter/windows_spec.rb +0 -0
- data/spec/lib/listen/adapter_spec.rb +0 -0
- data/spec/lib/listen/change_spec.rb +21 -27
- data/spec/lib/listen/directory_spec.rb +60 -42
- data/spec/lib/listen/file_spec.rb +16 -20
- data/spec/lib/listen/listener_spec.rb +136 -99
- data/spec/lib/listen/record_spec.rb +205 -62
- data/spec/lib/listen/silencer_spec.rb +44 -114
- data/spec/lib/listen/tcp/broadcaster_spec.rb +0 -0
- data/spec/lib/listen/tcp/listener_spec.rb +8 -5
- data/spec/lib/listen/tcp/message_spec.rb +0 -0
- data/spec/lib/listen_spec.rb +0 -0
- data/spec/spec_helper.rb +0 -0
- data/spec/support/acceptance_helper.rb +15 -4
- data/spec/support/fixtures_helper.rb +0 -0
- data/spec/support/platform_helper.rb +0 -0
- metadata +3 -2
@@ -25,14 +25,21 @@ module Listen
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def _configure
|
28
|
+
def _configure(dir, &callback)
|
29
|
+
require 'wdm'
|
29
30
|
_log :debug, 'wdm - starting...'
|
30
|
-
@worker
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
@worker ||= WDM::Monitor.new
|
32
|
+
@worker.watch_recursively(dir.to_s, :files) do |change|
|
33
|
+
callback.call([:file, change])
|
34
|
+
end
|
35
|
+
|
36
|
+
@worker.watch_recursively(dir.to_s, :directories) do |change|
|
37
|
+
callback.call([:dir, change])
|
38
|
+
end
|
39
|
+
|
40
|
+
events = [:attributes, :last_write]
|
41
|
+
@worker.watch_recursively(dir.to_s, *events) do |change|
|
42
|
+
callback.call([:attr, change])
|
36
43
|
end
|
37
44
|
end
|
38
45
|
|
@@ -40,62 +47,43 @@ module Listen
|
|
40
47
|
@worker.run!
|
41
48
|
end
|
42
49
|
|
43
|
-
def
|
44
|
-
|
45
|
-
begin
|
46
|
-
path = _path(change.path)
|
47
|
-
_log :debug, "wdm - FILE callback: #{change.inspect}"
|
48
|
-
options = { change: _change(change.type) }
|
49
|
-
_notify_change(:file, path, options)
|
50
|
-
rescue
|
51
|
-
_log :error, "wdm - callback failed: #{$!}:#{$@.join("\n")}"
|
52
|
-
raise
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
50
|
+
def _process_event(dir, event)
|
51
|
+
_log :debug, "wdm - callback: #{event.inspect}"
|
56
52
|
|
57
|
-
|
58
|
-
lambda do |change|
|
59
|
-
begin
|
60
|
-
path = _path(change.path)
|
61
|
-
return if path.directory?
|
53
|
+
type, change = event
|
62
54
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
rescue
|
67
|
-
_log :error, "wdm - callback failed: #{$!}:#{$@.join("\n")}"
|
68
|
-
raise
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
55
|
+
full_path = Pathname(change.path)
|
56
|
+
|
57
|
+
rel_path = full_path.relative_path_from(dir).to_s
|
72
58
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
59
|
+
options = { change: _change(change.type) }
|
60
|
+
|
61
|
+
case type
|
62
|
+
when :file
|
63
|
+
_queue_change(:file, dir, rel_path, options)
|
64
|
+
when :attr
|
65
|
+
unless full_path.directory?
|
66
|
+
_queue_change(:file, dir, rel_path, options)
|
67
|
+
end
|
68
|
+
when :dir
|
69
|
+
if change.type == :removed
|
70
|
+
# TODO: check if watched dir?
|
71
|
+
_queue_change(:dir, dir, Pathname(rel_path).dirname.to_s, {})
|
72
|
+
elsif change.type == :added
|
73
|
+
_queue_change(:dir, dir, rel_path, {})
|
74
|
+
else
|
75
|
+
# do nothing - changed directory means either:
|
76
|
+
# - removed subdirs (handled above)
|
77
|
+
# - added subdirs (handled above)
|
78
|
+
# - removed files (handled by _file_callback)
|
79
|
+
# - added files (handled by _file_callback)
|
80
|
+
# so what's left?
|
93
81
|
end
|
94
82
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
83
|
+
rescue
|
84
|
+
details = event.inspect
|
85
|
+
_log :error, "wdm - callback (#{details}): #{$!}:#{$@.join("\n")}"
|
86
|
+
raise
|
99
87
|
end
|
100
88
|
|
101
89
|
def _change(type)
|
data/lib/listen/change.rb
CHANGED
@@ -11,31 +11,35 @@ module Listen
|
|
11
11
|
@listener = listener
|
12
12
|
end
|
13
13
|
|
14
|
-
def change(type,
|
14
|
+
def change(type, watched_dir, rel_path, options = {})
|
15
15
|
change = options[:change]
|
16
16
|
cookie = options[:cookie]
|
17
17
|
|
18
|
-
if !cookie && listener.silencer.silenced?(
|
19
|
-
_log :debug, "(silenced): #{
|
18
|
+
if !cookie && listener.silencer.silenced?(Pathname(rel_path), type)
|
19
|
+
_log :debug, "(silenced): #{rel_path.inspect}"
|
20
20
|
return
|
21
21
|
end
|
22
22
|
|
23
|
+
path = watched_dir + rel_path
|
24
|
+
|
23
25
|
log_details = options[:silence] && 'recording' || change || 'unknown'
|
24
26
|
_log :debug, "#{log_details}: #{type}:#{path} (#{options.inspect})"
|
25
27
|
|
26
28
|
if change
|
27
|
-
|
29
|
+
# TODO: move this to Listener to avoid Celluloid overhead
|
30
|
+
# from caller
|
31
|
+
options = cookie ? { cookie: cookie } : {}
|
32
|
+
listener.queue(type, change, watched_dir, rel_path, options)
|
28
33
|
else
|
29
34
|
return unless (record = listener.sync(:record))
|
30
|
-
record.async.still_building! if options[:build]
|
31
35
|
|
32
36
|
if type == :dir
|
33
37
|
return unless (change_queue = listener.async(:change_pool))
|
34
|
-
Directory.scan(change_queue, record,
|
38
|
+
Directory.scan(change_queue, record, watched_dir, rel_path, options)
|
35
39
|
else
|
36
|
-
change = File.change(record,
|
40
|
+
change = File.change(record, watched_dir, rel_path)
|
37
41
|
return if !change || options[:silence]
|
38
|
-
listener.queue(:file, change,
|
42
|
+
listener.queue(:file, change, watched_dir, rel_path)
|
39
43
|
end
|
40
44
|
end
|
41
45
|
rescue Celluloid::Task::TerminatedError
|
data/lib/listen/cli.rb
CHANGED
File without changes
|
data/lib/listen/directory.rb
CHANGED
@@ -2,61 +2,69 @@ require 'set'
|
|
2
2
|
|
3
3
|
module Listen
|
4
4
|
class Directory
|
5
|
-
def self.scan(queue, sync_record,
|
5
|
+
def self.scan(queue, sync_record, dir, rel_path, options)
|
6
6
|
return unless (record = sync_record.async)
|
7
7
|
|
8
|
-
previous = sync_record.dir_entries(
|
8
|
+
previous = sync_record.dir_entries(dir, rel_path)
|
9
9
|
|
10
|
-
record.
|
10
|
+
record.add_dir(dir, rel_path)
|
11
|
+
|
12
|
+
# TODO: use children(with_directory: false)
|
13
|
+
path = dir + rel_path
|
11
14
|
current = Set.new(path.children)
|
12
15
|
|
13
16
|
if options[:silence]
|
14
|
-
_log :debug, "Recording: #{
|
17
|
+
_log :debug, "Recording: #{rel_path}: #{options.inspect}"\
|
15
18
|
" [#{previous.inspect}] -> (#{current.inspect})"
|
16
19
|
else
|
17
|
-
_log :debug, "Scanning: #{
|
20
|
+
_log :debug, "Scanning: #{rel_path}: #{options.inspect}"\
|
18
21
|
" [#{previous.inspect}] -> (#{current.inspect})"
|
19
22
|
end
|
20
23
|
|
21
24
|
current.each do |full_path|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
_change(queue, :file, full_path, options)
|
26
|
-
end
|
25
|
+
type = full_path.directory? ? :dir : :file
|
26
|
+
item_rel_path = full_path.relative_path_from(dir).to_s
|
27
|
+
_change(queue, type, dir, item_rel_path, options)
|
27
28
|
end
|
28
29
|
|
30
|
+
# TODO: this is not tested properly
|
29
31
|
previous.reject! { |entry, _| current.include? path + entry }
|
30
|
-
|
32
|
+
|
33
|
+
_async_changes(dir, rel_path, queue, previous, options)
|
31
34
|
|
32
35
|
rescue Errno::ENOENT
|
33
|
-
record.unset_path(
|
34
|
-
_async_changes(
|
36
|
+
record.unset_path(dir, rel_path)
|
37
|
+
_async_changes(dir, rel_path, queue, previous, options)
|
35
38
|
|
36
39
|
rescue Errno::ENOTDIR
|
37
40
|
# TODO: path not tested
|
38
|
-
record.unset_path(
|
39
|
-
_async_changes(path, queue, previous, options)
|
40
|
-
_change(queue, :file,
|
41
|
+
record.unset_path(dir, rel_path)
|
42
|
+
_async_changes(dir, path, queue, previous, options)
|
43
|
+
_change(queue, :file, dir, rel_path, options)
|
41
44
|
rescue
|
42
45
|
_log :warn, "scanning DIED: #{$!}:#{$@.join("\n")}"
|
43
46
|
raise
|
44
47
|
end
|
45
48
|
|
46
|
-
def self._async_changes(path, queue, previous, options)
|
49
|
+
def self._async_changes(dir, path, queue, previous, options)
|
47
50
|
previous.each do |entry, data|
|
48
|
-
|
51
|
+
# TODO: this is a hack with insufficient testing
|
52
|
+
type = data.key?(:mtime) ? :file : :dir
|
53
|
+
_change(queue, type, dir, (Pathname(path) + entry).to_s, options)
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
52
|
-
def self._change(queue, type,
|
53
|
-
return queue.change(type,
|
57
|
+
def self._change(queue, type, dir, path, options)
|
58
|
+
return queue.change(type, dir, path, options) if type == :dir
|
59
|
+
|
60
|
+
# Minor param cleanup for tests
|
61
|
+
# TODO: use a dedicated Event class
|
54
62
|
opts = options.dup
|
55
63
|
opts.delete(:recursive)
|
56
64
|
if opts.empty?
|
57
|
-
queue.change(type,
|
65
|
+
queue.change(type, dir, path)
|
58
66
|
else
|
59
|
-
queue.change(type,
|
67
|
+
queue.change(type, dir, path, opts)
|
60
68
|
end
|
61
69
|
end
|
62
70
|
|
data/lib/listen/file.rb
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
module Listen
|
2
2
|
class File
|
3
|
-
def self.change(record,
|
3
|
+
def self.change(record, dir, rel_path)
|
4
|
+
path = dir + rel_path
|
4
5
|
lstat = path.lstat
|
5
6
|
|
6
7
|
data = { mtime: lstat.mtime.to_f, mode: lstat.mode }
|
7
8
|
|
8
|
-
record_data = record.file_data(
|
9
|
+
record_data = record.file_data(dir, rel_path)
|
9
10
|
|
10
11
|
if record_data.empty?
|
11
|
-
record.async.
|
12
|
+
record.async.update_file(dir, rel_path, data)
|
12
13
|
return :added
|
13
14
|
end
|
14
15
|
|
15
16
|
if data[:mode] != record_data[:mode]
|
16
|
-
record.async.
|
17
|
+
record.async.update_file(dir, rel_path, data)
|
17
18
|
return :modified
|
18
19
|
end
|
19
20
|
|
20
21
|
if data[:mtime] != record_data[:mtime]
|
21
|
-
record.async.
|
22
|
+
record.async.update_file(dir, rel_path, data)
|
22
23
|
return :modified
|
23
24
|
end
|
24
25
|
|
@@ -54,7 +55,7 @@ module Listen
|
|
54
55
|
if data[:mtime].to_i + 2 > Time.now.to_f
|
55
56
|
begin
|
56
57
|
md5 = Digest::MD5.file(path).digest
|
57
|
-
record.async.
|
58
|
+
record.async.update_file(dir, rel_path, data.merge(md5: md5))
|
58
59
|
:modified if record_data[:md5] && md5 != record_data[:md5]
|
59
60
|
|
60
61
|
rescue SystemCallError
|
@@ -64,10 +65,10 @@ module Listen
|
|
64
65
|
end
|
65
66
|
end
|
66
67
|
rescue SystemCallError
|
67
|
-
record.async.unset_path(
|
68
|
+
record.async.unset_path(dir, rel_path)
|
68
69
|
:removed
|
69
70
|
rescue
|
70
|
-
Celluloid::Logger.debug "lstat failed for: #{
|
71
|
+
Celluloid::Logger.debug "lstat failed for: #{rel_path} (#{$!})"
|
71
72
|
raise
|
72
73
|
end
|
73
74
|
|
data/lib/listen/listener.rb
CHANGED
@@ -13,6 +13,8 @@ module Listen
|
|
13
13
|
|
14
14
|
attr_accessor :block
|
15
15
|
|
16
|
+
attr_reader :silencer
|
17
|
+
|
16
18
|
# TODO: deprecate
|
17
19
|
attr_reader :options, :directories
|
18
20
|
attr_reader :registry, :supervisor
|
@@ -35,6 +37,9 @@ module Listen
|
|
35
37
|
Celluloid.logger.level = _debug_level
|
36
38
|
_log :info, "Celluloid loglevel set to: #{Celluloid.logger.level}"
|
37
39
|
|
40
|
+
@silencer = Silencer.new
|
41
|
+
_reconfigure_silencer({})
|
42
|
+
|
38
43
|
@tcp_mode = nil
|
39
44
|
if [:recipient, :broadcaster].include? args[1]
|
40
45
|
target = args.shift
|
@@ -145,24 +150,21 @@ module Listen
|
|
145
150
|
@registry[type]
|
146
151
|
end
|
147
152
|
|
148
|
-
def queue(type, change, path, options = {})
|
153
|
+
def queue(type, change, dir, path, options = {})
|
149
154
|
fail "Invalid type: #{type.inspect}" unless [:dir, :file].include? type
|
150
155
|
fail "Invalid change: #{change.inspect}" unless change.is_a?(Symbol)
|
151
|
-
|
156
|
+
fail "Invalid path: #{path.inspect}" unless path.is_a?(String)
|
157
|
+
@queue << [type, change, dir, path, options]
|
152
158
|
|
153
159
|
@last_queue_event_time = Time.now.to_f
|
154
160
|
_wakeup_wait_thread unless state == :paused
|
155
161
|
|
156
162
|
return unless @tcp_mode == :broadcaster
|
157
163
|
|
158
|
-
message = TCP::Message.new(type, change, path, options)
|
164
|
+
message = TCP::Message.new(type, change, dir, path, options)
|
159
165
|
registry[:broadcaster].async.broadcast(message.payload)
|
160
166
|
end
|
161
167
|
|
162
|
-
def silencer
|
163
|
-
@registry[:silencer]
|
164
|
-
end
|
165
|
-
|
166
168
|
private
|
167
169
|
|
168
170
|
def _init_options(options = {})
|
@@ -191,7 +193,6 @@ module Listen
|
|
191
193
|
|
192
194
|
def _init_actors
|
193
195
|
@supervisor = Celluloid::SupervisionGroup.run!(registry)
|
194
|
-
supervisor.add(Silencer, as: :silencer, args: self)
|
195
196
|
supervisor.add(Record, as: :record, args: self)
|
196
197
|
supervisor.pool(Change, as: :change_pool, args: self)
|
197
198
|
|
@@ -205,7 +206,8 @@ module Listen
|
|
205
206
|
registry[:broadcaster].start
|
206
207
|
end
|
207
208
|
|
208
|
-
|
209
|
+
options = [mq: self, directories: directories]
|
210
|
+
supervisor.add(_adapter_class, as: :adapter, args: options)
|
209
211
|
end
|
210
212
|
|
211
213
|
def _wait_for_changes
|
@@ -236,12 +238,13 @@ module Listen
|
|
236
238
|
end
|
237
239
|
|
238
240
|
def _silenced?(path, type)
|
239
|
-
|
241
|
+
@silencer.silenced?(path, type)
|
240
242
|
end
|
241
243
|
|
242
244
|
def _start_adapter
|
243
245
|
# Don't run async, because configuration has to finish first
|
244
|
-
sync(:adapter)
|
246
|
+
adapter = sync(:adapter)
|
247
|
+
adapter.start
|
245
248
|
end
|
246
249
|
|
247
250
|
def _log(type, message)
|
@@ -294,7 +297,13 @@ module Listen
|
|
294
297
|
|
295
298
|
def _reconfigure_silencer(extra_options)
|
296
299
|
@options.merge!(extra_options)
|
297
|
-
|
300
|
+
|
301
|
+
# TODO: this should be directory specific
|
302
|
+
rules = [:only, :ignore, :ignore!].map do |option|
|
303
|
+
[option, @options[option]] if @options.key? option
|
304
|
+
end
|
305
|
+
|
306
|
+
@silencer.configure(Hash[rules.compact])
|
298
307
|
end
|
299
308
|
|
300
309
|
def _start_wait_thread
|
@@ -313,5 +322,19 @@ module Listen
|
|
313
322
|
end
|
314
323
|
@wait_thread = nil
|
315
324
|
end
|
325
|
+
|
326
|
+
def _queue_raw_change(type, dir, rel_path, options)
|
327
|
+
_log :debug, "raw queue: #{[type, dir, rel_path, options].inspect}"
|
328
|
+
|
329
|
+
unless (worker = async(:change_pool))
|
330
|
+
_log :warn, 'Failed to allocate worker from change pool'
|
331
|
+
return
|
332
|
+
end
|
333
|
+
|
334
|
+
worker.change(type, dir, rel_path, options)
|
335
|
+
rescue RuntimeError
|
336
|
+
_log :error, "#{__method__} crashed: #{$!}:#{$@.join("\n")}"
|
337
|
+
raise
|
338
|
+
end
|
316
339
|
end
|
317
340
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Listen
|
2
|
+
class Options
|
3
|
+
def initialize(opts, defaults)
|
4
|
+
@options = {}
|
5
|
+
given_options = opts.dup
|
6
|
+
defaults.keys.each do |key|
|
7
|
+
@options[key] = given_options.delete(key) || defaults[key]
|
8
|
+
end
|
9
|
+
|
10
|
+
return if given_options.empty?
|
11
|
+
|
12
|
+
msg = "Unknown options: #{given_options.inspect}"
|
13
|
+
Celluloid::Logger.warn msg
|
14
|
+
fail msg
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(name, *_)
|
18
|
+
return @options[name] if @options.key?(name)
|
19
|
+
msg = "Bad option: #{name.inspect} (valid:#{@options.keys.inspect})"
|
20
|
+
fail NameError, msg
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|