listen 2.4.1 → 2.5.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 +30 -0
- data/lib/listen.rb +26 -1
- data/lib/listen/adapter.rb +2 -0
- data/lib/listen/adapter/tcp.rb +64 -0
- data/lib/listen/tcp/broadcaster.rb +72 -0
- data/lib/listen/tcp/listener.rb +103 -0
- data/lib/listen/tcp/message.rb +52 -0
- data/lib/listen/version.rb +1 -1
- data/listen.gemspec +1 -0
- data/spec/acceptance/tcp_spec.rb +143 -0
- data/spec/lib/listen/adapter/tcp_spec.rb +110 -0
- data/spec/lib/listen/adapter_spec.rb +5 -0
- data/spec/lib/listen/tcp/broadcaster_spec.rb +114 -0
- data/spec/lib/listen/tcp/listener_spec.rb +142 -0
- data/spec/lib/listen/tcp/message_spec.rb +104 -0
- data/spec/lib/listen_spec.rb +14 -0
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 042d2544c103c6d5dfbf379cfc2c9f3f6bf84f3e
|
4
|
+
data.tar.gz: dc1649706fbc543ea1cce0ef66f004f27f785152
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d97f0b9f72731fe5675324aee96cc6671efd538bec2207f3b3386c5fb98e3aab1f85251f36b8f8a12447a2932c7ae0dfb9220bb37e3fd369326280037ead5d87
|
7
|
+
data.tar.gz: 5facee3fb794d2c49670a783119d434c589da911b12dd4b32445808e8b8750e19513ba57045da4ce6e18c38817e3b31d26bd219bb8b7d402523bb0e66544adba
|
data/README.md
CHANGED
@@ -11,6 +11,7 @@ The Listen gem listens to file modifications and notifies you about the changes.
|
|
11
11
|
* Detects file modification, addition and removal.
|
12
12
|
* Allows supplying regexp-patterns to ignore paths for better results.
|
13
13
|
* File content checksum comparison for modifications made under the same second (OS X only).
|
14
|
+
* Forwarding file events over TCP, [more info](#forwarding-file-events-over-tcp) below.
|
14
15
|
* Tested on MRI Ruby environments (1.9+ only) via [Travis CI](https://travis-ci.org/guard/listen),
|
15
16
|
|
16
17
|
Please note that:
|
@@ -192,6 +193,35 @@ Here are some things you could try to avoid forcing polling.
|
|
192
193
|
|
193
194
|
If your application keeps using the polling-adapter and you can't figure out why, feel free to [open an issue](https://github.com/guard/listen/issues/new) (and be sure to [give all the details](https://github.com/guard/listen/blob/master/CONTRIBUTING.md)).
|
194
195
|
|
196
|
+
## Forwarding file events over TCP
|
197
|
+
|
198
|
+
Listen is capable of forwarding file events over the network using a messaging protocol. This can be useful for virtualized development environments when file events are unavailable, as is the case with [Vagrant](https://github.com/mitchellh/vagrant).
|
199
|
+
|
200
|
+
To broadcast events over TCP, use the `forward_to` option with an address - just a port or a hostname/port combination:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
listener = Listen.to 'path/to/app', forward_to: '10.0.0.2:4000' do |modified, added, removed|
|
204
|
+
# After broadcasting the changes to any connected recipients,
|
205
|
+
# this block will still be called
|
206
|
+
end
|
207
|
+
listener.start
|
208
|
+
sleep
|
209
|
+
```
|
210
|
+
|
211
|
+
To connect to a broadcasting listener as a recipient, specify its address using `Listen.on`:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
listener = Listen.on '10.0.0.2:4000' do |modified, added, removed|
|
215
|
+
# This block will be called
|
216
|
+
end
|
217
|
+
listener.start
|
218
|
+
sleep
|
219
|
+
```
|
220
|
+
|
221
|
+
### Security considerations
|
222
|
+
|
223
|
+
Since file events potentially expose sensitive information, care must be taken when specifying the broadcaster address. It is recommended to **always** specify a hostname and make sure it is as specific as possible to reduce any undesirable eavesdropping.
|
224
|
+
|
195
225
|
## Development
|
196
226
|
|
197
227
|
* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/listen/master/frames).
|
data/lib/listen.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'celluloid'
|
2
2
|
require 'listen/listener'
|
3
|
+
require 'listen/tcp/listener'
|
3
4
|
|
4
5
|
module Listen
|
5
6
|
class << self
|
@@ -7,6 +8,8 @@ module Listen
|
|
7
8
|
|
8
9
|
# Listens to file system modifications on a either single directory or multiple directories.
|
9
10
|
#
|
11
|
+
# When :forward_to is specified, this listener will broadcast modifications over TCP.
|
12
|
+
#
|
10
13
|
# @param (see Listen::Listener#new)
|
11
14
|
#
|
12
15
|
# @yield [modified, added, removed] the changed files
|
@@ -18,7 +21,12 @@ module Listen
|
|
18
21
|
#
|
19
22
|
def to(*args, &block)
|
20
23
|
@stopping = false
|
21
|
-
|
24
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
25
|
+
if target = options.delete(:forward_to)
|
26
|
+
TCP::Listener.new(target, :broadcaster, *args, &block)
|
27
|
+
else
|
28
|
+
Listener.new(*args, &block)
|
29
|
+
end
|
22
30
|
end
|
23
31
|
|
24
32
|
# Stop all listeners
|
@@ -26,5 +34,22 @@ module Listen
|
|
26
34
|
def stop
|
27
35
|
@stopping = true
|
28
36
|
end
|
37
|
+
|
38
|
+
# Listens to file system modifications broadcast over TCP.
|
39
|
+
#
|
40
|
+
# @param [String/Fixnum] target to listen on (hostname:port or port)
|
41
|
+
#
|
42
|
+
# @yield [modified, added, removed] the changed files
|
43
|
+
# @yieldparam [Array<String>] modified the list of modified files
|
44
|
+
# @yieldparam [Array<String>] added the list of added files
|
45
|
+
# @yieldparam [Array<String>] removed the list of removed files
|
46
|
+
#
|
47
|
+
# @return [Listen::Listener] the listener
|
48
|
+
#
|
49
|
+
def on(target, *args, &block)
|
50
|
+
TCP::Listener.new(target, :recipient, *args, &block)
|
51
|
+
end
|
52
|
+
|
29
53
|
end
|
54
|
+
|
30
55
|
end
|
data/lib/listen/adapter.rb
CHANGED
@@ -3,6 +3,7 @@ require 'listen/adapter/bsd'
|
|
3
3
|
require 'listen/adapter/darwin'
|
4
4
|
require 'listen/adapter/linux'
|
5
5
|
require 'listen/adapter/polling'
|
6
|
+
require 'listen/adapter/tcp'
|
6
7
|
require 'listen/adapter/windows'
|
7
8
|
|
8
9
|
module Listen
|
@@ -11,6 +12,7 @@ module Listen
|
|
11
12
|
POLLING_FALLBACK_MESSAGE = "Listen will be polling for changes. Learn more at https://github.com/guard/listen#polling-fallback."
|
12
13
|
|
13
14
|
def self.select(options = {})
|
15
|
+
return TCP if options[:force_tcp]
|
14
16
|
return Polling if options[:force_polling]
|
15
17
|
return _usable_adapter_class if _usable_adapter_class
|
16
18
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'celluloid/io'
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
# Adapter to receive file system modifications over TCP
|
7
|
+
class TCP < Base
|
8
|
+
include Celluloid::IO
|
9
|
+
|
10
|
+
finalizer :finalize
|
11
|
+
|
12
|
+
attr_reader :buffer, :socket
|
13
|
+
|
14
|
+
def self.usable?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
# Initializes and starts a Celluloid::IO-powered TCP-recipient
|
19
|
+
def start
|
20
|
+
@socket = TCPSocket.new(listener.host, listener.port)
|
21
|
+
@buffer = String.new
|
22
|
+
run
|
23
|
+
end
|
24
|
+
|
25
|
+
# Cleans up buffer and socket
|
26
|
+
def finalize
|
27
|
+
@buffer = nil
|
28
|
+
if @socket
|
29
|
+
@socket.close
|
30
|
+
@socket = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Number of bytes to receive at a time
|
35
|
+
RECEIVE_WINDOW = 1024
|
36
|
+
|
37
|
+
# Continuously receive and asynchronously handle data
|
38
|
+
def run
|
39
|
+
while data = @socket.recv(RECEIVE_WINDOW)
|
40
|
+
async.handle_data(data)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Buffers incoming data and handles messages accordingly
|
45
|
+
def handle_data(data)
|
46
|
+
@buffer << data
|
47
|
+
while message = Listen::TCP::Message.from_buffer(@buffer)
|
48
|
+
handle_message(message)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Handles incoming message by notifying of path changes
|
53
|
+
def handle_message(message)
|
54
|
+
message.object.each do |change, paths|
|
55
|
+
paths.each do |path|
|
56
|
+
_notify_change(path, change: change.to_sym)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'celluloid/io'
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module TCP
|
5
|
+
class Broadcaster
|
6
|
+
include Celluloid::IO
|
7
|
+
|
8
|
+
finalizer :finalize
|
9
|
+
|
10
|
+
attr_reader :server, :sockets
|
11
|
+
|
12
|
+
# Initializes a Celluloid::IO-powered TCP-broadcaster
|
13
|
+
#
|
14
|
+
# @param [String] host to broadcast on
|
15
|
+
# @param [String] port to broadcast on
|
16
|
+
#
|
17
|
+
# Note: Listens on all addresses when host is nil
|
18
|
+
#
|
19
|
+
def initialize(host, port)
|
20
|
+
@server = TCPServer.new(host, port)
|
21
|
+
@sockets = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Asynchronously start accepting connections
|
25
|
+
def start
|
26
|
+
async.run
|
27
|
+
end
|
28
|
+
|
29
|
+
# Cleans up sockets and server
|
30
|
+
def finalize
|
31
|
+
if @server
|
32
|
+
@sockets.clear
|
33
|
+
@server.close
|
34
|
+
@server = nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Broadcasts given payload to all connected sockets
|
39
|
+
def broadcast(payload)
|
40
|
+
@sockets.each do |socket|
|
41
|
+
unicast(socket, payload)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Unicasts payload to given socket
|
46
|
+
#
|
47
|
+
# @return [Boolean] whether writing to socket was succesful
|
48
|
+
#
|
49
|
+
def unicast(socket, payload)
|
50
|
+
socket.write(payload)
|
51
|
+
true
|
52
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE
|
53
|
+
@sockets.delete(socket)
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Continuously accept and handle incoming connections
|
58
|
+
def run
|
59
|
+
while socket = @server.accept
|
60
|
+
handle_connection(socket)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Handles incoming socket connection
|
65
|
+
def handle_connection(socket)
|
66
|
+
@sockets << socket
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'listen/tcp/broadcaster'
|
2
|
+
require 'listen/tcp/message'
|
3
|
+
|
4
|
+
module Listen
|
5
|
+
module TCP
|
6
|
+
class Listener < Listen::Listener
|
7
|
+
|
8
|
+
DEFAULT_HOST = 'localhost'
|
9
|
+
|
10
|
+
attr_reader :host, :mode, :port
|
11
|
+
|
12
|
+
# Initializes a listener to broadcast or receive modifications over TCP
|
13
|
+
#
|
14
|
+
# @param [String/Fixnum] target to listen on (hostname:port or port)
|
15
|
+
# @param [Symbol] mode (either :broadcaster or :recipient)
|
16
|
+
#
|
17
|
+
# @param (see Listen::Listener#new)
|
18
|
+
#
|
19
|
+
def initialize(target, mode, *args, &block)
|
20
|
+
self.mode = mode
|
21
|
+
self.target = target
|
22
|
+
|
23
|
+
super *args, &block
|
24
|
+
end
|
25
|
+
|
26
|
+
def broadcaster?
|
27
|
+
@mode == :broadcaster
|
28
|
+
end
|
29
|
+
|
30
|
+
def recipient?
|
31
|
+
@mode == :recipient
|
32
|
+
end
|
33
|
+
|
34
|
+
# Initializes and starts TCP broadcaster
|
35
|
+
def start
|
36
|
+
super
|
37
|
+
if broadcaster?
|
38
|
+
supervisor.add(Broadcaster, as: :broadcaster, args: [host, port])
|
39
|
+
registry[:broadcaster].start
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Hook to broadcast changes over TCP
|
44
|
+
def block
|
45
|
+
if broadcaster?
|
46
|
+
Proc.new { |modified, added, removed|
|
47
|
+
|
48
|
+
# Honour paused and stopped states
|
49
|
+
next if @paused || @stopping
|
50
|
+
|
51
|
+
# Broadcast changes as a hash (see Listen::Adapter::TCP#handle_message)
|
52
|
+
message = Message.new(modified: modified, added: added, removed: removed)
|
53
|
+
registry[:broadcaster].async.broadcast(message.payload)
|
54
|
+
|
55
|
+
# Invoke the original callback block
|
56
|
+
@block.call(modified, added, removed) if @block
|
57
|
+
}
|
58
|
+
else
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def _init_options(options = {})
|
66
|
+
options = super(options)
|
67
|
+
options[:force_tcp] = true if recipient?
|
68
|
+
options
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets listener mode
|
72
|
+
#
|
73
|
+
# @param [Symbol] mode (either :broadcaster or :recipient)
|
74
|
+
#
|
75
|
+
def mode=(mode)
|
76
|
+
unless [:broadcaster, :recipient].include? mode
|
77
|
+
raise ArgumentError, 'TCP::Listener requires mode to be either :broadcaster or :recipient'
|
78
|
+
end
|
79
|
+
@mode = mode
|
80
|
+
end
|
81
|
+
|
82
|
+
# Sets listener target
|
83
|
+
#
|
84
|
+
# @param [String/Fixnum] target to listen on (hostname:port or port)
|
85
|
+
#
|
86
|
+
def target=(target)
|
87
|
+
unless target
|
88
|
+
raise ArgumentError, 'TCP::Listener requires target to be given'
|
89
|
+
end
|
90
|
+
|
91
|
+
@host = DEFAULT_HOST if recipient?
|
92
|
+
|
93
|
+
if target.is_a? Fixnum
|
94
|
+
@port = target
|
95
|
+
else
|
96
|
+
@host, @port = target.split(':')
|
97
|
+
@port = @port.to_i
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module TCP
|
5
|
+
class Message
|
6
|
+
|
7
|
+
attr_reader :body, :object, :payload, :size
|
8
|
+
|
9
|
+
HEADER_SIZE = 4
|
10
|
+
HEADER_FORMAT = 'N'
|
11
|
+
PAYLOAD_FORMAT = "#{HEADER_FORMAT}a*"
|
12
|
+
|
13
|
+
# Initializes a new message
|
14
|
+
#
|
15
|
+
# @param [Object] object to initialize message with
|
16
|
+
#
|
17
|
+
def initialize(object = nil)
|
18
|
+
self.object = object if object
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generates message size and payload for given object
|
22
|
+
def object=(obj)
|
23
|
+
@object = obj
|
24
|
+
@body = JSON.generate(@object)
|
25
|
+
@size = @body.bytesize
|
26
|
+
@payload = [@size, @body].pack(PAYLOAD_FORMAT)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Extracts message size and loads object from given payload
|
30
|
+
def payload=(payload)
|
31
|
+
@payload = payload
|
32
|
+
@size, @body = @payload.unpack(PAYLOAD_FORMAT)
|
33
|
+
@object = JSON.parse(@body)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Extracts a message from given buffer
|
37
|
+
def self.from_buffer(buffer)
|
38
|
+
if buffer.bytesize > HEADER_SIZE
|
39
|
+
size = buffer.unpack(HEADER_FORMAT).first
|
40
|
+
payload_size = HEADER_SIZE + size
|
41
|
+
if buffer.bytesize >= payload_size
|
42
|
+
payload = buffer.slice!(0...payload_size)
|
43
|
+
new.tap do |message|
|
44
|
+
message.payload = payload
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/listen/version.rb
CHANGED
data/listen.gemspec
CHANGED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Listen::TCP do
|
4
|
+
|
5
|
+
let(:port) { 4000 }
|
6
|
+
|
7
|
+
let(:broadcaster) { Listen.to(Dir.pwd, forward_to: port) }
|
8
|
+
let(:recipient) { Listen.on(port) }
|
9
|
+
let(:callback) { ->(modified, added, removed) {
|
10
|
+
add_changes(:modified, modified)
|
11
|
+
add_changes(:added, added)
|
12
|
+
add_changes(:removed, removed)
|
13
|
+
} }
|
14
|
+
let(:paths) { Pathname.new(Dir.pwd) }
|
15
|
+
|
16
|
+
around { |example| fixtures { |path| example.run } }
|
17
|
+
|
18
|
+
before do
|
19
|
+
broadcaster.start
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when broadcaster' do
|
23
|
+
before do
|
24
|
+
broadcaster.block = callback
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'still handles local changes' do
|
28
|
+
expect(listen {
|
29
|
+
touch 'file.rb'
|
30
|
+
}).to eq(
|
31
|
+
modified: [],
|
32
|
+
added: ['file.rb'],
|
33
|
+
removed: []
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'may be paused and unpaused' do
|
38
|
+
broadcaster.pause
|
39
|
+
|
40
|
+
expect(listen {
|
41
|
+
touch 'file.rb'
|
42
|
+
}).to eq(
|
43
|
+
modified: [],
|
44
|
+
added: [],
|
45
|
+
removed: []
|
46
|
+
)
|
47
|
+
|
48
|
+
broadcaster.unpause
|
49
|
+
|
50
|
+
expect(listen {
|
51
|
+
touch 'file.rb'
|
52
|
+
}).to eq(
|
53
|
+
modified: ['file.rb'],
|
54
|
+
added: [],
|
55
|
+
removed: []
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'may be stopped and restarted' do
|
60
|
+
broadcaster.stop
|
61
|
+
|
62
|
+
expect(listen {
|
63
|
+
touch 'file.rb'
|
64
|
+
}).to eq(
|
65
|
+
modified: [],
|
66
|
+
added: [],
|
67
|
+
removed: []
|
68
|
+
)
|
69
|
+
|
70
|
+
broadcaster.start
|
71
|
+
|
72
|
+
expect(listen {
|
73
|
+
touch 'file.rb'
|
74
|
+
}).to eq(
|
75
|
+
modified: ['file.rb'],
|
76
|
+
added: [],
|
77
|
+
removed: []
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when recipient' do
|
83
|
+
before do
|
84
|
+
recipient.start
|
85
|
+
recipient.block = callback
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'receives changes over TCP' do
|
89
|
+
expect(listen {
|
90
|
+
touch 'file.rb'
|
91
|
+
}).to eq(
|
92
|
+
modified: [],
|
93
|
+
added: ['file.rb'],
|
94
|
+
removed: []
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'may be paused and unpaused' do
|
99
|
+
recipient.pause
|
100
|
+
|
101
|
+
expect(listen {
|
102
|
+
touch 'file.rb'
|
103
|
+
}).to eq(
|
104
|
+
modified: [],
|
105
|
+
added: [],
|
106
|
+
removed: []
|
107
|
+
)
|
108
|
+
|
109
|
+
recipient.unpause
|
110
|
+
|
111
|
+
expect(listen {
|
112
|
+
touch 'file.rb'
|
113
|
+
}).to eq(
|
114
|
+
modified: ['file.rb'],
|
115
|
+
added: [],
|
116
|
+
removed: []
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'may be stopped and restarted' do
|
121
|
+
recipient.stop
|
122
|
+
|
123
|
+
expect(listen {
|
124
|
+
touch 'file.rb'
|
125
|
+
}).to eq(
|
126
|
+
modified: [],
|
127
|
+
added: [],
|
128
|
+
removed: []
|
129
|
+
)
|
130
|
+
|
131
|
+
recipient.start
|
132
|
+
|
133
|
+
expect(listen {
|
134
|
+
touch 'file.rb'
|
135
|
+
}).to eq(
|
136
|
+
modified: ['file.rb'],
|
137
|
+
added: [],
|
138
|
+
removed: []
|
139
|
+
)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Listen::Adapter::TCP do
|
4
|
+
|
5
|
+
let(:host) { '10.0.0.2' }
|
6
|
+
let(:port) { 4000 }
|
7
|
+
|
8
|
+
subject { described_class.new(listener) }
|
9
|
+
let(:registry) { double(Celluloid::Registry) }
|
10
|
+
let(:listener) { double(Listen::TCP::Listener, registry: registry, options: {}, host: host, port: port) }
|
11
|
+
let(:socket) { double(described_class::TCPSocket, close: true, recv: nil) }
|
12
|
+
|
13
|
+
before do
|
14
|
+
described_class::TCPSocket.stub(:new).and_return socket
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
subject.terminate
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '.usable?' do
|
22
|
+
it 'always returns true' do
|
23
|
+
expect(described_class).to be_usable
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#start' do
|
28
|
+
it 'initializes and exposes a socket with listener host and port' do
|
29
|
+
expect(described_class::TCPSocket).to receive(:new).with listener.host, listener.port
|
30
|
+
subject.start
|
31
|
+
expect(subject.socket).to be socket
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'initializes and exposes a string buffer' do
|
35
|
+
subject.start
|
36
|
+
expect(subject.buffer).to eq ''
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'invokes run loop' do
|
40
|
+
expect(subject.wrapped_object).to receive(:run)
|
41
|
+
subject.start
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#finalize' do
|
46
|
+
it 'clears buffer' do
|
47
|
+
subject.start
|
48
|
+
subject.finalize
|
49
|
+
expect(subject.buffer).to be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'closes socket' do
|
53
|
+
subject.start
|
54
|
+
expect(subject.socket).to receive(:close)
|
55
|
+
subject.finalize
|
56
|
+
expect(subject.socket).to be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#run' do
|
61
|
+
let(:async) { double('TCP-adapter async', handle_data: true) }
|
62
|
+
|
63
|
+
it 'handles data from socket' do
|
64
|
+
socket.stub(:recv).and_return 'foo', 'bar', nil
|
65
|
+
subject.stub(:async).and_return async
|
66
|
+
|
67
|
+
expect(async).to receive(:handle_data).with 'foo'
|
68
|
+
expect(async).to receive(:handle_data).with 'bar'
|
69
|
+
|
70
|
+
subject.start
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#handle_data' do
|
75
|
+
it 'buffers data' do
|
76
|
+
subject.start
|
77
|
+
subject.handle_data 'foo'
|
78
|
+
subject.handle_data 'bar'
|
79
|
+
expect(subject.buffer).to eq 'foobar'
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'handles messages accordingly' do
|
83
|
+
message = Listen::TCP::Message.new
|
84
|
+
|
85
|
+
Listen::TCP::Message.stub(:from_buffer).and_return message, nil
|
86
|
+
expect(Listen::TCP::Message).to receive(:from_buffer).with 'foo'
|
87
|
+
expect(subject.wrapped_object).to receive(:handle_message).with message
|
88
|
+
|
89
|
+
subject.start
|
90
|
+
subject.handle_data 'foo'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#handle_message' do
|
95
|
+
it 'notifies listener of path changes' do
|
96
|
+
message = Listen::TCP::Message.new(
|
97
|
+
'modified' => ['/foo', '/bar'],
|
98
|
+
'added' => ['/baz'],
|
99
|
+
'removed' => []
|
100
|
+
)
|
101
|
+
|
102
|
+
expect(subject.wrapped_object).to receive(:_notify_change).with '/foo', change: :modified
|
103
|
+
expect(subject.wrapped_object).to receive(:_notify_change).with '/bar', change: :modified
|
104
|
+
expect(subject.wrapped_object).to receive(:_notify_change).with '/baz', change: :added
|
105
|
+
|
106
|
+
subject.handle_message message
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -10,6 +10,11 @@ describe Listen::Adapter do
|
|
10
10
|
}
|
11
11
|
|
12
12
|
describe ".select" do
|
13
|
+
it 'returns TCP adapter when requested' do
|
14
|
+
klass = Listen::Adapter.select(force_tcp: true)
|
15
|
+
expect(klass).to eq Listen::Adapter::TCP
|
16
|
+
end
|
17
|
+
|
13
18
|
it "returns Polling adapter if forced" do
|
14
19
|
klass = Listen::Adapter.select(force_polling: true)
|
15
20
|
expect(klass).to eq Listen::Adapter::Polling
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Listen::TCP::Broadcaster do
|
4
|
+
|
5
|
+
let(:host) { '10.0.0.2' }
|
6
|
+
let(:port) { 4000 }
|
7
|
+
|
8
|
+
subject { described_class.new(host, port) }
|
9
|
+
let(:server) { double(described_class::TCPServer, close: true, accept: nil) }
|
10
|
+
let(:socket) { double(described_class::TCPSocket, write: true) }
|
11
|
+
let(:payload) { Listen::TCP::Message.new.payload }
|
12
|
+
|
13
|
+
before do
|
14
|
+
expect(described_class::TCPServer).to receive(:new).with(host, port).and_return server
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
subject.terminate
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#initialize' do
|
22
|
+
it 'initializes and exposes a server' do
|
23
|
+
expect(subject.server).to be server
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'initializes and exposes a list of sockets' do
|
27
|
+
expect(subject.sockets).to eq []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#start' do
|
32
|
+
let(:async) { double('TCP-listener async') }
|
33
|
+
|
34
|
+
it 'invokes run loop asynchronously' do
|
35
|
+
subject.stub(:async).and_return async
|
36
|
+
expect(async).to receive(:run)
|
37
|
+
subject.start
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#finalize' do
|
42
|
+
it 'clears sockets' do
|
43
|
+
expect(subject.sockets).to receive(:clear)
|
44
|
+
subject.finalize
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'closes server' do
|
48
|
+
expect(subject.server).to receive(:close)
|
49
|
+
subject.finalize
|
50
|
+
expect(subject.server).to be_nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#broadcast' do
|
55
|
+
it 'unicasts to connected sockets' do
|
56
|
+
subject.handle_connection socket
|
57
|
+
expect(subject.wrapped_object).to receive(:unicast).with socket, payload
|
58
|
+
subject.broadcast payload
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#unicast' do
|
63
|
+
before do
|
64
|
+
subject.handle_connection socket
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when succesful' do
|
68
|
+
it 'returns true and leaves socket untouched' do
|
69
|
+
expect(subject.unicast(socket, payload)).to be_true
|
70
|
+
expect(subject.sockets).to include socket
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'on IO errors' do
|
75
|
+
it 'returns false and removes socket from list' do
|
76
|
+
socket.stub(:write).and_raise IOError
|
77
|
+
expect(subject.unicast(socket, payload)).to be_false
|
78
|
+
expect(subject.sockets).not_to include socket
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'on connection reset by peer' do
|
83
|
+
it 'returns false and removes socket from list' do
|
84
|
+
socket.stub(:write).and_raise Errno::ECONNRESET
|
85
|
+
expect(subject.unicast(socket, payload)).to be_false
|
86
|
+
expect(subject.sockets).not_to include socket
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'on broken pipe' do
|
91
|
+
it 'returns false and removes socket from list' do
|
92
|
+
socket.stub(:write).and_raise Errno::EPIPE
|
93
|
+
expect(subject.unicast(socket, payload)).to be_false
|
94
|
+
expect(subject.sockets).not_to include socket
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#run' do
|
100
|
+
it 'handles incoming connections' do
|
101
|
+
server.stub(:accept).and_return socket, nil
|
102
|
+
expect(subject.wrapped_object).to receive(:handle_connection).with socket
|
103
|
+
subject.run
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#handle_connection' do
|
108
|
+
it 'adds socket to list' do
|
109
|
+
subject.handle_connection socket
|
110
|
+
expect(subject.sockets).to include socket
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Listen::TCP::Listener do
|
4
|
+
|
5
|
+
let(:host) { '10.0.0.2' }
|
6
|
+
let(:port) { 4000 }
|
7
|
+
|
8
|
+
subject { described_class.new("#{host}:#{port}", :recipient, options) }
|
9
|
+
let(:options) { {} }
|
10
|
+
let(:registry) { double(Celluloid::Registry, :[]= => true) }
|
11
|
+
let(:supervisor) { double(Celluloid::SupervisionGroup, add: true, pool: true) }
|
12
|
+
let(:record) { double(Listen::Record, terminate: true, build: true) }
|
13
|
+
let(:silencer) { double(Listen::Silencer, terminate: true) }
|
14
|
+
let(:adapter) { double(Listen::Adapter::Base) }
|
15
|
+
let(:broadcaster) { double(Listen::TCP::Broadcaster) }
|
16
|
+
let(:change_pool) { double(Listen::Change, terminate: true) }
|
17
|
+
let(:change_pool_async) { double('ChangePoolAsync') }
|
18
|
+
before {
|
19
|
+
Celluloid::Registry.stub(:new) { registry }
|
20
|
+
Celluloid::SupervisionGroup.stub(:run!) { supervisor }
|
21
|
+
registry.stub(:[]).with(:silencer) { silencer }
|
22
|
+
registry.stub(:[]).with(:adapter) { adapter }
|
23
|
+
registry.stub(:[]).with(:record) { record }
|
24
|
+
registry.stub(:[]).with(:change_pool) { change_pool }
|
25
|
+
registry.stub(:[]).with(:broadcaster) { broadcaster }
|
26
|
+
}
|
27
|
+
|
28
|
+
describe '#initialize' do
|
29
|
+
its(:mode) { should be :recipient }
|
30
|
+
its(:host) { should eq host }
|
31
|
+
its(:port) { should eq port }
|
32
|
+
|
33
|
+
it 'raises on invalid mode' do
|
34
|
+
expect do
|
35
|
+
described_class.new(port, :foo)
|
36
|
+
end.to raise_error ArgumentError
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'raises on omitted target' do
|
40
|
+
expect do
|
41
|
+
described_class.new(nil, :recipient)
|
42
|
+
end.to raise_error ArgumentError
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when broadcaster' do
|
47
|
+
subject { described_class.new(port, :broadcaster) }
|
48
|
+
|
49
|
+
it { should be_a_broadcaster }
|
50
|
+
it { should_not be_a_recipient }
|
51
|
+
|
52
|
+
it 'does not force TCP adapter through options' do
|
53
|
+
expect(subject.options).not_to include(force_tcp: true)
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when host is omitted' do
|
57
|
+
its(:host) { should be_nil }
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#start' do
|
61
|
+
before do
|
62
|
+
adapter.stub_chain(:async, :start)
|
63
|
+
broadcaster.stub(:start)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'registers broadcaster' do
|
67
|
+
expect(supervisor).to receive(:add).with(Listen::TCP::Broadcaster, as: :broadcaster, args: [nil, port])
|
68
|
+
subject.start
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'starts broadcaster' do
|
72
|
+
expect(broadcaster).to receive(:start)
|
73
|
+
subject.start
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#block' do
|
78
|
+
let(:async) { double('TCP broadcaster async', broadcast: true) }
|
79
|
+
let(:callback) { double(call: true) }
|
80
|
+
let(:changes) { {
|
81
|
+
modified: ['/foo'],
|
82
|
+
added: [],
|
83
|
+
removed: []
|
84
|
+
}}
|
85
|
+
|
86
|
+
before do
|
87
|
+
broadcaster.stub(:async).and_return async
|
88
|
+
end
|
89
|
+
|
90
|
+
after do
|
91
|
+
subject.block.call changes.values
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when paused' do
|
95
|
+
it 'honours paused state and does nothing' do
|
96
|
+
subject.pause
|
97
|
+
expect(broadcaster).not_to receive(:async)
|
98
|
+
expect(callback).not_to receive(:call)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when stopped' do
|
103
|
+
let(:thread) { double(join: true) }
|
104
|
+
before do
|
105
|
+
subject.stub(:thread) { thread }
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'honours stopped state and does nothing' do
|
109
|
+
subject.stop
|
110
|
+
expect(broadcaster).not_to receive(:async)
|
111
|
+
expect(callback).not_to receive(:call)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'broadcasts changes asynchronously' do
|
116
|
+
message = Listen::TCP::Message.new changes
|
117
|
+
expect(async).to receive(:broadcast).with message.payload
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'invokes original callback block' do
|
121
|
+
subject.block = callback
|
122
|
+
expect(callback).to receive(:call).with *changes.values
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'when recipient' do
|
128
|
+
subject { described_class.new(port, :recipient) }
|
129
|
+
|
130
|
+
it 'forces TCP adapter through options' do
|
131
|
+
expect(subject.options).to include(force_tcp: true)
|
132
|
+
end
|
133
|
+
|
134
|
+
it { should_not be_a_broadcaster }
|
135
|
+
it { should be_a_recipient }
|
136
|
+
|
137
|
+
context 'when host is omitted' do
|
138
|
+
its(:host) { should eq described_class::DEFAULT_HOST }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Listen::TCP::Message do
|
6
|
+
|
7
|
+
let(:object) { [1, 2, {'foo' => 'bar'}] }
|
8
|
+
let(:body) { '[1,2,{"foo":"bar"}]' }
|
9
|
+
let(:size) { 19 }
|
10
|
+
let(:payload) { "\x00\x00\x00\x13[1,2,{\"foo\":\"bar\"}]" }
|
11
|
+
|
12
|
+
describe '#initialize' do
|
13
|
+
it 'initializes with an object' do
|
14
|
+
message = described_class.new(object)
|
15
|
+
expect(message.object).to be object
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#object=' do
|
20
|
+
before do
|
21
|
+
subject.object = object
|
22
|
+
end
|
23
|
+
|
24
|
+
its(:object) { should be object }
|
25
|
+
its(:body) { should eq body }
|
26
|
+
its(:size) { should eq size }
|
27
|
+
its(:payload) { should eq payload }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#payload=' do
|
31
|
+
before do
|
32
|
+
subject.payload = payload
|
33
|
+
end
|
34
|
+
|
35
|
+
its(:object) { should eq object }
|
36
|
+
its(:body) { should eq body }
|
37
|
+
its(:size) { should eq size }
|
38
|
+
its(:payload) { should be payload }
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.from_buffer' do
|
42
|
+
|
43
|
+
context 'when buffer is empty' do
|
44
|
+
it 'returns nil and leaves buffer intact' do
|
45
|
+
buffer = ''
|
46
|
+
message = described_class.from_buffer buffer
|
47
|
+
expect(message).to be_nil
|
48
|
+
expect(buffer).to eq ''
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when buffer has data' do
|
53
|
+
|
54
|
+
context 'with a partial packet' do
|
55
|
+
it 'returns nil and leaves remaining data intact' do
|
56
|
+
buffer = payload[0..4]
|
57
|
+
message = described_class.from_buffer buffer
|
58
|
+
expect(message).to be_nil
|
59
|
+
expect(buffer).to eq payload[0..4]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with a full packet' do
|
64
|
+
it 'extracts message from buffer and depletes buffer' do
|
65
|
+
buffer = payload.dup
|
66
|
+
message = described_class.from_buffer buffer
|
67
|
+
expect(message).to be_a described_class
|
68
|
+
expect(message.object).to eq object
|
69
|
+
expect(buffer).to eq ''
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'with a full and a partial packet' do
|
74
|
+
it 'extracts message from buffer and leaves remaining data intact' do
|
75
|
+
buffer = payload + payload[0..10]
|
76
|
+
message = described_class.from_buffer buffer
|
77
|
+
expect(message).to be_a described_class
|
78
|
+
expect(message.object).to eq object
|
79
|
+
expect(buffer).to eq payload[0..10]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'with two full packets' do
|
84
|
+
it 'extracts both messages from buffer and depletes buffer' do
|
85
|
+
buffer = payload + payload
|
86
|
+
|
87
|
+
message1 = described_class.from_buffer buffer
|
88
|
+
expect(message1).to be_a described_class
|
89
|
+
expect(message1.object).to eq object
|
90
|
+
|
91
|
+
message2 = described_class.from_buffer buffer
|
92
|
+
expect(message2).to be_a described_class
|
93
|
+
expect(message2.object).to eq object
|
94
|
+
|
95
|
+
expect(message1).not_to be message2
|
96
|
+
expect(buffer).to eq ''
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/spec/lib/listen_spec.rb
CHANGED
@@ -7,6 +7,13 @@ describe Listen do
|
|
7
7
|
described_class.to('/path')
|
8
8
|
end
|
9
9
|
|
10
|
+
context 'when using :forward_to option' do
|
11
|
+
it 'initializes TCP-listener in broadcast-mode' do
|
12
|
+
expect(Listen::TCP::Listener).to receive(:new).with(4000, :broadcaster, '/path', {})
|
13
|
+
described_class.to('/path', forward_to: 4000)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
10
17
|
it "sets stopping at false" do
|
11
18
|
allow(Listen::Listener).to receive(:new)
|
12
19
|
Listen.to('/path')
|
@@ -20,4 +27,11 @@ describe Listen do
|
|
20
27
|
expect(Listen.stopping).to be_true
|
21
28
|
end
|
22
29
|
end
|
30
|
+
|
31
|
+
describe '.on' do
|
32
|
+
it 'initializes TCP-listener in recipient-mode' do
|
33
|
+
expect(Listen::TCP::Listener).to receive(:new).with(4000, :recipient, '/path')
|
34
|
+
described_class.on(4000, '/path')
|
35
|
+
end
|
36
|
+
end
|
23
37
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.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: 2014-02-
|
11
|
+
date: 2014-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: celluloid
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.15.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: celluloid-io
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.15.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.15.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rb-fsevent
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -133,6 +147,7 @@ files:
|
|
133
147
|
- lib/listen/adapter/darwin.rb
|
134
148
|
- lib/listen/adapter/linux.rb
|
135
149
|
- lib/listen/adapter/polling.rb
|
150
|
+
- lib/listen/adapter/tcp.rb
|
136
151
|
- lib/listen/adapter/windows.rb
|
137
152
|
- lib/listen/change.rb
|
138
153
|
- lib/listen/directory.rb
|
@@ -140,14 +155,19 @@ files:
|
|
140
155
|
- lib/listen/listener.rb
|
141
156
|
- lib/listen/record.rb
|
142
157
|
- lib/listen/silencer.rb
|
158
|
+
- lib/listen/tcp/broadcaster.rb
|
159
|
+
- lib/listen/tcp/listener.rb
|
160
|
+
- lib/listen/tcp/message.rb
|
143
161
|
- lib/listen/version.rb
|
144
162
|
- listen.gemspec
|
145
163
|
- spec/acceptance/listen_spec.rb
|
164
|
+
- spec/acceptance/tcp_spec.rb
|
146
165
|
- spec/lib/listen/adapter/base_spec.rb
|
147
166
|
- spec/lib/listen/adapter/bsd_spec.rb
|
148
167
|
- spec/lib/listen/adapter/darwin_spec.rb
|
149
168
|
- spec/lib/listen/adapter/linux_spec.rb
|
150
169
|
- spec/lib/listen/adapter/polling_spec.rb
|
170
|
+
- spec/lib/listen/adapter/tcp_spec.rb
|
151
171
|
- spec/lib/listen/adapter/windows_spec.rb
|
152
172
|
- spec/lib/listen/adapter_spec.rb
|
153
173
|
- spec/lib/listen/change_spec.rb
|
@@ -156,6 +176,9 @@ files:
|
|
156
176
|
- spec/lib/listen/listener_spec.rb
|
157
177
|
- spec/lib/listen/record_spec.rb
|
158
178
|
- spec/lib/listen/silencer_spec.rb
|
179
|
+
- spec/lib/listen/tcp/broadcaster_spec.rb
|
180
|
+
- spec/lib/listen/tcp/listener_spec.rb
|
181
|
+
- spec/lib/listen/tcp/message_spec.rb
|
159
182
|
- spec/lib/listen_spec.rb
|
160
183
|
- spec/spec_helper.rb
|
161
184
|
- spec/support/acceptance_helper.rb
|
@@ -187,11 +210,13 @@ specification_version: 4
|
|
187
210
|
summary: Listen to file modifications
|
188
211
|
test_files:
|
189
212
|
- spec/acceptance/listen_spec.rb
|
213
|
+
- spec/acceptance/tcp_spec.rb
|
190
214
|
- spec/lib/listen/adapter/base_spec.rb
|
191
215
|
- spec/lib/listen/adapter/bsd_spec.rb
|
192
216
|
- spec/lib/listen/adapter/darwin_spec.rb
|
193
217
|
- spec/lib/listen/adapter/linux_spec.rb
|
194
218
|
- spec/lib/listen/adapter/polling_spec.rb
|
219
|
+
- spec/lib/listen/adapter/tcp_spec.rb
|
195
220
|
- spec/lib/listen/adapter/windows_spec.rb
|
196
221
|
- spec/lib/listen/adapter_spec.rb
|
197
222
|
- spec/lib/listen/change_spec.rb
|
@@ -200,6 +225,9 @@ test_files:
|
|
200
225
|
- spec/lib/listen/listener_spec.rb
|
201
226
|
- spec/lib/listen/record_spec.rb
|
202
227
|
- spec/lib/listen/silencer_spec.rb
|
228
|
+
- spec/lib/listen/tcp/broadcaster_spec.rb
|
229
|
+
- spec/lib/listen/tcp/listener_spec.rb
|
230
|
+
- spec/lib/listen/tcp/message_spec.rb
|
203
231
|
- spec/lib/listen_spec.rb
|
204
232
|
- spec/spec_helper.rb
|
205
233
|
- spec/support/acceptance_helper.rb
|