omf_common 6.0.0 → 6.0.2.pre.1
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/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
|