pants 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +1 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +42 -0
- data/History.rdoc +6 -0
- data/README.rdoc +339 -0
- data/Rakefile +23 -0
- data/bin/pants +59 -0
- data/lib/pants.rb +84 -0
- data/lib/pants/core.rb +216 -0
- data/lib/pants/error.rb +5 -0
- data/lib/pants/logger.rb +8 -0
- data/lib/pants/network_helpers.rb +25 -0
- data/lib/pants/readers/base_reader.rb +302 -0
- data/lib/pants/readers/file_reader.rb +81 -0
- data/lib/pants/readers/udp_reader.rb +80 -0
- data/lib/pants/seam.rb +120 -0
- data/lib/pants/version.rb +3 -0
- data/lib/pants/writers/base_writer.rb +73 -0
- data/lib/pants/writers/file_writer.rb +59 -0
- data/lib/pants/writers/udp_writer.rb +125 -0
- data/pants.gemspec +31 -0
- data/spec/acceptance/file_reads_spec.rb +24 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/matchers/file_same_size_as.rb +20 -0
- data/spec/support/pants.wav +0 -0
- data/spec/unit/pants/core_spec.rb +105 -0
- data/spec/unit/pants/readers/base_reader_spec.rb +340 -0
- data/spec/unit/pants/readers/file_reader_spec.rb +94 -0
- data/spec/unit/pants/version_spec.rb +7 -0
- data/spec/unit/pants/writers/base_writer_spec.rb +35 -0
- data/spec/unit/pants/writers/file_writer_spec.rb +71 -0
- data/spec/unit/pants/writers/udp_writer_spec.rb +146 -0
- data/spec/unit/pants_spec.rb +6 -0
- data/tasks/pantsmark.thor +66 -0
- data/test_readers.rb +43 -0
- data/test_seams.rb +107 -0
- metadata +215 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require_relative 'base_reader'
|
3
|
+
|
4
|
+
|
5
|
+
class Pants
|
6
|
+
module Readers
|
7
|
+
# This is the EventMachine connection that reads the source file and puts
|
8
|
+
# the read data into the data channel so writers can write as they need to.
|
9
|
+
class FileReaderConnection < EventMachine::Connection
|
10
|
+
include LogSwitch::Mixin
|
11
|
+
|
12
|
+
# @param [EventMachine::Channel] write_to_channel The data channel to write
|
13
|
+
# read data to.
|
14
|
+
|
15
|
+
# @param [EventMachine::Callback] starter Gets called when the
|
16
|
+
# it's been fulling initialized.
|
17
|
+
#
|
18
|
+
# @param [EventMachine::Callback] stopper Gets called when the
|
19
|
+
# file-to-read has been fully read.
|
20
|
+
def initialize(write_to_channel, starter, stopper)
|
21
|
+
@write_to_channel = write_to_channel
|
22
|
+
@stopper = stopper
|
23
|
+
@starter = starter
|
24
|
+
end
|
25
|
+
|
26
|
+
def post_init
|
27
|
+
@starter.call
|
28
|
+
end
|
29
|
+
|
30
|
+
# Reads the data and writes it to the data channel.
|
31
|
+
#
|
32
|
+
# @param [String] data The file data to write to the channel.
|
33
|
+
def receive_data(data)
|
34
|
+
log "<< #{data.size}"
|
35
|
+
@write_to_channel << data
|
36
|
+
end
|
37
|
+
|
38
|
+
# Called when the file is done being read.
|
39
|
+
def unbind
|
40
|
+
log "Unbinding, done writing, and notifying the stopper..."
|
41
|
+
@stopper.call
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# This is the interface for FileReaderConnections. It controls starting and
|
47
|
+
# stopping the connection.
|
48
|
+
class FileReader < BaseReader
|
49
|
+
include LogSwitch::Mixin
|
50
|
+
|
51
|
+
# @return [String] Path to the file that's being read.
|
52
|
+
attr_reader :file_path
|
53
|
+
|
54
|
+
# @param [String] file_path Path to the file to read.
|
55
|
+
#
|
56
|
+
# @param [EventMachine::Callback] core_stopper_callback The Callback that will get
|
57
|
+
# called when #stopper is called. #stopper is called when the whole
|
58
|
+
# file has been read and pushed to the channel.
|
59
|
+
def initialize(file_path, core_stopper_callback)
|
60
|
+
log "Initializing #{self.class} with file path '#{file_path}'"
|
61
|
+
@read_object = file_path
|
62
|
+
@file_path = file_path
|
63
|
+
|
64
|
+
log "Opening file '#{@file_path}'"
|
65
|
+
@file = File.open(@file_path, 'r')
|
66
|
+
|
67
|
+
super(core_stopper_callback)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Starts reading the file after all writers have been started.
|
71
|
+
def start
|
72
|
+
callback = EM.Callback do
|
73
|
+
log "Adding file '#{@file_path}'..."
|
74
|
+
EM.attach(@file, FileReaderConnection, @write_to_channel, starter, stopper)
|
75
|
+
end
|
76
|
+
|
77
|
+
super(callback)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require_relative 'base_reader'
|
3
|
+
require_relative '../network_helpers'
|
4
|
+
|
5
|
+
|
6
|
+
class Pants
|
7
|
+
module Readers
|
8
|
+
|
9
|
+
# This is the EventMachine connection that reads on the source IP and UDP
|
10
|
+
# port. It places all read data onto the data channel. Allows for unicast or
|
11
|
+
# multicast addresses; it'll detect which to use from the IP you pass in.
|
12
|
+
class UDPReaderConnection < EventMachine::Connection
|
13
|
+
include LogSwitch::Mixin
|
14
|
+
include Pants::NetworkHelpers
|
15
|
+
|
16
|
+
# @param [EventMachine::Channel] write_to_channel The data channel to write
|
17
|
+
# read data to.
|
18
|
+
#
|
19
|
+
# @param [EventMachine::Callback] starter_callback The callback that
|
20
|
+
# should get called when the connection has been fully initialized.
|
21
|
+
def initialize(write_to_channel, starter_callback)
|
22
|
+
@write_to_channel = write_to_channel
|
23
|
+
@starter_callback = starter_callback
|
24
|
+
port, ip = Socket.unpack_sockaddr_in(get_sockname)
|
25
|
+
|
26
|
+
if Addrinfo.ip(ip).ipv4_multicast? || Addrinfo.ip(ip).ipv6_multicast?
|
27
|
+
log "Got a multicast address: #{ip}:#{port}"
|
28
|
+
setup_multicast_socket(ip)
|
29
|
+
else
|
30
|
+
log "Got a unicast address: #{ip}:#{port}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_init
|
35
|
+
@starter_callback.call
|
36
|
+
end
|
37
|
+
|
38
|
+
# Reads the data and writes it to the data channel.
|
39
|
+
#
|
40
|
+
# @param [String] data The socket data to write to the channel.
|
41
|
+
def receive_data(data)
|
42
|
+
@write_to_channel << data
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# This is the interface for UDPReaderConnections. It controls what happens
|
48
|
+
# when the you want to start it up and stop it.
|
49
|
+
class UDPReader < BaseReader
|
50
|
+
include LogSwitch::Mixin
|
51
|
+
|
52
|
+
# @param [String] host The IP address to read on.
|
53
|
+
#
|
54
|
+
# @param [Fixnum] port The UDP port to read on.
|
55
|
+
#
|
56
|
+
# @param [EventMachine::Callback] core_stopper_callback The Callback that will get
|
57
|
+
# called when #stopper is called. Since there is no clear end to when
|
58
|
+
# to stop reading this I/O, #stopper is never called internally; it must
|
59
|
+
# be called externally.
|
60
|
+
def initialize(host, port, core_stopper_callback)
|
61
|
+
@read_object = "udp://#{host}:#{port}"
|
62
|
+
@host = host
|
63
|
+
@port = port
|
64
|
+
|
65
|
+
super(core_stopper_callback)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Starts reading on the UDP IP and port and pushing packets to the channel.
|
69
|
+
def start
|
70
|
+
callback = EM.Callback do
|
71
|
+
log "Adding a #{self.class} at #{@host}:#{@port}..."
|
72
|
+
EM.open_datagram_socket(@host, @port, UDPReaderConnection,
|
73
|
+
@write_to_channel, starter)
|
74
|
+
end
|
75
|
+
|
76
|
+
super(callback)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/pants/seam.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require_relative 'readers/base_reader'
|
2
|
+
|
3
|
+
|
4
|
+
class Pants
|
5
|
+
|
6
|
+
# A Seam is a core Pants object type (like Readers and Writers) that lets you
|
7
|
+
# attach to a Reader, work with the read data, and pass it on to attached
|
8
|
+
# Writers. It implements buffering by using EventMachine Queues: pop data
|
9
|
+
# off the @read_queue, work with it, then push it onto the @write_queue. Once
|
10
|
+
# on the @write_queue, the Seam will pass on to all Writers that have been
|
11
|
+
# added to it.
|
12
|
+
#
|
13
|
+
# The @read_queue is wrapped by #read_items, which yields data
|
14
|
+
# chunks from the Reader in, allowing easy access to each bit of data as it
|
15
|
+
# was when it was read in. The @write_queue is wrapped by #write, which
|
16
|
+
# lets you just give it the data you want to pass on to the attached Writers.
|
17
|
+
#
|
18
|
+
# Seams are particularly useful for working with network data, where if you're
|
19
|
+
# redirecting traffic from one place to another, you may need to alter data
|
20
|
+
# in those packets to make it useful to the receiving ends.
|
21
|
+
class Seam < Pants::Readers::BaseReader
|
22
|
+
include LogSwitch::Mixin
|
23
|
+
|
24
|
+
# @param [EventMachine::Callback] core_stopper_callback The callback that's
|
25
|
+
# provided by Core.
|
26
|
+
#
|
27
|
+
# @param [EventMachine::Channel] reader_channel The channel from the Reader
|
28
|
+
# that the Seam is attached to.
|
29
|
+
def initialize(core_stopper_callback, reader_channel)
|
30
|
+
@read_queue = EM::Queue.new
|
31
|
+
@write_queue = EM::Queue.new
|
32
|
+
@write_object ||= nil
|
33
|
+
|
34
|
+
@receives = 0
|
35
|
+
@reads = 0
|
36
|
+
@writes = 0
|
37
|
+
@sends = 0
|
38
|
+
|
39
|
+
reader_channel.subscribe do |data|
|
40
|
+
log "Got data on reader channel"
|
41
|
+
@read_queue << data
|
42
|
+
@receives += data.size
|
43
|
+
end
|
44
|
+
|
45
|
+
super(core_stopper_callback)
|
46
|
+
send_data
|
47
|
+
end
|
48
|
+
|
49
|
+
def start(callback)
|
50
|
+
super(callback)
|
51
|
+
|
52
|
+
starter.call
|
53
|
+
end
|
54
|
+
|
55
|
+
# Make sure you call this (with super()) in your child to ensure read and
|
56
|
+
# write queues are flushed.
|
57
|
+
def stop
|
58
|
+
log "Stopping..."
|
59
|
+
log "receives #{@receives}"
|
60
|
+
log "reads #{@reads}"
|
61
|
+
log "writes #{@writes}"
|
62
|
+
log "sends #{@sends}"
|
63
|
+
|
64
|
+
finish_loop = EM.tick_loop do
|
65
|
+
if @read_queue.empty? && @write_queue.empty?
|
66
|
+
:stop
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
finish_loop.on_stop { stopper.call }
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] A String that identifies what the writer is writing to.
|
74
|
+
# This is simply used for displaying info to the user.
|
75
|
+
def write_object
|
76
|
+
if @write_object
|
77
|
+
@write_object
|
78
|
+
else
|
79
|
+
warn "No write_object info has been defined for this writer."
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Call this to read data that was put into the read queue. It yields one
|
84
|
+
# "item" (however the data was put onto the queue) at a time. It will
|
85
|
+
# continually yield as there is data that comes in on the queue.
|
86
|
+
#
|
87
|
+
# @param [Proc] block The block to yield items from the reader to.
|
88
|
+
# @yield [item] Gives one item off the read queue.
|
89
|
+
def read_items(&block)
|
90
|
+
processor = proc do |item|
|
91
|
+
block.call(item)
|
92
|
+
@reads += item.size
|
93
|
+
@read_queue.pop(&processor)
|
94
|
+
end
|
95
|
+
|
96
|
+
@read_queue.pop(&processor)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Call this after your Seam child has processed data and is ready to send it
|
100
|
+
# to its writers.
|
101
|
+
#
|
102
|
+
# @param [Object] data
|
103
|
+
def write(data)
|
104
|
+
@write_queue << data
|
105
|
+
@writes += data.size
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def send_data
|
111
|
+
processor = proc do |data|
|
112
|
+
@write_to_channel << data
|
113
|
+
@sends += data.size
|
114
|
+
@write_queue.pop(&processor)
|
115
|
+
end
|
116
|
+
|
117
|
+
@write_queue.pop(&processor)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require_relative '../logger'
|
3
|
+
|
4
|
+
|
5
|
+
class Pants
|
6
|
+
module Writers
|
7
|
+
|
8
|
+
# Provides conventions for creating your own writer that can stop and start
|
9
|
+
# safely.
|
10
|
+
#
|
11
|
+
# You should also consider adding attr_readers/methods for attributes that
|
12
|
+
# are differentiators from other writers of the same type. This will allow
|
13
|
+
# readers to more easily remove your writer from them.
|
14
|
+
class BaseWriter
|
15
|
+
|
16
|
+
# @param [EventMachine::Channel] read_from_channel The channel that this
|
17
|
+
# writer should read from.
|
18
|
+
def initialize(read_from_channel)
|
19
|
+
@running = false
|
20
|
+
@read_from_channel = read_from_channel
|
21
|
+
@write_object ||= nil
|
22
|
+
@starter = nil
|
23
|
+
@stopper = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# This method must be redefined in a child class. The reader that this
|
27
|
+
# writer is tied to will call this before it starts reading.
|
28
|
+
def start
|
29
|
+
warn "You haven't defined a start method--are you sure this writer does something?"
|
30
|
+
end
|
31
|
+
|
32
|
+
# This method must be redefined in a child class. The reader that this
|
33
|
+
# writer is tied to will call this when it's done reading whatever it's
|
34
|
+
# reading.
|
35
|
+
def stop
|
36
|
+
warn "You haven't defined a stop method--are you sure you're cleaning up?"
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String] A String that identifies what the writer is writing to.
|
40
|
+
# This is simply used for displaying info to the user.
|
41
|
+
def write_object
|
42
|
+
if @write_object
|
43
|
+
@write_object
|
44
|
+
else
|
45
|
+
warn "No write_object info has been defined for this writer."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# This should get called with #call after the writer is sure to be up
|
50
|
+
# and running, ready for accepting data.
|
51
|
+
#
|
52
|
+
# @return [EventMachine::Callback] The Callback that should get
|
53
|
+
# called.
|
54
|
+
def starter
|
55
|
+
@starter ||= EM.Callback { @running = true }
|
56
|
+
end
|
57
|
+
|
58
|
+
# This should get called with #call after the writer is done writing
|
59
|
+
# out the data in its channel.
|
60
|
+
#
|
61
|
+
# @return [EventMachine::Callback] The Callback that should get
|
62
|
+
# called.
|
63
|
+
def stopper
|
64
|
+
@stopper ||= EM.Callback { @running = false }
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Boolean] Is the Writer writing data?
|
68
|
+
def running?
|
69
|
+
@running
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative 'base_writer'
|
2
|
+
|
3
|
+
|
4
|
+
class Pants
|
5
|
+
module Writers
|
6
|
+
|
7
|
+
# This is the interface for FileWriterConnections. It controls starting,
|
8
|
+
# stopping, and threading the connection.
|
9
|
+
class FileWriter < BaseWriter
|
10
|
+
include LogSwitch::Mixin
|
11
|
+
|
12
|
+
# @return [String] The path to the file that's being written to.
|
13
|
+
attr_reader :file_path
|
14
|
+
|
15
|
+
# @param [EventMachine::Channel] read_from_channel The channel to read data
|
16
|
+
# from and thus write to file.
|
17
|
+
#
|
18
|
+
# @param [String] file_path The path to write to.
|
19
|
+
def initialize(file_path, read_from_channel)
|
20
|
+
@file = file_path.is_a?(File) ? file_path : File.open(file_path, 'w')
|
21
|
+
@file_path = file_path
|
22
|
+
@write_object = @file_path
|
23
|
+
|
24
|
+
super(read_from_channel)
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
log "Finishing ID #{__id__} and closing file #{@file}"
|
29
|
+
@file.close unless @file.closed?
|
30
|
+
stopper.call
|
31
|
+
end
|
32
|
+
|
33
|
+
def start
|
34
|
+
log "#{__id__} Adding a #{self.class} to write to #{@file_path}"
|
35
|
+
|
36
|
+
EM.defer do
|
37
|
+
@read_from_channel.subscribe do |data|
|
38
|
+
begin
|
39
|
+
bytes_written = @file.write_nonblock(data)
|
40
|
+
log "Wrote normal, #{bytes_written} bytes"
|
41
|
+
rescue IOError
|
42
|
+
log "Finishing writing; only wrote #{bytes_written}"
|
43
|
+
|
44
|
+
unless bytes_written == data.size
|
45
|
+
File.open(@file, 'a') do |file|
|
46
|
+
file.write_nonblock(data)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
start_loop = EM.tick_loop { :stop unless @file.closed? }
|
53
|
+
start_loop.on_stop { starter.call }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative 'base_writer'
|
3
|
+
require_relative '../network_helpers'
|
4
|
+
|
5
|
+
|
6
|
+
class Pants
|
7
|
+
module Writers
|
8
|
+
|
9
|
+
# This is the EventMachine connection that connects the data from the data
|
10
|
+
# channel (put there by the reader you're using) to the IP and UDP port you
|
11
|
+
# want to send it to.
|
12
|
+
class UDPWriterConnection < EM::Connection
|
13
|
+
include LogSwitch::Mixin
|
14
|
+
include Pants::NetworkHelpers
|
15
|
+
|
16
|
+
# Packets get split up before writing if they're over this size.
|
17
|
+
PACKET_SPLIT_THRESHOLD = 1400
|
18
|
+
|
19
|
+
# Packets get split up to this size before writing.
|
20
|
+
PACKET_SPLIT_SIZE = 1300
|
21
|
+
|
22
|
+
# @param [EventMachine::Channel] read_from_channel The channel to expect
|
23
|
+
# data on and write to the socket.
|
24
|
+
#
|
25
|
+
# @param [String] dest_ip The IP address to send data to. Can be unicast
|
26
|
+
# or multicast.
|
27
|
+
#
|
28
|
+
# @param [Fixnum] dest_port The UDP port to send data to.
|
29
|
+
def initialize(read_from_channel, dest_ip, dest_port)
|
30
|
+
@read_from_channel = read_from_channel
|
31
|
+
@dest_ip = dest_ip
|
32
|
+
@dest_port = dest_port
|
33
|
+
|
34
|
+
if Addrinfo.ip(@dest_ip).ipv4_multicast? || Addrinfo.ip(@dest_ip).ipv6_multicast?
|
35
|
+
log "Got a multicast address: #{@dest_ip}:#{@dest_port}"
|
36
|
+
setup_multicast_socket(@dest_ip)
|
37
|
+
else
|
38
|
+
log "Got a unicast address: #{@dest_ip}:#{@dest_port}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sends data received on the data channel to the destination IP and port.
|
43
|
+
# Since data may have been put in to the channel by a File reader (and will
|
44
|
+
# therefore be larger chunks of data than you'll want to send in a packet
|
45
|
+
# over the wire), it will split packets into +PACKET_SPLIT_SIZE+ sized
|
46
|
+
# packets before sending.
|
47
|
+
def post_init
|
48
|
+
@read_from_channel.subscribe do |data|
|
49
|
+
if data.size > PACKET_SPLIT_THRESHOLD
|
50
|
+
log "#{__id__} Got big data: #{data.size}. Splitting..."
|
51
|
+
io = StringIO.new(data)
|
52
|
+
io.binmode
|
53
|
+
|
54
|
+
begin
|
55
|
+
log "#{__id__} Spliced #{PACKET_SPLIT_SIZE} bytes to socket packet"
|
56
|
+
|
57
|
+
while true
|
58
|
+
new_packet = io.read_nonblock(PACKET_SPLIT_SIZE)
|
59
|
+
send_datagram(new_packet, @dest_ip, @dest_port)
|
60
|
+
new_packet = nil
|
61
|
+
end
|
62
|
+
rescue EOFError
|
63
|
+
send_datagram(new_packet, @dest_ip, @dest_port) if new_packet
|
64
|
+
io.close
|
65
|
+
end
|
66
|
+
else
|
67
|
+
log "Sending data to #{@dest_ip}:#{@dest_port}"
|
68
|
+
send_datagram(data, @dest_ip, @dest_port)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def receive_data(data)
|
74
|
+
log "Got data (should I?): #{data.size}, port #{@dest_port}, peer: #{get_peername}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# This is the interface to UDPWriterConnections. It defines what happens
|
80
|
+
# when you want to start it up and stop it.
|
81
|
+
class UDPWriter < BaseWriter
|
82
|
+
include LogSwitch::Mixin
|
83
|
+
|
84
|
+
# @return [String] The IP address that's being written to.
|
85
|
+
attr_reader :host
|
86
|
+
|
87
|
+
# @return [Fixnum] The port that's being written to.
|
88
|
+
attr_reader :port
|
89
|
+
|
90
|
+
# @param [String] host
|
91
|
+
#
|
92
|
+
# @param [Fixnum] port
|
93
|
+
#
|
94
|
+
# @param [EventMachine::Channel] read_from_channel
|
95
|
+
def initialize(host, port, read_from_channel)
|
96
|
+
@host = host
|
97
|
+
@port = port
|
98
|
+
@connection = nil
|
99
|
+
@write_object = "udp://#{@host}:#{@port}"
|
100
|
+
|
101
|
+
super(read_from_channel)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Readies the writer for data to write and waits for data to write.
|
105
|
+
def start
|
106
|
+
log "#{__id__} Adding a #{self.class} at #{@host}:#{@port}..."
|
107
|
+
|
108
|
+
EM.defer do
|
109
|
+
@connection = EM.open_datagram_socket('0.0.0.0', 0, UDPWriterConnection,
|
110
|
+
@read_from_channel, @host, @port)
|
111
|
+
|
112
|
+
start_loop = EM.tick_loop { :stop if @connection }
|
113
|
+
start_loop.on_stop { starter.call }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Closes the connection and notifies the reader that it's done.
|
118
|
+
def stop
|
119
|
+
log "Finishing ID #{__id__}"
|
120
|
+
@connection.close_connection_after_writing
|
121
|
+
stopper.call
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|