omf_common 6.0.0 → 6.0.2.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/bin/file_broadcaster.rb +56 -0
- data/bin/file_receiver.rb +62 -0
- data/bin/omf_keygen +21 -0
- data/bin/{monitor_topic.rb → omf_monitor_topic} +21 -8
- data/bin/omf_send_create +118 -0
- data/bin/{send_request.rb → omf_send_request} +12 -7
- data/example/engine_alt.rb +23 -24
- data/example/ls_app.yaml +21 -0
- data/lib/omf_common.rb +73 -12
- data/lib/omf_common/auth.rb +15 -0
- data/lib/omf_common/auth/certificate.rb +174 -0
- data/lib/omf_common/auth/certificate_store.rb +72 -0
- data/lib/omf_common/auth/ssh_pub_key_convert.rb +80 -0
- data/lib/omf_common/comm.rb +66 -9
- data/lib/omf_common/comm/amqp/amqp_communicator.rb +40 -13
- data/lib/omf_common/comm/amqp/amqp_file_transfer.rb +259 -0
- data/lib/omf_common/comm/amqp/amqp_topic.rb +14 -21
- data/lib/omf_common/comm/local/local_communicator.rb +31 -2
- data/lib/omf_common/comm/local/local_topic.rb +19 -3
- data/lib/omf_common/comm/topic.rb +48 -34
- data/lib/omf_common/comm/xmpp/communicator.rb +19 -10
- data/lib/omf_common/comm/xmpp/topic.rb +22 -81
- data/lib/omf_common/default_logging.rb +11 -0
- data/lib/omf_common/eventloop.rb +14 -0
- data/lib/omf_common/eventloop/em.rb +39 -6
- data/lib/omf_common/eventloop/local_evl.rb +15 -0
- data/lib/omf_common/exec_app.rb +29 -15
- data/lib/omf_common/message.rb +53 -5
- data/lib/omf_common/message/json/json_message.rb +149 -39
- data/lib/omf_common/message/xml/message.rb +112 -39
- data/lib/omf_common/protocol/6.0.rnc +5 -1
- data/lib/omf_common/protocol/6.0.rng +12 -0
- data/lib/omf_common/version.rb +1 -1
- data/omf_common.gemspec +7 -2
- data/test/fixture/omf_test.cert.pem +15 -0
- data/test/fixture/omf_test.pem +15 -0
- data/test/fixture/omf_test.pub +1 -0
- data/test/fixture/omf_test.pub.pem +6 -0
- data/test/omf_common/auth/certificate_spec.rb +113 -0
- data/test/omf_common/auth/ssh_pub_key_convert_spec.rb +13 -0
- data/test/omf_common/comm/topic_spec.rb +175 -0
- data/test/omf_common/comm/xmpp/communicator_spec.rb +15 -16
- data/test/omf_common/comm/xmpp/topic_spec.rb +63 -10
- data/test/omf_common/comm_spec.rb +66 -9
- data/test/omf_common/message/xml/message_spec.rb +43 -13
- data/test/omf_common/message_spec.rb +14 -0
- data/test/test_helper.rb +25 -0
- metadata +78 -15
- data/bin/send_create.rb +0 -94
data/lib/omf_common/comm.rb
CHANGED
@@ -49,7 +49,7 @@ module OmfCommon
|
|
49
49
|
provider = @@providers[type]
|
50
50
|
end
|
51
51
|
unless provider
|
52
|
-
raise "Missing Comm provider declaration. Either define 'type', 'provider', or 'url'"
|
52
|
+
raise ArgumentError, "Missing Comm provider declaration. Either define 'type', 'provider', or 'url'"
|
53
53
|
end
|
54
54
|
|
55
55
|
require provider[:require] if provider[:require]
|
@@ -58,10 +58,18 @@ module OmfCommon
|
|
58
58
|
provider_class = class_name.split('::').inject(Object) {|c,n| c.const_get(n) }
|
59
59
|
inst = provider_class.new(opts)
|
60
60
|
else
|
61
|
-
raise "Missing communicator creation info - :constructor"
|
61
|
+
raise ArgumentError, "Missing communicator creation info - :constructor"
|
62
62
|
end
|
63
63
|
@@instance = inst
|
64
|
-
|
64
|
+
mopts = provider[:message_provider]
|
65
|
+
mopts[:authenticate] = (opts[:auth] != nil)
|
66
|
+
Message.init(mopts)
|
67
|
+
|
68
|
+
if aopts = opts[:auth]
|
69
|
+
require 'omf_common/auth'
|
70
|
+
OmfCommon::Auth.init(aopts)
|
71
|
+
end
|
72
|
+
|
65
73
|
inst.init(opts)
|
66
74
|
end
|
67
75
|
|
@@ -72,30 +80,51 @@ module OmfCommon
|
|
72
80
|
# Initialize comms layer
|
73
81
|
#
|
74
82
|
def init(opts = {})
|
75
|
-
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return the address used for all 'generic' messages
|
86
|
+
# not specifically being sent from a resource
|
87
|
+
#
|
88
|
+
def local_address()
|
89
|
+
@local_topic.address
|
90
|
+
end
|
91
|
+
|
92
|
+
def local_topic()
|
93
|
+
@local_topic
|
76
94
|
end
|
77
95
|
|
78
96
|
# Shut down comms layer
|
79
97
|
def disconnect(opts = {})
|
80
|
-
raise
|
98
|
+
raise NotImplementedError
|
81
99
|
end
|
82
100
|
|
83
101
|
def on_connected(&block)
|
84
|
-
raise
|
102
|
+
raise NotImplementedError
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO should expand this to on_signal(:INT)
|
106
|
+
def on_interrupted(*args, &block)
|
85
107
|
end
|
86
108
|
|
87
109
|
# Create a new pubsub topic with additional configuration
|
88
110
|
#
|
89
111
|
# @param [String] topic Pubsub topic name
|
90
112
|
def create_topic(topic, opts = {})
|
91
|
-
raise
|
113
|
+
raise NotImplementedError
|
92
114
|
end
|
93
115
|
|
94
116
|
# Delete a pubsub topic
|
95
117
|
#
|
96
118
|
# @param [String] topic Pubsub topic name
|
97
119
|
def delete_topic(topic, &block)
|
98
|
-
raise
|
120
|
+
raise NotImplementedError
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returning connection information
|
124
|
+
#
|
125
|
+
# @retun [Hash] connection information hash, with type, user and domain.
|
126
|
+
def conn_info
|
127
|
+
{ proto: nil, user: nil, domain: nil }
|
99
128
|
end
|
100
129
|
|
101
130
|
# Subscribe to a pubsub topic
|
@@ -109,13 +138,29 @@ module OmfCommon
|
|
109
138
|
ta = tna.collect do |tn|
|
110
139
|
t = create_topic(tn)
|
111
140
|
if block
|
112
|
-
|
141
|
+
t.on_subscribed do
|
142
|
+
block.call(t)
|
143
|
+
end
|
113
144
|
end
|
114
145
|
t
|
115
146
|
end
|
116
147
|
ta[0]
|
117
148
|
end
|
118
149
|
|
150
|
+
# Publish a message on a topic
|
151
|
+
#
|
152
|
+
# @param [String, Array] topic_name Pubsub topic name
|
153
|
+
# @param [OmfCoomon::Message] message
|
154
|
+
#
|
155
|
+
def publish(topic_name, message)
|
156
|
+
#puts "PUBLISH>>>>> #{topic_name}::#{message}"
|
157
|
+
tna = (topic_name.is_a? Array) ? topic_name : [topic_name]
|
158
|
+
ta = tna.collect do |tn|
|
159
|
+
t = create_topic(tn)
|
160
|
+
t.publish(message)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
119
164
|
# Return the options used to initiate this
|
120
165
|
# communicator.
|
121
166
|
#
|
@@ -126,6 +171,18 @@ module OmfCommon
|
|
126
171
|
private
|
127
172
|
def initialize(opts = {})
|
128
173
|
@opts = opts
|
174
|
+
unless local_address = opts[:local_address]
|
175
|
+
hostname = nil
|
176
|
+
begin
|
177
|
+
hostname = Socket.gethostbyname(Socket.gethostname)[0]
|
178
|
+
rescue
|
179
|
+
hostname = (`hostname` || 'unknown').strip
|
180
|
+
end
|
181
|
+
local_address = "#{hostname}-#{Process.pid}"
|
182
|
+
end
|
183
|
+
on_connected do
|
184
|
+
@local_topic = create_topic(local_address.gsub('.', '-'))
|
185
|
+
end
|
129
186
|
end
|
130
187
|
|
131
188
|
end
|
@@ -6,7 +6,7 @@ module OmfCommon
|
|
6
6
|
class Comm
|
7
7
|
class AMQP
|
8
8
|
class Communicator < OmfCommon::Comm
|
9
|
-
|
9
|
+
|
10
10
|
# def initialize(opts = {})
|
11
11
|
# # ignore arguments
|
12
12
|
# end
|
@@ -20,25 +20,30 @@ module OmfCommon
|
|
20
20
|
@address_prefix = @url + '/'
|
21
21
|
::AMQP.connect(@url) do |connection|
|
22
22
|
@channel = ::AMQP::Channel.new(connection)
|
23
|
-
|
24
|
-
|
25
|
-
@on_connected_proc.arity == 1 ? @on_connected_proc.call(self) : @on_connected_proc.call
|
23
|
+
@on_connected_procs.each do |proc|
|
24
|
+
proc.arity == 1 ? proc.call(self) : proc.call
|
26
25
|
end
|
27
|
-
|
26
|
+
|
28
27
|
OmfCommon.eventloop.on_stop do
|
29
28
|
connection.close
|
30
29
|
end
|
31
30
|
end
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def conn_info
|
35
|
+
{ proto: :amqp, user: ::AMQP.settings[:user], domain: ::AMQP.settings[:host] }
|
32
36
|
end
|
33
|
-
|
37
|
+
|
34
38
|
# Shut down comms layer
|
35
39
|
def disconnect(opts = {})
|
36
40
|
end
|
37
|
-
|
41
|
+
|
42
|
+
# TODO: Should be thread safe and check if already connected
|
38
43
|
def on_connected(&block)
|
39
|
-
@
|
44
|
+
@on_connected_procs << block
|
40
45
|
end
|
41
|
-
|
46
|
+
|
42
47
|
# Create a new pubsub topic with additional configuration
|
43
48
|
#
|
44
49
|
# @param [String] topic Pubsub topic name
|
@@ -46,9 +51,10 @@ module OmfCommon
|
|
46
51
|
raise "Topic can't be nil or empty" if topic.nil? || topic.empty?
|
47
52
|
opts = opts.dup
|
48
53
|
opts[:channel] = @channel
|
54
|
+
topic = topic.to_s
|
49
55
|
if topic.start_with? 'amqp:'
|
50
56
|
# absolute address
|
51
|
-
unless topic.start_with? @address_prefix
|
57
|
+
unless topic.start_with? @address_prefix
|
52
58
|
raise "Cannot subscribe to a topic from different domain (#{topic})"
|
53
59
|
end
|
54
60
|
opts[:address] = topic
|
@@ -58,7 +64,7 @@ module OmfCommon
|
|
58
64
|
end
|
59
65
|
OmfCommon::Comm::AMQP::Topic.create(topic, opts)
|
60
66
|
end
|
61
|
-
|
67
|
+
|
62
68
|
# Delete a pubsub topic
|
63
69
|
#
|
64
70
|
# @param [String] topic Pubsub topic name
|
@@ -67,9 +73,30 @@ module OmfCommon
|
|
67
73
|
t.release
|
68
74
|
else
|
69
75
|
warn "Attempt to delete unknown topic '#{topic}"
|
70
|
-
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def broadcast_file(file_path, topic_name = nil, opts = {}, &block)
|
80
|
+
topic_name ||= SecureRandom.uuid
|
81
|
+
require 'omf_common/comm/amqp/amqp_file_transfer'
|
82
|
+
OmfCommon::Comm::AMQP::FileBroadcaster.new(file_path, @channel, topic_name, opts, &block)
|
83
|
+
"bdcst:#{@address_prefix + topic_name}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def receive_file(topic_url, file_path = nil, opts = {}, &block)
|
87
|
+
if topic_url.start_with? @address_prefix
|
88
|
+
topic_url = topic_url[@address_prefix.length .. -1]
|
89
|
+
end
|
90
|
+
require 'omf_common/comm/amqp/amqp_file_transfer'
|
91
|
+
file_path ||= File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname('bdcast', '.xxx'))
|
92
|
+
FileReceiver.new(file_path, @channel, topic_url, opts, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def initialize(opts = {})
|
97
|
+
@on_connected_procs = []
|
98
|
+
super
|
71
99
|
end
|
72
|
-
|
73
100
|
end
|
74
101
|
end
|
75
102
|
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'monitor'
|
3
|
+
|
4
|
+
module OmfCommon
|
5
|
+
class Comm::AMQP
|
6
|
+
|
7
|
+
# Distributes a local file to a set of receivers subscribed to the same
|
8
|
+
# topic but may join a various stages.
|
9
|
+
#
|
10
|
+
class FileBroadcaster
|
11
|
+
include MonitorMixin
|
12
|
+
|
13
|
+
DEF_CHUNK_SIZE = 2**16
|
14
|
+
DEF_IDLE_TIME = 60
|
15
|
+
|
16
|
+
# @param topic[String] Name of topic to send file to
|
17
|
+
# @param file_path[String] Path to a local file
|
18
|
+
# @param opts[Hash]
|
19
|
+
# :chunk_size Max size of data chunk to send
|
20
|
+
# :idle_time Min. time in sec to close down broadcaster after having sent last chunk
|
21
|
+
#
|
22
|
+
def initialize(file_path, channel, topic, opts = {}, &block)
|
23
|
+
super() # init monitor mixin
|
24
|
+
@block = block
|
25
|
+
unless File.readable?(file_path)
|
26
|
+
raise "Can't read file '#{file_path}'"
|
27
|
+
end
|
28
|
+
@mime_type = `file -b --mime-type #{file_path}`.strip
|
29
|
+
unless $?.success?
|
30
|
+
raise "Can't determine file's mime-type (#{$?})"
|
31
|
+
end
|
32
|
+
@file_path = file_path
|
33
|
+
f = File.open(file_path, 'rb')
|
34
|
+
chunk_size = opts[:chunk_size] || DEF_CHUNK_SIZE
|
35
|
+
chunk_count = (f.size / chunk_size) + 1
|
36
|
+
|
37
|
+
@outstanding_chunks = Set.new
|
38
|
+
@running = true
|
39
|
+
@semaphore = new_cond()
|
40
|
+
idle_time = opts[:idle_time] || DEF_IDLE_TIME
|
41
|
+
|
42
|
+
#chunk_count.times.each {|i| @outstanding_chunks << i}
|
43
|
+
|
44
|
+
exchange = channel.topic(topic, :auto_delete => true)
|
45
|
+
OmfCommon.eventloop.defer do
|
46
|
+
_send(f, chunk_size, chunk_count, exchange, idle_time)
|
47
|
+
end
|
48
|
+
|
49
|
+
control_topic = "#{topic}_control"
|
50
|
+
control_exchange = channel.topic(control_topic, :auto_delete => true)
|
51
|
+
channel.queue("", :exclusive => false) do |queue|
|
52
|
+
queue.bind(control_exchange)
|
53
|
+
debug "Subscribing to control channel '#{control_topic}'"
|
54
|
+
queue.subscribe do |headers, payload|
|
55
|
+
hdrs = headers.headers
|
56
|
+
debug "Incoming control message '#{hdrs}'"
|
57
|
+
from = hdrs['request_from']
|
58
|
+
from = 0 if from < 0
|
59
|
+
to = hdrs['request_to']
|
60
|
+
to = chunk_count - 1 if !to || to >= chunk_count
|
61
|
+
synchronize do
|
62
|
+
(from .. to).each { |i| @outstanding_chunks << i}
|
63
|
+
@semaphore.signal
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@control_queue = queue
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def _send(f, chunk_size, chunk_count, exchange, idle_time)
|
71
|
+
chunks_to_send = nil
|
72
|
+
@sent_chunk = false
|
73
|
+
_wait_for_closedown(idle_time)
|
74
|
+
loop do
|
75
|
+
synchronize do
|
76
|
+
@semaphore.wait_while { @outstanding_chunks.empty? && @running }
|
77
|
+
return unless @running # done!
|
78
|
+
chunks_to_send = @outstanding_chunks.to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
chunks_to_send.each do |chunk_id|
|
82
|
+
#sleep 3
|
83
|
+
synchronize do
|
84
|
+
@outstanding_chunks.delete(chunk_id)
|
85
|
+
@sent_chunk = true
|
86
|
+
end
|
87
|
+
offset = chunk_id * chunk_size
|
88
|
+
f.seek(offset, IO::SEEK_SET)
|
89
|
+
chunk = f.read(chunk_size)
|
90
|
+
payload = Base64.encode64(chunk)
|
91
|
+
headers = {chunk_id: chunk_id, chunk_count: chunk_count, chunk_offset: offset,
|
92
|
+
chunk_size: payload.size,
|
93
|
+
path: f.path, file_size: f.size, mime_type: @mime_type}
|
94
|
+
debug "Sending chunk #{chunk_id}"
|
95
|
+
exchange.publish(payload, {headers: headers})
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def _wait_for_closedown(idle_time)
|
101
|
+
OmfCommon.eventloop.after(idle_time) do
|
102
|
+
done = false
|
103
|
+
synchronize do
|
104
|
+
done = !@sent_chunk && @outstanding_chunks.empty?
|
105
|
+
@sent_chunk = false
|
106
|
+
end
|
107
|
+
if done
|
108
|
+
@control_queue.unsubscribe if @control_queue
|
109
|
+
@block.call({action: :done}) if @block
|
110
|
+
else
|
111
|
+
# there was activity in last interval, wait a bit longer
|
112
|
+
_wait_for_closedown(idle_time)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Receives a file broadcast on 'topic' and stores it in a local file.
|
119
|
+
# Optionally, it can report on progress through a provided block.
|
120
|
+
#
|
121
|
+
class FileReceiver
|
122
|
+
include MonitorMixin
|
123
|
+
|
124
|
+
WAIT_BEFORE_REQUESTING = 2
|
125
|
+
WAIT_BEFORE_REQUESTING_EVERYTHING = 3 * WAIT_BEFORE_REQUESTING
|
126
|
+
|
127
|
+
# @param topic[String] Name of topic to receive file on
|
128
|
+
# @param file_path[String] Path to a local file
|
129
|
+
# @param opts[Hash]
|
130
|
+
# @param block Called on progress.
|
131
|
+
#
|
132
|
+
def initialize(file_path, channel, topic, opts = {}, &block)
|
133
|
+
super() # init monitor mixin
|
134
|
+
f = File.open(file_path, 'wb')
|
135
|
+
@running = false
|
136
|
+
@received_chunks = false
|
137
|
+
@outstanding_chunks = Set.new
|
138
|
+
@all_requested = false # set to true if we encountered a request for ALL (no 'to')
|
139
|
+
@requested_chunks = Set.new
|
140
|
+
@received_anything = false
|
141
|
+
|
142
|
+
control_topic = "#{topic}_control"
|
143
|
+
@control_exchange = channel.topic(control_topic, :auto_delete => true)
|
144
|
+
channel.queue("", :exclusive => false) do |queue|
|
145
|
+
queue.bind(@control_exchange)
|
146
|
+
debug "Subscribing to control topic '#{control_topic}'"
|
147
|
+
queue.subscribe do |headers, payload|
|
148
|
+
hdrs = headers.headers
|
149
|
+
debug "Incoming control message '#{hdrs}'"
|
150
|
+
from = hdrs['request_from']
|
151
|
+
to = hdrs['request_to']
|
152
|
+
synchronize do
|
153
|
+
if to
|
154
|
+
(from .. to).each { |i| @requested_chunks << i}
|
155
|
+
else
|
156
|
+
debug "Observed request for everything"
|
157
|
+
@all_requested = true
|
158
|
+
@nothing_received = -1 * WAIT_BEFORE_REQUESTING # Throttle our own desire to request everything
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
@control_queue = queue
|
163
|
+
end
|
164
|
+
|
165
|
+
@nothing_received = WAIT_BEFORE_REQUESTING_EVERYTHING - 2 * WAIT_BEFORE_REQUESTING
|
166
|
+
|
167
|
+
data_exchange = channel.topic(topic, :auto_delete => true)
|
168
|
+
channel.queue("", :exclusive => false) do |queue|
|
169
|
+
queue.bind(data_exchange)
|
170
|
+
queue.subscribe do |headers, payload|
|
171
|
+
synchronize do
|
172
|
+
@received_chunks = true
|
173
|
+
end
|
174
|
+
hdrs = headers.headers
|
175
|
+
chunk_id = hdrs['chunk_id']
|
176
|
+
chunk_offset = hdrs['chunk_offset']
|
177
|
+
chunk_count = hdrs['chunk_count']
|
178
|
+
unless chunk_id && chunk_offset && chunk_count
|
179
|
+
debug "Received message with missing 'chunk_id' or 'chunk_offset' header information (#{hdrs})"
|
180
|
+
end
|
181
|
+
unless @received_anything
|
182
|
+
@outstanding_chunks = chunk_count.times.to_set
|
183
|
+
synchronize do
|
184
|
+
@running = true
|
185
|
+
@received_anything = true
|
186
|
+
end
|
187
|
+
end
|
188
|
+
next unless @outstanding_chunks.include?(chunk_id)
|
189
|
+
|
190
|
+
debug "Receiving chunk #{chunk_id}"
|
191
|
+
f.seek(chunk_offset, IO::SEEK_SET)
|
192
|
+
f.write(Base64.decode64(payload))
|
193
|
+
@outstanding_chunks.delete(chunk_id)
|
194
|
+
received = chunk_count - @outstanding_chunks.size
|
195
|
+
if block
|
196
|
+
block.call({action: :progress, received: received, progress: 1.0 * received / chunk_count, total: chunk_count})
|
197
|
+
end
|
198
|
+
|
199
|
+
if @outstanding_chunks.empty?
|
200
|
+
# got everything
|
201
|
+
f.close
|
202
|
+
queue.unsubscribe
|
203
|
+
@control_queue.unsubscribe if @control_queue
|
204
|
+
@timer.cancel
|
205
|
+
synchronize { @running = false }
|
206
|
+
debug "Fully received #{file_path}"
|
207
|
+
if block
|
208
|
+
block.call({action: :done, size: hdrs['file_size'],
|
209
|
+
path: file_path, mime_type: hdrs['mime_type'],
|
210
|
+
received: chunk_count})
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
@timer = OmfCommon.eventloop.every(WAIT_BEFORE_REQUESTING) do
|
217
|
+
from = to = nil
|
218
|
+
synchronize do
|
219
|
+
#puts "RUNNING: #{@running}"
|
220
|
+
#break unless @running
|
221
|
+
if @received_chunks
|
222
|
+
@received_chunks = false
|
223
|
+
@nothing_received = 0
|
224
|
+
break # ok there is still action
|
225
|
+
else
|
226
|
+
# nothing happened, so let's ask for something
|
227
|
+
if (@nothing_received += WAIT_BEFORE_REQUESTING) >= WAIT_BEFORE_REQUESTING_EVERYTHING
|
228
|
+
# something stuck here, let's re-ask for everything
|
229
|
+
from = 0
|
230
|
+
@nothing_received = 0
|
231
|
+
else
|
232
|
+
# ask_for is the set of chunks we are still missing but haven't asked for
|
233
|
+
ask_for = @outstanding_chunks - @requested_chunks
|
234
|
+
break if ask_for.empty? # ok, someone already asked, so better wait
|
235
|
+
|
236
|
+
# Ask for a single span of consecutive chunks
|
237
|
+
aa = ask_for.to_a.sort
|
238
|
+
from = to = aa[0]
|
239
|
+
aa.each.with_index do |e, i|
|
240
|
+
break unless (from + i == e)
|
241
|
+
to = e
|
242
|
+
@requested_chunks << e
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
if from
|
249
|
+
headers = {request_from: from}
|
250
|
+
headers[:request_to] = to if to # if nil, ask for everything
|
251
|
+
@control_exchange.publish(nil, {headers: headers})
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
end
|