listen 2.4.1 → 2.5.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 +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
|