pants 0.1.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.
- 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
|