bunny 0.2.0 → 0.3.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/LICENSE +20 -0
- data/README +49 -0
- data/Rakefile +3 -4
- data/bunny.gemspec +37 -0
- data/examples/simple.rb +1 -1
- data/examples/simple_ack.rb +1 -1
- data/examples/simple_consumer.rb +1 -1
- data/examples/simple_fanout.rb +1 -1
- data/examples/simple_publisher.rb +1 -1
- data/examples/simple_topic.rb +3 -3
- data/{protocol → ext}/amqp-0.8.json +0 -0
- data/ext/codegen.rb +177 -0
- data/lib/bunny.rb +20 -62
- data/lib/bunny/client.rb +161 -14
- data/lib/bunny/exchange.rb +155 -80
- data/lib/bunny/protocol/protocol.rb +135 -0
- data/lib/bunny/protocol/spec.rb +836 -0
- data/lib/bunny/queue.rb +223 -23
- data/lib/bunny/transport/buffer.rb +266 -0
- data/lib/bunny/transport/frame.rb +62 -0
- data/spec/bunny_spec.rb +2 -2
- data/spec/exchange_spec.rb +3 -3
- data/spec/queue_spec.rb +10 -9
- metadata +22 -22
- data/README.markdown +0 -126
- data/lib/api_messages.rb +0 -18
- data/lib/bunny/header.rb +0 -30
- data/lib/engineroom/buffer.rb +0 -274
- data/lib/engineroom/frame.rb +0 -61
- data/lib/engineroom/protocol.rb +0 -156
- data/lib/engineroom/spec.rb +0 -830
- data/protocol/codegen.rb +0 -171
data/lib/bunny/queue.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
|
-
module
|
1
|
+
module Bunny
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
Queues store and forward messages. Queues can be configured in the server or created at runtime.
|
8
|
+
Queues must be attached to at least one exchange in order to receive messages from publishers.
|
9
|
+
|
10
|
+
=end
|
11
|
+
|
2
12
|
class Queue
|
3
|
-
|
4
13
|
attr_reader :name, :client
|
5
14
|
attr_accessor :delivery_tag
|
6
15
|
|
7
16
|
def initialize(client, name, opts = {})
|
8
17
|
# check connection to server
|
9
|
-
raise
|
18
|
+
raise Bunny::ConnectionError, 'Not connected to server' if client.status == :not_connected
|
10
19
|
|
11
20
|
@client = client
|
12
21
|
@opts = opts
|
@@ -21,8 +30,25 @@ module API
|
|
21
30
|
Protocol::Queue::Declare.new({ :queue => name, :nowait => false }.merge(opts))
|
22
31
|
)
|
23
32
|
|
24
|
-
raise
|
33
|
+
raise Bunny::ProtocolError, "Error declaring queue #{name}" unless client.next_method.is_a?(Protocol::Queue::DeclareOk)
|
25
34
|
end
|
35
|
+
|
36
|
+
=begin rdoc
|
37
|
+
|
38
|
+
=== DESCRIPTION:
|
39
|
+
|
40
|
+
Acknowledges one or more messages delivered via the _Deliver_ or _Get_-_Ok_ methods. The client can
|
41
|
+
ask to confirm a single message or a set of messages up to and including a specific message.
|
42
|
+
|
43
|
+
==== OPTIONS:
|
44
|
+
|
45
|
+
* <tt>:delivery_tag</tt>
|
46
|
+
* <tt>:multiple => true or false (_default_)</tt> - If set to _true_, the delivery tag is treated
|
47
|
+
as "up to and including", so that the client can acknowledge multiple messages with a single
|
48
|
+
method. If set to _false_, the delivery tag refers to a single message. If the multiple field
|
49
|
+
is _true_, and the delivery tag is zero, tells the server to acknowledge all outstanding messages.
|
50
|
+
|
51
|
+
=end
|
26
52
|
|
27
53
|
def ack
|
28
54
|
client.send_frame(
|
@@ -33,6 +59,29 @@ module API
|
|
33
59
|
self.delivery_tag = nil
|
34
60
|
end
|
35
61
|
|
62
|
+
=begin rdoc
|
63
|
+
|
64
|
+
=== DESCRIPTION:
|
65
|
+
|
66
|
+
Gets a message from a queue in a synchronous way. If error occurs, raises _Bunny_::_ProtocolError_.
|
67
|
+
|
68
|
+
==== OPTIONS:
|
69
|
+
|
70
|
+
* <tt>:header => true or false (_default_)</tt> - If set to _true_,
|
71
|
+
hash <tt>{:header, :delivery_details, :payload}</tt> is returned.
|
72
|
+
* <tt>:no_ack => true (_default_) or false</tt> - If set to _true_, the server does not expect an
|
73
|
+
acknowledgement message from the client. If set to _false_, the server expects an acknowledgement
|
74
|
+
message from the client and will re-queue the message if it does not receive one within a time specified
|
75
|
+
by the server.
|
76
|
+
|
77
|
+
==== RETURNS:
|
78
|
+
|
79
|
+
If <tt>:header => true</tt> returns hash <tt>{:header, :delivery_details, :payload}</tt>. <tt>:delivery_details</tt> is
|
80
|
+
a hash <tt>{:delivery_tag, :redelivered, :exchange, :routing_key, :message_count}</tt>. If
|
81
|
+
<tt>:header => false</tt> only the message payload is returned.
|
82
|
+
|
83
|
+
=end
|
84
|
+
|
36
85
|
def pop(opts = {})
|
37
86
|
|
38
87
|
# do we want the message header?
|
@@ -51,9 +100,9 @@ module API
|
|
51
100
|
method = client.next_method
|
52
101
|
|
53
102
|
if method.is_a?(Protocol::Basic::GetEmpty) then
|
54
|
-
return
|
103
|
+
return :queue_empty
|
55
104
|
elsif !method.is_a?(Protocol::Basic::GetOk)
|
56
|
-
raise
|
105
|
+
raise Bunny::ProtocolError, "Error getting message from queue #{name}"
|
57
106
|
end
|
58
107
|
|
59
108
|
# get delivery tag to use for acknowledge
|
@@ -61,39 +110,107 @@ module API
|
|
61
110
|
|
62
111
|
header = client.next_payload
|
63
112
|
msg = client.next_payload
|
64
|
-
raise
|
113
|
+
raise Bunny::MessageError, 'unexpected length' if msg.length < header.size
|
65
114
|
|
66
|
-
|
115
|
+
# Return message with additional info if requested
|
116
|
+
hdr ? {:header => header, :payload => msg, :delivery_details => method.arguments} : msg
|
67
117
|
|
68
118
|
end
|
69
119
|
|
120
|
+
=begin rdoc
|
121
|
+
|
122
|
+
=== DESCRIPTION:
|
123
|
+
|
124
|
+
Publishes a message to the queue via the default nameless '' direct exchange.
|
125
|
+
|
126
|
+
==== RETURNS:
|
127
|
+
|
128
|
+
nil
|
129
|
+
|
130
|
+
=end
|
131
|
+
|
70
132
|
def publish(data, opts = {})
|
71
133
|
exchange.publish(data, opts)
|
72
134
|
end
|
73
135
|
|
136
|
+
=begin rdoc
|
137
|
+
|
138
|
+
=== DESCRIPTION:
|
139
|
+
|
140
|
+
Returns message count from Queue#status.
|
141
|
+
|
142
|
+
=end
|
143
|
+
|
74
144
|
def message_count
|
75
145
|
s = status
|
76
146
|
s[:message_count]
|
77
147
|
end
|
78
148
|
|
149
|
+
=begin rdoc
|
150
|
+
|
151
|
+
=== DESCRIPTION:
|
152
|
+
|
153
|
+
Returns consumer count from Queue#status.
|
154
|
+
|
155
|
+
=end
|
156
|
+
|
79
157
|
def consumer_count
|
80
158
|
s = status
|
81
159
|
s[:consumer_count]
|
82
160
|
end
|
83
|
-
|
84
|
-
|
161
|
+
|
162
|
+
=begin rdoc
|
163
|
+
|
164
|
+
=== DESCRIPTION:
|
165
|
+
|
166
|
+
Returns hash {:message_count, :consumer_count}.
|
167
|
+
|
168
|
+
=end
|
169
|
+
|
170
|
+
def status
|
85
171
|
client.send_frame(
|
86
|
-
Protocol::Queue::Declare.new({ :queue => name, :passive => true }
|
172
|
+
Protocol::Queue::Declare.new({ :queue => name, :passive => true })
|
87
173
|
)
|
88
174
|
method = client.next_method
|
89
175
|
{:message_count => method.message_count, :consumer_count => method.consumer_count}
|
90
176
|
end
|
177
|
+
|
178
|
+
=begin rdoc
|
179
|
+
|
180
|
+
=== DESCRIPTION:
|
181
|
+
|
182
|
+
Asks the server to start a "consumer", which is a transient request for messages from a specific
|
183
|
+
queue. Consumers last as long as the channel they were created on, or until the client cancels them
|
184
|
+
with an _unsubscribe_. Every time a message reaches the queue it is passed to the _blk_ for
|
185
|
+
processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
|
186
|
+
|
187
|
+
==== OPTIONS:
|
188
|
+
* <tt>:header => true or false (_default_)</tt> - If set to _true_, hash is delivered for each message
|
189
|
+
<tt>{:header, :delivery_details, :payload}</tt>.
|
190
|
+
* <tt>:consumer_tag => '_tag_'</tt> - Specifies the identifier for the consumer. The consumer tag is
|
191
|
+
local to a connection, so two clients can use the same consumer tags. If this field is empty the
|
192
|
+
queue name is used.
|
193
|
+
* <tt>:no_ack=> true (_default_) or false</tt> - If set to _true_, the server does not expect an
|
194
|
+
acknowledgement message from the client. If set to _false_, the server expects an acknowledgement
|
195
|
+
message from the client and will re-queue the message if it does not receive one within a time specified
|
196
|
+
by the server.
|
197
|
+
* <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
|
198
|
+
only this consumer can access the queue.
|
199
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
200
|
+
|
201
|
+
==== RETURNS:
|
202
|
+
|
203
|
+
If <tt>:header => true</tt> returns hash <tt>{:header, :delivery_details, :payload}</tt> for each message.
|
204
|
+
<tt>:delivery_details</tt> is a hash <tt>{:consumer_tag, :delivery_tag, :redelivered, :exchange, :routing_key}</tt>.
|
205
|
+
If <tt>:header => false</tt> only message payload is returned.
|
206
|
+
|
207
|
+
=end
|
91
208
|
|
92
209
|
def subscribe(opts = {}, &blk)
|
93
210
|
consumer_tag = opts[:consumer_tag] || name
|
94
211
|
|
95
|
-
# ignore the :nowait option if passed, otherwise program will
|
96
|
-
#
|
212
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
213
|
+
# response from the server causing an error.
|
97
214
|
opts.delete(:nowait)
|
98
215
|
|
99
216
|
# do we want the message header?
|
@@ -109,12 +226,13 @@ module API
|
|
109
226
|
:nowait => false }.merge(opts))
|
110
227
|
)
|
111
228
|
|
112
|
-
raise
|
229
|
+
raise Bunny::ProtocolError,
|
113
230
|
"Error subscribing to queue #{name}" unless
|
114
231
|
client.next_method.is_a?(Protocol::Basic::ConsumeOk)
|
115
232
|
|
116
233
|
while true
|
117
234
|
method = client.next_method
|
235
|
+
|
118
236
|
break if method.is_a?(Protocol::Basic::CancelOk)
|
119
237
|
|
120
238
|
# get delivery tag to use for acknowledge
|
@@ -122,19 +240,56 @@ module API
|
|
122
240
|
|
123
241
|
header = client.next_payload
|
124
242
|
msg = client.next_payload
|
125
|
-
raise
|
243
|
+
raise Bunny::MessageError, 'unexpected length' if msg.length < header.size
|
126
244
|
|
127
|
-
# pass the message to the block for processing
|
128
|
-
blk.call(hdr ? {:header => header, :payload => msg} : msg)
|
245
|
+
# pass the message and related info, if requested, to the block for processing
|
246
|
+
blk.call(hdr ? {:header => header, :payload => msg, :delivery_details => method.arguments} : msg)
|
129
247
|
end
|
130
248
|
|
131
249
|
end
|
132
250
|
|
251
|
+
=begin rdoc
|
252
|
+
|
253
|
+
=== DESCRIPTION:
|
254
|
+
|
255
|
+
Cancels a consumer. This does not affect already delivered messages, but it does mean
|
256
|
+
the server will not send any more messages for that consumer.
|
257
|
+
|
258
|
+
==== OPTIONS:
|
259
|
+
|
260
|
+
* <tt>:consumer_tag => '_tag_'</tt> - Specifies the identifier for the consumer.
|
261
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
262
|
+
|
263
|
+
=end
|
264
|
+
|
133
265
|
def unsubscribe(opts = {})
|
134
266
|
consumer_tag = opts[:consumer_tag] || name
|
267
|
+
|
268
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
269
|
+
# response from the server causing an error
|
270
|
+
opts.delete(:nowait)
|
271
|
+
|
135
272
|
client.send_frame( Protocol::Basic::Cancel.new({ :consumer_tag => consumer_tag }.merge(opts)))
|
273
|
+
|
136
274
|
end
|
137
275
|
|
276
|
+
=begin rdoc
|
277
|
+
|
278
|
+
=== DESCRIPTION:
|
279
|
+
|
280
|
+
Binds a queue to an exchange. Until a queue is bound it will not receive any messages. Queues are
|
281
|
+
bound to the direct exchange '' by default. If error occurs, a _Bunny_::_ProtocolError_ is raised.
|
282
|
+
|
283
|
+
* <tt>:key => 'routing key'* <tt>:key => 'routing_key'</tt> - Specifies the routing key for
|
284
|
+
the binding. The routing key is used for routing messages depending on the exchange configuration.
|
285
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
286
|
+
|
287
|
+
==== RETURNS:
|
288
|
+
|
289
|
+
<tt>:bind_ok</tt> if successful.
|
290
|
+
|
291
|
+
=end
|
292
|
+
|
138
293
|
def bind(exchange, opts = {})
|
139
294
|
exchange = exchange.respond_to?(:name) ? exchange.name : exchange
|
140
295
|
|
@@ -150,16 +305,38 @@ module API
|
|
150
305
|
:nowait => false }.merge(opts))
|
151
306
|
)
|
152
307
|
|
153
|
-
raise
|
308
|
+
raise Bunny::ProtocolError,
|
154
309
|
"Error binding queue #{name}" unless
|
155
310
|
client.next_method.is_a?(Protocol::Queue::BindOk)
|
156
311
|
|
157
312
|
# return message
|
158
|
-
|
313
|
+
:bind_ok
|
159
314
|
end
|
160
315
|
|
316
|
+
=begin rdoc
|
317
|
+
|
318
|
+
=== DESCRIPTION:
|
319
|
+
|
320
|
+
Removes a queue binding from an exchange. If error occurs, a _Bunny_::_ProtocolError_ is raised.
|
321
|
+
|
322
|
+
==== OPTIONS:
|
323
|
+
* <tt>:key => 'routing key'* <tt>:key => 'routing_key'</tt> - Specifies the routing key for
|
324
|
+
the binding.
|
325
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
326
|
+
|
327
|
+
==== RETURNS:
|
328
|
+
|
329
|
+
<tt>:unbind_ok</tt> if successful.
|
330
|
+
|
331
|
+
=end
|
332
|
+
|
161
333
|
def unbind(exchange, opts = {})
|
162
334
|
exchange = exchange.respond_to?(:name) ? exchange.name : exchange
|
335
|
+
|
336
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
337
|
+
# response that will not be sent by the server
|
338
|
+
opts.delete(:nowait)
|
339
|
+
|
163
340
|
bindings.delete(exchange)
|
164
341
|
|
165
342
|
client.send_frame(
|
@@ -170,13 +347,36 @@ module API
|
|
170
347
|
)
|
171
348
|
)
|
172
349
|
|
173
|
-
raise
|
350
|
+
raise Bunny::ProtocolError,
|
174
351
|
"Error unbinding queue #{name}" unless
|
175
352
|
client.next_method.is_a?(Protocol::Queue::UnbindOk)
|
176
353
|
|
177
354
|
# return message
|
178
|
-
|
355
|
+
:unbind_ok
|
179
356
|
end
|
357
|
+
|
358
|
+
=begin rdoc
|
359
|
+
|
360
|
+
=== DESCRIPTION:
|
361
|
+
|
362
|
+
Requests that a queue is deleted from broker/server. When a queue is deleted any pending messages
|
363
|
+
are sent to a dead-letter queue if this is defined in the server configuration. Removes reference
|
364
|
+
from queues if successful. If an error occurs raises _Bunny_::_ProtocolError_.
|
365
|
+
|
366
|
+
==== Options:
|
367
|
+
|
368
|
+
* <tt>:if_unused => true or false (_default_)</tt> - If set to _true_, the server will only
|
369
|
+
delete the queue if it has no consumers. If the queue has consumers the server does not
|
370
|
+
delete it but raises a channel exception instead.
|
371
|
+
* <tt>:if_empty => true or false (_default_)</tt> - If set to _true_, the server will only
|
372
|
+
delete the queue if it has no messages. If the queue is not empty the server raises a channel
|
373
|
+
exception.
|
374
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
375
|
+
|
376
|
+
==== Returns:
|
377
|
+
|
378
|
+
<tt>:delete_ok</tt> if successful
|
379
|
+
=end
|
180
380
|
|
181
381
|
def delete(opts = {})
|
182
382
|
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
@@ -187,14 +387,14 @@ module API
|
|
187
387
|
Protocol::Queue::Delete.new({ :queue => name, :nowait => false }.merge(opts))
|
188
388
|
)
|
189
389
|
|
190
|
-
raise
|
390
|
+
raise Bunny::ProtocolError,
|
191
391
|
"Error deleting queue #{name}" unless
|
192
392
|
client.next_method.is_a?(Protocol::Queue::DeleteOk)
|
193
393
|
|
194
394
|
client.queues.delete(name)
|
195
395
|
|
196
396
|
# return confirmation
|
197
|
-
|
397
|
+
:delete_ok
|
198
398
|
end
|
199
399
|
|
200
400
|
private
|
@@ -0,0 +1,266 @@
|
|
1
|
+
module Bunny
|
2
|
+
module Transport #:nodoc: all
|
3
|
+
class Buffer
|
4
|
+
|
5
|
+
def initialize data = ''
|
6
|
+
@data = data
|
7
|
+
@pos = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :pos
|
11
|
+
|
12
|
+
def data
|
13
|
+
@data.clone
|
14
|
+
end
|
15
|
+
alias :contents :data
|
16
|
+
alias :to_s :data
|
17
|
+
|
18
|
+
def << data
|
19
|
+
@data << data.to_s
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def length
|
24
|
+
@data.length
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
pos == length
|
29
|
+
end
|
30
|
+
|
31
|
+
def rewind
|
32
|
+
@pos = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def read_properties *types
|
36
|
+
types.shift if types.first == :properties
|
37
|
+
|
38
|
+
i = 0
|
39
|
+
values = []
|
40
|
+
|
41
|
+
while props = read(:short)
|
42
|
+
(0..14).each do |n|
|
43
|
+
# no more property types
|
44
|
+
break unless types[i]
|
45
|
+
|
46
|
+
# if flag is set
|
47
|
+
if props & (1<<(15-n)) != 0
|
48
|
+
if types[i] == :bit
|
49
|
+
# bit values exist in flags only
|
50
|
+
values << true
|
51
|
+
else
|
52
|
+
# save type name for later reading
|
53
|
+
values << types[i]
|
54
|
+
end
|
55
|
+
else
|
56
|
+
# property not set or is false bit
|
57
|
+
values << (types[i] == :bit ? false : nil)
|
58
|
+
end
|
59
|
+
|
60
|
+
i+=1
|
61
|
+
end
|
62
|
+
|
63
|
+
# bit(0) == 0 means no more property flags
|
64
|
+
break unless props & 1 == 1
|
65
|
+
end
|
66
|
+
|
67
|
+
values.map do |value|
|
68
|
+
value.is_a?(Symbol) ? read(value) : value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def read *types
|
73
|
+
if types.first == :properties
|
74
|
+
return read_properties(*types)
|
75
|
+
end
|
76
|
+
|
77
|
+
values = types.map do |type|
|
78
|
+
case type
|
79
|
+
when :octet
|
80
|
+
_read(1, 'C')
|
81
|
+
when :short
|
82
|
+
_read(2, 'n')
|
83
|
+
when :long
|
84
|
+
_read(4, 'N')
|
85
|
+
when :longlong
|
86
|
+
upper, lower = _read(8, 'NN')
|
87
|
+
upper << 32 | lower
|
88
|
+
when :shortstr
|
89
|
+
_read read(:octet)
|
90
|
+
when :longstr
|
91
|
+
_read read(:long)
|
92
|
+
when :timestamp
|
93
|
+
Time.at read(:longlong)
|
94
|
+
when :table
|
95
|
+
t = Hash.new
|
96
|
+
|
97
|
+
table = Buffer.new(read(:longstr))
|
98
|
+
until table.empty?
|
99
|
+
key, type = table.read(:shortstr, :octet)
|
100
|
+
key = key.intern
|
101
|
+
t[key] ||= case type
|
102
|
+
when 83 # 'S'
|
103
|
+
table.read(:longstr)
|
104
|
+
when 73 # 'I'
|
105
|
+
table.read(:long)
|
106
|
+
when 68 # 'D'
|
107
|
+
exp = table.read(:octet)
|
108
|
+
num = table.read(:long)
|
109
|
+
num / 10.0**exp
|
110
|
+
when 84 # 'T'
|
111
|
+
table.read(:timestamp)
|
112
|
+
when 70 # 'F'
|
113
|
+
table.read(:table)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
t
|
118
|
+
when :bit
|
119
|
+
if (@bits ||= []).empty?
|
120
|
+
val = read(:octet)
|
121
|
+
@bits = (0..7).map{|i| (val & 1<<i) != 0 }
|
122
|
+
end
|
123
|
+
|
124
|
+
@bits.shift
|
125
|
+
else
|
126
|
+
raise Bunny::InvalidTypeError, "Cannot read data of type #{type}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
types.size == 1 ? values.first : values
|
131
|
+
end
|
132
|
+
|
133
|
+
def write type, data
|
134
|
+
case type
|
135
|
+
when :octet
|
136
|
+
_write(data, 'C')
|
137
|
+
when :short
|
138
|
+
_write(data, 'n')
|
139
|
+
when :long
|
140
|
+
_write(data, 'N')
|
141
|
+
when :longlong
|
142
|
+
lower = data & 0xffffffff
|
143
|
+
upper = (data & ~0xffffffff) >> 32
|
144
|
+
_write([upper, lower], 'NN')
|
145
|
+
when :shortstr
|
146
|
+
data = (data || '').to_s
|
147
|
+
_write([data.length, data], 'Ca*')
|
148
|
+
when :longstr
|
149
|
+
if data.is_a? Hash
|
150
|
+
write(:table, data)
|
151
|
+
else
|
152
|
+
data = (data || '').to_s
|
153
|
+
_write([data.length, data], 'Na*')
|
154
|
+
end
|
155
|
+
when :timestamp
|
156
|
+
write(:longlong, data.to_i)
|
157
|
+
when :table
|
158
|
+
data ||= {}
|
159
|
+
write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
|
160
|
+
table.write(:shortstr, key.to_s)
|
161
|
+
|
162
|
+
case value
|
163
|
+
when String
|
164
|
+
table.write(:octet, 83) # 'S'
|
165
|
+
table.write(:longstr, value.to_s)
|
166
|
+
when Fixnum
|
167
|
+
table.write(:octet, 73) # 'I'
|
168
|
+
table.write(:long, value)
|
169
|
+
when Float
|
170
|
+
table.write(:octet, 68) # 'D'
|
171
|
+
# XXX there's gotta be a better way to do this..
|
172
|
+
exp = value.to_s.split('.').last.length
|
173
|
+
num = value * 10**exp
|
174
|
+
table.write(:octet, exp)
|
175
|
+
table.write(:long, num)
|
176
|
+
when Time
|
177
|
+
table.write(:octet, 84) # 'T'
|
178
|
+
table.write(:timestamp, value)
|
179
|
+
when Hash
|
180
|
+
table.write(:octet, 70) # 'F'
|
181
|
+
table.write(:table, value)
|
182
|
+
end
|
183
|
+
|
184
|
+
table
|
185
|
+
end)
|
186
|
+
when :bit
|
187
|
+
[*data].to_enum(:each_slice, 8).each{|bits|
|
188
|
+
write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
|
189
|
+
byte |= 1<<i if bit
|
190
|
+
byte
|
191
|
+
})
|
192
|
+
}
|
193
|
+
when :properties
|
194
|
+
values = []
|
195
|
+
data.enum_with_index.inject(0) do |short, ((type, value), i)|
|
196
|
+
n = i % 15
|
197
|
+
last = i+1 == data.size
|
198
|
+
|
199
|
+
if (n == 0 and i != 0) or last
|
200
|
+
if data.size > i+1
|
201
|
+
short |= 1<<0
|
202
|
+
elsif last and value
|
203
|
+
values << [type,value]
|
204
|
+
short |= 1<<(15-n)
|
205
|
+
end
|
206
|
+
|
207
|
+
write(:short, short)
|
208
|
+
short = 0
|
209
|
+
end
|
210
|
+
|
211
|
+
if value and !last
|
212
|
+
values << [type,value]
|
213
|
+
short |= 1<<(15-n)
|
214
|
+
end
|
215
|
+
|
216
|
+
short
|
217
|
+
end
|
218
|
+
|
219
|
+
values.each do |type, value|
|
220
|
+
write(type, value) unless type == :bit
|
221
|
+
end
|
222
|
+
else
|
223
|
+
raise Bunny::InvalidTypeError, "Cannot write data of type #{type}"
|
224
|
+
end
|
225
|
+
|
226
|
+
self
|
227
|
+
end
|
228
|
+
|
229
|
+
def extract
|
230
|
+
begin
|
231
|
+
cur_data, cur_pos = @data.clone, @pos
|
232
|
+
yield self
|
233
|
+
rescue Bunny::BufferOverflowError
|
234
|
+
@data, @pos = cur_data, cur_pos
|
235
|
+
nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def _read(size, pack = nil)
|
240
|
+
if @data.is_a?(Bunny::Client)
|
241
|
+
raw = @data.read(size)
|
242
|
+
return raw if raw.nil? or pack.nil?
|
243
|
+
return raw.unpack(pack).first
|
244
|
+
end
|
245
|
+
|
246
|
+
if @pos + size > length
|
247
|
+
raise Bunny::BufferOverflowError
|
248
|
+
else
|
249
|
+
data = @data[@pos,size]
|
250
|
+
@data[@pos,size] = ''
|
251
|
+
if pack
|
252
|
+
data = data.unpack(pack)
|
253
|
+
data = data.pop if data.size == 1
|
254
|
+
end
|
255
|
+
data
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def _write data, pack = nil
|
260
|
+
data = [*data].pack(pack) if pack
|
261
|
+
@data[@pos,0] = data
|
262
|
+
@pos += data.length
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|