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
@@ -5,18 +5,26 @@ module Listen
|
|
5
5
|
def _smoosh_changes(changes)
|
6
6
|
# TODO: adapter could be nil at this point (shutdown)
|
7
7
|
if _adapter_class.local_fs?
|
8
|
-
cookies = changes.group_by do |_, _, _, options|
|
8
|
+
cookies = changes.group_by do |_, _, _, _, options|
|
9
9
|
(options || {})[:cookie]
|
10
10
|
end
|
11
11
|
_squash_changes(_reinterpret_related_changes(cookies))
|
12
12
|
else
|
13
13
|
smooshed = { modified: [], added: [], removed: [] }
|
14
|
-
changes.each
|
14
|
+
changes.each do |_, change, dir, rel_path, _|
|
15
|
+
smooshed[change] << (dir + rel_path).to_s
|
16
|
+
end
|
15
17
|
smooshed.tap { |s| s.each { |_, v| v.uniq! } }
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
21
|
+
# groups changes into the expected structure expected by
|
22
|
+
# clients
|
19
23
|
def _squash_changes(changes)
|
24
|
+
# We combine here for backward compatibility
|
25
|
+
# Newer clients should receive dir and path separately
|
26
|
+
changes = changes.map { |change, dir, path| [change, dir + path] }
|
27
|
+
|
20
28
|
actions = changes.group_by(&:last).map do |path, action_list|
|
21
29
|
[_logical_action_for(path, action_list.map(&:first)), path.to_s]
|
22
30
|
end
|
@@ -63,15 +71,16 @@ module Listen
|
|
63
71
|
def _reinterpret_related_changes(cookies)
|
64
72
|
table = { moved_to: :added, moved_from: :removed }
|
65
73
|
cookies.map do |_, changes|
|
66
|
-
|
67
|
-
if
|
68
|
-
|
74
|
+
data = _detect_possible_editor_save(changes)
|
75
|
+
if data
|
76
|
+
to_dir, to_file = data
|
77
|
+
[[:modified, to_dir, to_file]]
|
69
78
|
else
|
70
|
-
not_silenced = changes.reject do |type, _, path, _|
|
71
|
-
_silenced?(path, type)
|
79
|
+
not_silenced = changes.reject do |type, _, _, path, _|
|
80
|
+
_silenced?(Pathname(path), type)
|
72
81
|
end
|
73
|
-
not_silenced.map do |_, change, path, _|
|
74
|
-
[table.fetch(change, change), path]
|
82
|
+
not_silenced.map do |_, change, dir, path, _|
|
83
|
+
[table.fetch(change, change), dir, path]
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end.flatten(1)
|
@@ -81,14 +90,14 @@ module Listen
|
|
81
90
|
return unless changes.size == 2
|
82
91
|
|
83
92
|
from_type = from_change = from = nil
|
84
|
-
to_type = to_change = to = nil
|
93
|
+
to_type = to_change = to_dir = to = nil
|
85
94
|
|
86
95
|
changes.each do |data|
|
87
96
|
case data[1]
|
88
97
|
when :moved_from
|
89
|
-
from_type, from_change, from, _ = data
|
98
|
+
from_type, from_change, _, from, _ = data
|
90
99
|
when :moved_to
|
91
|
-
to_type, to_change, to, _ = data
|
100
|
+
to_type, to_change, to_dir, to, _ = data
|
92
101
|
else
|
93
102
|
return nil
|
94
103
|
end
|
@@ -98,7 +107,8 @@ module Listen
|
|
98
107
|
|
99
108
|
# Expect an ignored moved_from and non-ignored moved_to
|
100
109
|
# to qualify as an "editor modify"
|
101
|
-
_silenced?(from, from_type)
|
110
|
+
return unless _silenced?(Pathname(from), from_type)
|
111
|
+
_silenced?(Pathname(to), to_type) ? nil : [to_dir, to]
|
102
112
|
end
|
103
113
|
end
|
104
114
|
end
|
data/lib/listen/record.rb
CHANGED
@@ -2,52 +2,129 @@ module Listen
|
|
2
2
|
class Record
|
3
3
|
include Celluloid
|
4
4
|
|
5
|
+
# TODO: one Record object per watched directory?
|
6
|
+
|
5
7
|
# TODO: deprecate
|
6
8
|
attr_accessor :paths, :listener
|
7
9
|
|
8
10
|
def initialize(listener)
|
9
11
|
@listener = listener
|
10
|
-
@paths =
|
12
|
+
@paths = _auto_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_dir(dir, rel_path)
|
16
|
+
return if [nil, '', '.'].include? rel_path
|
17
|
+
@paths[dir.to_s][rel_path] ||= {}
|
11
18
|
end
|
12
19
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
20
|
+
def update_file(dir, rel_path, data)
|
21
|
+
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
22
|
+
_fast_update_file(dir, dirname, basename, data)
|
16
23
|
end
|
17
24
|
|
18
|
-
def unset_path(
|
19
|
-
|
25
|
+
def unset_path(dir, rel_path)
|
26
|
+
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
27
|
+
|
28
|
+
@paths[dir.to_s][dirname] ||= {}
|
29
|
+
_fast_unset_path(dir, dirname, basename)
|
20
30
|
end
|
21
31
|
|
22
|
-
def file_data(
|
23
|
-
@paths[
|
32
|
+
def file_data(dir, rel_path)
|
33
|
+
root = @paths[dir.to_s]
|
34
|
+
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
35
|
+
if [nil, '', '.'].include? dirname
|
36
|
+
root[basename] ||= {}
|
37
|
+
root[basename].dup
|
38
|
+
else
|
39
|
+
root[dirname] ||= {}
|
40
|
+
root[dirname][basename] ||= {}
|
41
|
+
root[dirname][basename].dup
|
42
|
+
end
|
24
43
|
end
|
25
44
|
|
26
|
-
def dir_entries(
|
27
|
-
|
45
|
+
def dir_entries(dir, rel_path)
|
46
|
+
tree = if [nil, '', '.'].include? rel_path.to_s
|
47
|
+
@paths[dir.to_s]
|
48
|
+
else
|
49
|
+
@paths[dir.to_s][rel_path.to_s] ||= _auto_hash
|
50
|
+
@paths[dir.to_s][rel_path.to_s]
|
51
|
+
end
|
52
|
+
|
53
|
+
result = {}
|
54
|
+
tree.each do |key, values|
|
55
|
+
# only get data for file entries
|
56
|
+
result[key] = values.key?(:mtime) ? values : {}
|
57
|
+
end
|
58
|
+
result
|
28
59
|
end
|
29
60
|
|
30
61
|
def build
|
31
|
-
|
32
|
-
@paths =
|
33
|
-
|
34
|
-
|
35
|
-
|
62
|
+
start = Time.now.to_f
|
63
|
+
@paths = _auto_hash
|
64
|
+
|
65
|
+
# TODO: refactor this out (1 Record = 1 watched dir)
|
66
|
+
listener.directories.each do |directory|
|
67
|
+
_fast_build(directory.to_s)
|
36
68
|
end
|
37
|
-
|
69
|
+
|
70
|
+
Celluloid.logger.info "Record.build took #{Time.now.to_f - start} seconds"
|
38
71
|
rescue
|
39
72
|
Celluloid.logger.warn "build crashed: #{$!.inspect}"
|
40
73
|
raise
|
41
74
|
end
|
42
75
|
|
43
|
-
def still_building!
|
44
|
-
@last_build_at = Time.now
|
45
|
-
end
|
46
|
-
|
47
76
|
private
|
48
77
|
|
49
|
-
def
|
78
|
+
def _auto_hash
|
50
79
|
Hash.new { |h, k| h[k] = Hash.new }
|
51
80
|
end
|
81
|
+
|
82
|
+
def _fast_update_file(dir, dirname, basename, data)
|
83
|
+
root = @paths[dir.to_s]
|
84
|
+
if [nil, '', '.'].include? dirname
|
85
|
+
root[basename] = (root[basename] || {}).merge(data)
|
86
|
+
else
|
87
|
+
root[dirname] ||= {}
|
88
|
+
root[dirname][basename] = (root[dirname][basename] || {}).merge(data)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def _fast_unset_path(dir, dirname, basename)
|
93
|
+
root = @paths[dir.to_s]
|
94
|
+
# this may need to be reworked to properly remove
|
95
|
+
# entries from a tree, without adding non-existing dirs to the record
|
96
|
+
return unless root.key?(dirname)
|
97
|
+
root[dirname].delete(basename)
|
98
|
+
end
|
99
|
+
|
100
|
+
def _fast_build(root)
|
101
|
+
@paths[root] = _auto_hash
|
102
|
+
left = Queue.new
|
103
|
+
left << '.'
|
104
|
+
|
105
|
+
while !left.empty?
|
106
|
+
dirname = left.pop
|
107
|
+
add_dir(root, dirname)
|
108
|
+
|
109
|
+
path = ::File.join(root, dirname)
|
110
|
+
current = Dir.entries(path.to_s) - %w(. ..)
|
111
|
+
|
112
|
+
current.each do |entry|
|
113
|
+
full_path = ::File.join(path, entry)
|
114
|
+
|
115
|
+
if Dir.exist?(full_path)
|
116
|
+
left << (dirname == '.' ? entry : ::File.join(dirname, entry))
|
117
|
+
else
|
118
|
+
begin
|
119
|
+
lstat = ::File.lstat(full_path)
|
120
|
+
data = { mtime: lstat.mtime.to_f, mode: lstat.mode }
|
121
|
+
_fast_update_file(root, dirname, entry, data)
|
122
|
+
rescue SystemCallError
|
123
|
+
_fast_unset_path(root, dirname, entry)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
52
129
|
end
|
53
130
|
end
|
data/lib/listen/silencer.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Listen
|
2
2
|
class Silencer
|
3
|
-
include Celluloid
|
4
|
-
|
5
3
|
# The default list of directories that get ignored.
|
6
4
|
DEFAULT_IGNORED_DIRECTORIES = %r{^(?:
|
7
5
|
\.git
|
@@ -40,63 +38,46 @@ module Listen
|
|
40
38
|
| ~
|
41
39
|
)$}x
|
42
40
|
|
43
|
-
attr_accessor :
|
41
|
+
attr_accessor :only_patterns, :ignore_patterns
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
configure({})
|
45
|
+
end
|
44
46
|
|
45
|
-
def
|
46
|
-
@
|
47
|
-
|
48
|
-
_init_ignore_patterns
|
47
|
+
def configure(options)
|
48
|
+
@only_patterns = options[:only] ? Array(options[:only]) : nil
|
49
|
+
@ignore_patterns = _init_ignores(options[:ignore], options[:ignore!])
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
-
|
52
|
+
# Note: relative_path is temporarily expected to be a relative Pathname to
|
53
|
+
# make refactoring easier (ideally, it would take a string)
|
53
54
|
|
54
|
-
|
55
|
+
# TODO: switch type and path places - and verify
|
56
|
+
def silenced?(relative_path, type)
|
57
|
+
path = relative_path.to_s
|
55
58
|
|
56
59
|
if only_patterns && type == :file
|
57
|
-
|
60
|
+
return true unless only_patterns.any? { |pattern| path =~ pattern }
|
58
61
|
end
|
59
62
|
|
60
|
-
|
63
|
+
ignore_patterns.any? { |pattern| path =~ pattern }
|
61
64
|
end
|
62
65
|
|
63
66
|
private
|
64
67
|
|
65
|
-
|
66
|
-
return unless listener.options[:only]
|
67
|
-
|
68
|
-
@only_patterns = Array(listener.options[:only])
|
69
|
-
end
|
70
|
-
|
71
|
-
def _init_ignore_patterns
|
72
|
-
options = listener.options
|
68
|
+
attr_reader :options
|
73
69
|
|
70
|
+
def _init_ignores(ignores, overrides)
|
74
71
|
patterns = []
|
75
|
-
unless
|
72
|
+
unless overrides
|
76
73
|
patterns << DEFAULT_IGNORED_DIRECTORIES
|
77
74
|
patterns << DEFAULT_IGNORED_EXTENSIONS
|
78
75
|
end
|
79
76
|
|
80
|
-
patterns <<
|
81
|
-
patterns <<
|
82
|
-
|
83
|
-
patterns.compact!
|
84
|
-
patterns.flatten!
|
77
|
+
patterns << ignores
|
78
|
+
patterns << overrides
|
85
79
|
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
def _relative_path(path)
|
90
|
-
relative_paths = listener.directories.map do |dir|
|
91
|
-
begin
|
92
|
-
path.relative_path_from(dir).to_s
|
93
|
-
rescue ArgumentError
|
94
|
-
# Windows raises errors across drives, e.g. when 'C:/' and 'E:/dir'
|
95
|
-
# So, here's a Dirty hack to fool the detect() below..
|
96
|
-
'../'
|
97
|
-
end
|
98
|
-
end
|
99
|
-
relative_paths.detect { |rel_path| !rel_path.start_with?('../') }
|
80
|
+
patterns.compact.flatten
|
100
81
|
end
|
101
82
|
end
|
102
83
|
end
|
data/lib/listen/tcp.rb
CHANGED
File without changes
|
File without changes
|
data/lib/listen/tcp/message.rb
CHANGED
File without changes
|
data/lib/listen/version.rb
CHANGED
data/listen.gemspec
CHANGED
File without changes
|
File without changes
|
data/spec/acceptance/tcp_spec.rb
CHANGED
File without changes
|
@@ -1,29 +1,30 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
subject { described_class.new(listener) }
|
3
|
+
include Listen
|
5
4
|
|
6
|
-
|
5
|
+
describe Adapter::Base do
|
7
6
|
|
8
|
-
|
7
|
+
class FakeAdapter < described_class
|
8
|
+
def initialize(*args)
|
9
|
+
super(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { FakeAdapter.new(mq: mq, directories: []) }
|
14
|
+
|
15
|
+
let(:mq) { instance_double(Listener) }
|
9
16
|
|
10
17
|
describe '#_notify_change' do
|
18
|
+
let(:dir) { Pathname.pwd }
|
19
|
+
|
11
20
|
context 'listener is listening or paused' do
|
12
|
-
let(:worker) { instance_double(
|
21
|
+
let(:worker) { instance_double(Change) }
|
13
22
|
|
14
23
|
it 'calls change on change_pool asynchronously' do
|
15
|
-
expect(
|
16
|
-
with(:dir, 'path', recursive: true)
|
17
|
-
subject.send(:_notify_change, :dir, 'path', recursive: true)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
context 'listener is stopped' do
|
22
|
-
let(:worker) { nil }
|
24
|
+
expect(mq).to receive(:_queue_raw_change).
|
25
|
+
with(:dir, dir, 'path', recursive: true)
|
23
26
|
|
24
|
-
|
25
|
-
expect(worker).to_not receive(:change)
|
26
|
-
subject.send(:_notify_change, :dir, 'path', recursive: true)
|
27
|
+
subject.send(:_queue_change, :dir, dir, 'path', recursive: true)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
File without changes
|
@@ -1,6 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
3
|
+
require 'listen/adapter/darwin'
|
4
|
+
|
5
|
+
include Listen
|
6
|
+
|
7
|
+
describe Adapter::Darwin do
|
4
8
|
describe 'class' do
|
5
9
|
subject { described_class }
|
6
10
|
it { should be_local_fs }
|
@@ -13,13 +17,16 @@ describe Listen::Adapter::Darwin do
|
|
13
17
|
end
|
14
18
|
|
15
19
|
let(:options) { {} }
|
16
|
-
let(:
|
20
|
+
let(:mq) { instance_double(Listener, options: options) }
|
17
21
|
|
18
22
|
describe '#_latency' do
|
19
|
-
subject
|
23
|
+
subject do
|
24
|
+
adapter = described_class.new(options.merge(mq: mq, directories: []))
|
25
|
+
adapter.options.latency
|
26
|
+
end
|
20
27
|
|
21
28
|
context 'with no overriding option' do
|
22
|
-
it { should eq
|
29
|
+
it { should eq 0.1 }
|
23
30
|
end
|
24
31
|
|
25
32
|
context 'with custom latency overriding' do
|
@@ -13,26 +13,17 @@ describe Listen::Adapter::Linux do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
if linux?
|
16
|
-
let(:
|
17
|
-
let(:
|
16
|
+
let(:directories) { [] }
|
17
|
+
let(:mq) { instance_double(Listen::Listener) }
|
18
18
|
|
19
|
-
|
20
|
-
before do
|
21
|
-
allow(listener).to receive(:directories) { [] }
|
22
|
-
end
|
23
|
-
it 'requires rb-inotify gem' do
|
24
|
-
adapter.send(:_configure)
|
25
|
-
expect(defined?(INotify)).to be
|
26
|
-
end
|
27
|
-
end
|
19
|
+
subject { described_class.new(mq: mq, directories: directories) }
|
28
20
|
|
29
21
|
# workaround: Celluloid ignores SystemExit exception messages
|
30
22
|
describe 'inotify limit message' do
|
31
|
-
let
|
23
|
+
let(:directories) { [Pathname.pwd] }
|
32
24
|
|
33
25
|
before do
|
34
26
|
require 'rb-inotify'
|
35
|
-
allow(listener).to receive(:directories) { ['foo/dir'] }
|
36
27
|
fake_worker = double(:fake_worker)
|
37
28
|
allow(fake_worker).to receive(:watch).and_raise(Errno::ENOSPC)
|
38
29
|
|
@@ -46,23 +37,21 @@ describe Listen::Adapter::Linux do
|
|
46
37
|
|
47
38
|
# Expect RuntimeError here, for the sake of unit testing (actual
|
48
39
|
# handling depends on Celluloid supervisor setup, which is beyond the
|
49
|
-
# scope of
|
50
|
-
expect {
|
40
|
+
# scope of subject tests)
|
41
|
+
expect { subject.start }.to raise_error RuntimeError, expected_message
|
51
42
|
end
|
52
43
|
end
|
53
44
|
|
54
45
|
describe '_callback' do
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
46
|
+
let(:directories) { [Pathname.pwd] }
|
47
|
+
before { subject.configure }
|
59
48
|
let(:expect_change) do
|
60
49
|
lambda do |change|
|
61
|
-
|
62
|
-
to receive(:_notify_change).
|
50
|
+
allow(mq).to receive(:_queue_raw_change).
|
63
51
|
with(
|
64
52
|
:file,
|
65
|
-
Pathname.
|
53
|
+
Pathname.pwd,
|
54
|
+
'path/foo.txt',
|
66
55
|
change: change,
|
67
56
|
cookie: 123)
|
68
57
|
end
|
@@ -70,13 +59,15 @@ describe Listen::Adapter::Linux do
|
|
70
59
|
|
71
60
|
let(:event_callback) do
|
72
61
|
lambda do |flags|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
62
|
+
callbacks = subject.instance_variable_get(:'@callbacks')
|
63
|
+
callbacks.values.flatten.each do |callback|
|
64
|
+
callback.call double(
|
65
|
+
:inotify_event,
|
66
|
+
name: 'foo.txt',
|
67
|
+
watcher: double(:watcher, path: (Pathname.pwd + 'path').to_s),
|
68
|
+
flags: flags,
|
69
|
+
cookie: 123)
|
70
|
+
end
|
80
71
|
end
|
81
72
|
end
|
82
73
|
|