listen 2.10.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|