listen 2.10.1 → 3.0.0
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/README.md +11 -46
- data/lib/listen.rb +19 -40
- data/lib/listen/adapter.rb +1 -3
- data/lib/listen/adapter/base.rb +66 -36
- data/lib/listen/adapter/config.rb +21 -0
- data/lib/listen/adapter/linux.rb +5 -7
- data/lib/listen/adapter/polling.rb +2 -1
- data/lib/listen/backend.rb +41 -0
- data/lib/listen/change.rb +45 -26
- data/lib/listen/cli.rb +3 -11
- data/lib/listen/directory.rb +31 -29
- data/lib/listen/event/config.rb +59 -0
- data/lib/listen/event/loop.rb +113 -0
- data/lib/listen/event/processor.rb +122 -0
- data/lib/listen/event/queue.rb +54 -0
- data/lib/listen/file.rb +9 -9
- data/lib/listen/fsm.rb +131 -0
- data/lib/listen/internals/thread_pool.rb +1 -1
- data/lib/listen/listener.rb +66 -305
- data/lib/listen/listener/config.rb +45 -0
- data/lib/listen/logger.rb +32 -0
- data/lib/listen/options.rb +1 -1
- data/lib/listen/queue_optimizer.rb +38 -20
- data/lib/listen/record.rb +50 -65
- data/lib/listen/silencer/controller.rb +48 -0
- data/lib/listen/version.rb +1 -1
- metadata +12 -21
- data/lib/listen/adapter/tcp.rb +0 -88
- data/lib/listen/internals/logging.rb +0 -35
- data/lib/listen/tcp.rb +0 -8
- data/lib/listen/tcp/broadcaster.rb +0 -79
- data/lib/listen/tcp/message.rb +0 -50
@@ -0,0 +1,45 @@
|
|
1
|
+
module Listen
|
2
|
+
class Listener
|
3
|
+
class Config
|
4
|
+
DEFAULTS = {
|
5
|
+
# Listener options
|
6
|
+
debug: false, # TODO: is this broken?
|
7
|
+
wait_for_delay: nil, # NOTE: should be provided by adapter if possible
|
8
|
+
relative: false,
|
9
|
+
|
10
|
+
# Backend selecting options
|
11
|
+
force_polling: false,
|
12
|
+
polling_fallback_message: nil
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(opts)
|
16
|
+
@options = DEFAULTS.merge(opts)
|
17
|
+
@relative = @options[:relative]
|
18
|
+
@min_delay_between_events = @options[:wait_for_delay]
|
19
|
+
@silencer_rules = @options # silencer will extract what it needs
|
20
|
+
end
|
21
|
+
|
22
|
+
def relative?
|
23
|
+
@relative
|
24
|
+
end
|
25
|
+
|
26
|
+
def min_delay_between_events
|
27
|
+
@min_delay_between_events
|
28
|
+
end
|
29
|
+
|
30
|
+
def silencer_rules
|
31
|
+
@silencer_rules
|
32
|
+
end
|
33
|
+
|
34
|
+
def adapter_instance_options(klass)
|
35
|
+
valid_keys = klass.const_get('DEFAULTS').keys
|
36
|
+
Hash[@options.select { |key, _| valid_keys.include?(key) }]
|
37
|
+
end
|
38
|
+
|
39
|
+
def adapter_select_options
|
40
|
+
valid_keys = %w(force_polling polling_fallback_message).map(&:to_sym)
|
41
|
+
Hash[@options.select { |key, _| valid_keys.include?(key) }]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Listen
|
2
|
+
def self.logger
|
3
|
+
@logger
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.logger=(logger)
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.setup_default_logger_if_unset
|
11
|
+
self.logger ||= ::Logger.new(STDERR).tap do |logger|
|
12
|
+
debugging = ENV['LISTEN_GEM_DEBUGGING']
|
13
|
+
logger.level =
|
14
|
+
case debugging.to_s
|
15
|
+
when /2/
|
16
|
+
::Logger::DEBUG
|
17
|
+
when /true|yes|1/i
|
18
|
+
::Logger::INFO
|
19
|
+
else
|
20
|
+
::Logger::ERROR
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Logger
|
26
|
+
%i(fatal error warn info debug).each do |meth|
|
27
|
+
define_singleton_method(meth) do |*args, &block|
|
28
|
+
Listen.logger.public_send(meth, *args, &block) if Listen.logger
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/listen/options.rb
CHANGED
@@ -1,23 +1,40 @@
|
|
1
1
|
module Listen
|
2
|
-
|
3
|
-
|
2
|
+
class QueueOptimizer
|
3
|
+
class Config
|
4
|
+
def initialize(adapter_class, silencer)
|
5
|
+
@adapter_class = adapter_class
|
6
|
+
@silencer = silencer
|
7
|
+
end
|
8
|
+
|
9
|
+
def exist?(path)
|
10
|
+
Pathname(path).exist?
|
11
|
+
end
|
12
|
+
|
13
|
+
def silenced?(path, type)
|
14
|
+
@silencer.silenced?(path, type)
|
15
|
+
end
|
16
|
+
|
17
|
+
def debug(*args, &block)
|
18
|
+
Listen.logger.debug(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
4
21
|
|
5
|
-
def
|
22
|
+
def smoosh_changes(changes)
|
6
23
|
# TODO: adapter could be nil at this point (shutdown)
|
7
|
-
|
8
|
-
|
9
|
-
(options || {})[:cookie]
|
10
|
-
end
|
11
|
-
_squash_changes(_reinterpret_related_changes(cookies))
|
12
|
-
else
|
13
|
-
smooshed = { modified: [], added: [], removed: [] }
|
14
|
-
changes.each do |_, change, dir, rel_path, _|
|
15
|
-
smooshed[change] << (dir + rel_path).to_s if smooshed.key?(change)
|
16
|
-
end
|
17
|
-
smooshed.tap { |s| s.each { |_, v| v.uniq! } }
|
24
|
+
cookies = changes.group_by do |_, _, _, _, options|
|
25
|
+
(options || {})[:cookie]
|
18
26
|
end
|
27
|
+
_squash_changes(_reinterpret_related_changes(cookies))
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(config)
|
31
|
+
@config = config
|
19
32
|
end
|
20
33
|
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :config
|
37
|
+
|
21
38
|
# groups changes into the expected structure expected by
|
22
39
|
# clients
|
23
40
|
def _squash_changes(changes)
|
@@ -28,13 +45,14 @@ module Listen
|
|
28
45
|
actions = changes.group_by(&:last).map do |path, action_list|
|
29
46
|
[_logical_action_for(path, action_list.map(&:first)), path.to_s]
|
30
47
|
end
|
31
|
-
|
48
|
+
|
49
|
+
config.debug("listen: raw changes: #{actions.inspect}")
|
32
50
|
|
33
51
|
{ modified: [], added: [], removed: [] }.tap do |squashed|
|
34
52
|
actions.each do |type, path|
|
35
53
|
squashed[type] << path unless type.nil?
|
36
54
|
end
|
37
|
-
|
55
|
+
config.debug("listen: final changes: #{squashed.inspect}")
|
38
56
|
end
|
39
57
|
end
|
40
58
|
|
@@ -53,7 +71,7 @@ module Listen
|
|
53
71
|
|
54
72
|
# TODO: avoid checking if path exists and instead assume the events are
|
55
73
|
# in order (if last is :removed, it doesn't exist, etc.)
|
56
|
-
if
|
74
|
+
if config.exist?(path)
|
57
75
|
if diff > 0
|
58
76
|
:added
|
59
77
|
elsif diff.zero? && added > 0
|
@@ -77,7 +95,7 @@ module Listen
|
|
77
95
|
[[:modified, to_dir, to_file]]
|
78
96
|
else
|
79
97
|
not_silenced = changes.reject do |type, _, _, path, _|
|
80
|
-
|
98
|
+
config.silenced?(Pathname(path), type)
|
81
99
|
end
|
82
100
|
not_silenced.map do |_, change, dir, path, _|
|
83
101
|
[table.fetch(change, change), dir, path]
|
@@ -107,8 +125,8 @@ module Listen
|
|
107
125
|
|
108
126
|
# Expect an ignored moved_from and non-ignored moved_to
|
109
127
|
# to qualify as an "editor modify"
|
110
|
-
return unless
|
111
|
-
|
128
|
+
return unless config.silenced?(Pathname(from), from_type)
|
129
|
+
config.silenced?(Pathname(to), to_type) ? nil : [to_dir, to]
|
112
130
|
end
|
113
131
|
end
|
114
132
|
end
|
data/lib/listen/record.rb
CHANGED
@@ -3,55 +3,53 @@ require 'listen/record/symlink_detector'
|
|
3
3
|
|
4
4
|
module Listen
|
5
5
|
class Record
|
6
|
-
include Celluloid
|
7
6
|
# TODO: one Record object per watched directory?
|
8
|
-
|
9
7
|
# TODO: deprecate
|
10
|
-
attr_accessor :paths, :listener
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
@
|
9
|
+
attr_reader :root
|
10
|
+
def initialize(directory)
|
11
|
+
@tree = _auto_hash
|
12
|
+
@root = directory.to_s
|
15
13
|
end
|
16
14
|
|
17
|
-
def add_dir(
|
15
|
+
def add_dir(rel_path)
|
18
16
|
return if [nil, '', '.'].include? rel_path
|
19
|
-
@
|
17
|
+
@tree[rel_path] ||= {}
|
20
18
|
end
|
21
19
|
|
22
|
-
def update_file(
|
20
|
+
def update_file(rel_path, data)
|
23
21
|
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
24
|
-
_fast_update_file(
|
22
|
+
_fast_update_file(dirname, basename, data)
|
25
23
|
end
|
26
24
|
|
27
|
-
def unset_path(
|
25
|
+
def unset_path(rel_path)
|
28
26
|
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
29
|
-
_fast_unset_path(
|
27
|
+
_fast_unset_path(dirname, basename)
|
30
28
|
end
|
31
29
|
|
32
|
-
def file_data(
|
33
|
-
root = @paths[dir.to_s]
|
30
|
+
def file_data(rel_path)
|
34
31
|
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
35
32
|
if [nil, '', '.'].include? dirname
|
36
|
-
|
37
|
-
|
33
|
+
tree[basename] ||= {}
|
34
|
+
tree[basename].dup
|
38
35
|
else
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
tree[dirname] ||= {}
|
37
|
+
tree[dirname][basename] ||= {}
|
38
|
+
tree[dirname][basename].dup
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
45
|
-
def dir_entries(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
def dir_entries(rel_path)
|
43
|
+
subtree =
|
44
|
+
if [nil, '', '.'].include? rel_path.to_s
|
45
|
+
tree
|
46
|
+
else
|
47
|
+
tree[rel_path.to_s] ||= _auto_hash
|
48
|
+
tree[rel_path.to_s]
|
49
|
+
end
|
52
50
|
|
53
51
|
result = {}
|
54
|
-
|
52
|
+
subtree.each do |key, values|
|
55
53
|
# only get data for file entries
|
56
54
|
result[key] = values.key?(:mtime) ? values : {}
|
57
55
|
end
|
@@ -59,18 +57,14 @@ module Listen
|
|
59
57
|
end
|
60
58
|
|
61
59
|
def build
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
# TODO:
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
Celluloid::Logger.info "Record.build(): #{Time.now.to_f - start} seconds"
|
71
|
-
rescue
|
72
|
-
Celluloid::Logger.warn "build crashed: #{$ERROR_INFO.inspect}"
|
73
|
-
raise
|
60
|
+
@tree = _auto_hash
|
61
|
+
# TODO: test with a file name given
|
62
|
+
# TODO: test other permissions
|
63
|
+
# TODO: test with mixed encoding
|
64
|
+
symlink_detector = SymlinkDetector.new
|
65
|
+
remaining = Queue.new
|
66
|
+
remaining << Entry.new(root, nil, nil)
|
67
|
+
_fast_build_dir(remaining, symlink_detector) until remaining.empty?
|
74
68
|
end
|
75
69
|
|
76
70
|
private
|
@@ -79,56 +73,47 @@ module Listen
|
|
79
73
|
Hash.new { |h, k| h[k] = Hash.new }
|
80
74
|
end
|
81
75
|
|
82
|
-
def
|
83
|
-
|
76
|
+
def tree
|
77
|
+
@tree
|
78
|
+
end
|
79
|
+
|
80
|
+
def _fast_update_file(dirname, basename, data)
|
84
81
|
if [nil, '', '.'].include? dirname
|
85
|
-
|
82
|
+
tree[basename] = (tree[basename] || {}).merge(data)
|
86
83
|
else
|
87
|
-
|
88
|
-
|
84
|
+
tree[dirname] ||= {}
|
85
|
+
tree[dirname][basename] = (tree[dirname][basename] || {}).merge(data)
|
89
86
|
end
|
90
87
|
end
|
91
88
|
|
92
|
-
def _fast_unset_path(
|
93
|
-
root = @paths[dir.to_s]
|
89
|
+
def _fast_unset_path(dirname, basename)
|
94
90
|
# this may need to be reworked to properly remove
|
95
91
|
# entries from a tree, without adding non-existing dirs to the record
|
96
92
|
if [nil, '', '.'].include? dirname
|
97
|
-
return unless
|
98
|
-
|
93
|
+
return unless tree.key?(basename)
|
94
|
+
tree.delete(basename)
|
99
95
|
else
|
100
|
-
return unless
|
101
|
-
|
96
|
+
return unless tree.key?(dirname)
|
97
|
+
tree[dirname].delete(basename)
|
102
98
|
end
|
103
99
|
end
|
104
100
|
|
105
|
-
# TODO: test with a file name given
|
106
|
-
# TODO: test other permissions
|
107
|
-
# TODO: test with mixed encoding
|
108
|
-
def _fast_build(root)
|
109
|
-
symlink_detector = SymlinkDetector.new
|
110
|
-
@paths[root] = _auto_hash
|
111
|
-
remaining = Queue.new
|
112
|
-
remaining << Entry.new(root, nil, nil)
|
113
|
-
_fast_build_dir(remaining, symlink_detector) until remaining.empty?
|
114
|
-
end
|
115
|
-
|
116
101
|
def _fast_build_dir(remaining, symlink_detector)
|
117
102
|
entry = remaining.pop
|
118
103
|
children = entry.children # NOTE: children() implicitly tests if dir
|
119
104
|
symlink_detector.verify_unwatched!(entry)
|
120
105
|
children.each { |child| remaining << child }
|
121
|
-
add_dir(entry.
|
106
|
+
add_dir(entry.record_dir_key)
|
122
107
|
rescue Errno::ENOTDIR
|
123
108
|
_fast_try_file(entry)
|
124
109
|
rescue SystemCallError, SymlinkDetector::Error
|
125
|
-
_fast_unset_path(entry.
|
110
|
+
_fast_unset_path(entry.relative, entry.name)
|
126
111
|
end
|
127
112
|
|
128
113
|
def _fast_try_file(entry)
|
129
|
-
_fast_update_file(entry.
|
114
|
+
_fast_update_file(entry.relative, entry.name, entry.meta)
|
130
115
|
rescue SystemCallError
|
131
|
-
_fast_unset_path(entry.
|
116
|
+
_fast_unset_path(entry.relative, entry.name)
|
132
117
|
end
|
133
118
|
end
|
134
119
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Listen
|
2
|
+
class Silencer
|
3
|
+
class Controller
|
4
|
+
def initialize(silencer, default_options)
|
5
|
+
@silencer = silencer
|
6
|
+
|
7
|
+
opts = default_options
|
8
|
+
|
9
|
+
@prev_silencer_options = {}
|
10
|
+
rules = [:only, :ignore, :ignore!].map do |option|
|
11
|
+
[option, opts[option]] if opts.key? option
|
12
|
+
end
|
13
|
+
|
14
|
+
_reconfigure_silencer(Hash[rules.compact])
|
15
|
+
end
|
16
|
+
|
17
|
+
def append_ignores(*regexps)
|
18
|
+
prev_ignores = Array(@prev_silencer_options[:ignore])
|
19
|
+
_reconfigure_silencer(ignore: [prev_ignores + regexps])
|
20
|
+
end
|
21
|
+
|
22
|
+
def replace_with_bang_ignores(regexps)
|
23
|
+
_reconfigure_silencer(ignore!: regexps)
|
24
|
+
end
|
25
|
+
|
26
|
+
def replace_with_only(regexps)
|
27
|
+
_reconfigure_silencer(only: regexps)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def _reconfigure_silencer(extra_options)
|
33
|
+
opts = extra_options.dup
|
34
|
+
opts = opts.map do |key, value|
|
35
|
+
[key, Array(value).flatten.compact]
|
36
|
+
end
|
37
|
+
opts = Hash[opts]
|
38
|
+
|
39
|
+
if opts.key?(:ignore) && opts[:ignore].empty?
|
40
|
+
opts.delete(:ignore)
|
41
|
+
end
|
42
|
+
|
43
|
+
@prev_silencer_options = opts
|
44
|
+
@silencer.configure(@prev_silencer_options.dup.freeze)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/listen/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thibaud Guillaume-Gentil
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: celluloid
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.16.0
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 0.16.0
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rb-fsevent
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,27 +69,32 @@ files:
|
|
83
69
|
- lib/listen/adapter.rb
|
84
70
|
- lib/listen/adapter/base.rb
|
85
71
|
- lib/listen/adapter/bsd.rb
|
72
|
+
- lib/listen/adapter/config.rb
|
86
73
|
- lib/listen/adapter/darwin.rb
|
87
74
|
- lib/listen/adapter/linux.rb
|
88
75
|
- lib/listen/adapter/polling.rb
|
89
|
-
- lib/listen/adapter/tcp.rb
|
90
76
|
- lib/listen/adapter/windows.rb
|
77
|
+
- lib/listen/backend.rb
|
91
78
|
- lib/listen/change.rb
|
92
79
|
- lib/listen/cli.rb
|
93
80
|
- lib/listen/directory.rb
|
81
|
+
- lib/listen/event/config.rb
|
82
|
+
- lib/listen/event/loop.rb
|
83
|
+
- lib/listen/event/processor.rb
|
84
|
+
- lib/listen/event/queue.rb
|
94
85
|
- lib/listen/file.rb
|
95
|
-
- lib/listen/
|
86
|
+
- lib/listen/fsm.rb
|
96
87
|
- lib/listen/internals/thread_pool.rb
|
97
88
|
- lib/listen/listener.rb
|
89
|
+
- lib/listen/listener/config.rb
|
90
|
+
- lib/listen/logger.rb
|
98
91
|
- lib/listen/options.rb
|
99
92
|
- lib/listen/queue_optimizer.rb
|
100
93
|
- lib/listen/record.rb
|
101
94
|
- lib/listen/record/entry.rb
|
102
95
|
- lib/listen/record/symlink_detector.rb
|
103
96
|
- lib/listen/silencer.rb
|
104
|
-
- lib/listen/
|
105
|
-
- lib/listen/tcp/broadcaster.rb
|
106
|
-
- lib/listen/tcp/message.rb
|
97
|
+
- lib/listen/silencer/controller.rb
|
107
98
|
- lib/listen/version.rb
|
108
99
|
homepage: https://github.com/guard/listen
|
109
100
|
licenses:
|