maxlapshin-carrot 0.6.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.
@@ -0,0 +1,34 @@
1
+ # Carrot
2
+
3
+ A synchronous amqp client. Based on Aman's amqp client:
4
+
5
+ [http://github.com/tmm1/amqp/tree/master] (http://github.com/tmm1/amqp/tree/master)
6
+
7
+ ## Motivation
8
+
9
+ This client does not use eventmachine so no background thread necessary. As a result, it is much easier to use from script/console and Passenger. It also solves the problem of buffering messages and ack responses. For more details see [this thread] (http://groups.google.com/group/ruby-amqp/browse_thread/thread/fdae324a0ebb1961/fa185fdce1841b68).
10
+
11
+ There is currently no way to prevent buffering using eventmachine. Support for prefetch is still unreliable.
12
+
13
+
14
+ ## Example
15
+
16
+ require 'carrot'
17
+
18
+ q = Carrot.queue('name')
19
+ 10.times do |num|
20
+ q.publish(num.to_s)
21
+ end
22
+
23
+ puts "Queued #{q.message_count} messages"
24
+ puts
25
+
26
+ while msg = q.pop(:ack => true)
27
+ puts "Popping: #{msg}"
28
+ q.ack
29
+ end
30
+ Carrot.stop
31
+
32
+ # LICENSE
33
+
34
+ Copyright (c) 2009 Amos Elliston, Geni.com; Published under The MIT License, see License
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 6
@@ -0,0 +1,401 @@
1
+ if [].map.respond_to? :with_index
2
+ class Array #:nodoc:
3
+ def enum_with_index
4
+ each.with_index
5
+ end
6
+ end
7
+ else
8
+ require 'enumerator'
9
+ end
10
+
11
+ module Carrot::AMQP
12
+ class Buffer #:nodoc: all
13
+ class Overflow < StandardError; end
14
+ class InvalidType < StandardError; end
15
+
16
+ def initialize data = ''
17
+ @data = data
18
+ @pos = 0
19
+ end
20
+
21
+ attr_reader :pos
22
+
23
+ def data
24
+ @data.clone
25
+ end
26
+ alias :contents :data
27
+ alias :to_s :data
28
+
29
+ def << data
30
+ @data << data.to_s
31
+ self
32
+ end
33
+
34
+ def length
35
+ @data.length
36
+ end
37
+
38
+ def empty?
39
+ pos == length
40
+ end
41
+
42
+ def rewind
43
+ @pos = 0
44
+ end
45
+
46
+ def read_properties *types
47
+ types.shift if types.first == :properties
48
+
49
+ i = 0
50
+ values = []
51
+
52
+ while props = read(:short)
53
+ (0..14).each do |n|
54
+ # no more property types
55
+ break unless types[i]
56
+
57
+ # if flag is set
58
+ if props & (1<<(15-n)) != 0
59
+ if types[i] == :bit
60
+ # bit values exist in flags only
61
+ values << true
62
+ else
63
+ # save type name for later reading
64
+ values << types[i]
65
+ end
66
+ else
67
+ # property not set or is false bit
68
+ values << (types[i] == :bit ? false : nil)
69
+ end
70
+
71
+ i+=1
72
+ end
73
+
74
+ # bit(0) == 0 means no more property flags
75
+ break unless props & 1 == 1
76
+ end
77
+
78
+ values.map do |value|
79
+ value.is_a?(Symbol) ? read(value) : value
80
+ end
81
+ end
82
+
83
+ def read *types
84
+ if types.first == :properties
85
+ return read_properties(*types)
86
+ end
87
+
88
+ values = types.map do |type|
89
+ case type
90
+ when :octet
91
+ _read(1, 'C')
92
+ when :short
93
+ _read(2, 'n')
94
+ when :long
95
+ _read(4, 'N')
96
+ when :longlong
97
+ upper, lower = _read(8, 'NN')
98
+ upper << 32 | lower
99
+ when :shortstr
100
+ _read read(:octet)
101
+ when :longstr
102
+ _read read(:long)
103
+ when :timestamp
104
+ Time.at read(:longlong)
105
+ when :table
106
+ t = Hash.new
107
+
108
+ table = Buffer.new(read(:longstr))
109
+ until table.empty?
110
+ key, type = table.read(:shortstr, :octet)
111
+ key = key.intern
112
+ t[key] ||= case type
113
+ when 83 # 'S'
114
+ table.read(:longstr)
115
+ when 73 # 'I'
116
+ table.read(:long)
117
+ when 68 # 'D'
118
+ exp = table.read(:octet)
119
+ num = table.read(:long)
120
+ num / 10.0**exp
121
+ when 84 # 'T'
122
+ table.read(:timestamp)
123
+ when 70 # 'F'
124
+ table.read(:table)
125
+ end
126
+ end
127
+
128
+ t
129
+ when :bit
130
+ if (@bits ||= []).empty?
131
+ val = read(:octet)
132
+ @bits = (0..7).map{|i| (val & 1<<i) != 0 }
133
+ end
134
+
135
+ @bits.shift
136
+ else
137
+ raise InvalidType, "Cannot read data of type #{type}"
138
+ end
139
+ end
140
+
141
+ types.size == 1 ? values.first : values
142
+ end
143
+
144
+ def write type, data
145
+ case type
146
+ when :octet
147
+ _write(data, 'C')
148
+ when :short
149
+ _write(data, 'n')
150
+ when :long
151
+ _write(data, 'N')
152
+ when :longlong
153
+ lower = data & 0xffffffff
154
+ upper = (data & ~0xffffffff) >> 32
155
+ _write([upper, lower], 'NN')
156
+ when :shortstr
157
+ data = (data || '').to_s
158
+ _write([data.length, data], 'Ca*')
159
+ when :longstr
160
+ if data.is_a? Hash
161
+ write(:table, data)
162
+ else
163
+ data = (data || '').to_s
164
+ _write([data.length, data], 'Na*')
165
+ end
166
+ when :timestamp
167
+ write(:longlong, data.to_i)
168
+ when :table
169
+ data ||= {}
170
+ write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
171
+ table.write(:shortstr, key.to_s)
172
+
173
+ case value
174
+ when String
175
+ table.write(:octet, 83) # 'S'
176
+ table.write(:longstr, value.to_s)
177
+ when Fixnum
178
+ table.write(:octet, 73) # 'I'
179
+ table.write(:long, value)
180
+ when Float
181
+ table.write(:octet, 68) # 'D'
182
+ # XXX there's gotta be a better way to do this..
183
+ exp = value.to_s.split('.').last.length
184
+ num = value * 10**exp
185
+ table.write(:octet, exp)
186
+ table.write(:long, num)
187
+ when Time
188
+ table.write(:octet, 84) # 'T'
189
+ table.write(:timestamp, value)
190
+ when Hash
191
+ table.write(:octet, 70) # 'F'
192
+ table.write(:table, value)
193
+ end
194
+
195
+ table
196
+ end)
197
+ when :bit
198
+ [*data].to_enum(:each_slice, 8).each{|bits|
199
+ write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
200
+ byte |= 1<<i if bit
201
+ byte
202
+ })
203
+ }
204
+ when :properties
205
+ values = []
206
+ data.enum_with_index.inject(0) do |short, ((type, value), i)|
207
+ n = i % 15
208
+ last = i+1 == data.size
209
+
210
+ if (n == 0 and i != 0) or last
211
+ if data.size > i+1
212
+ short |= 1<<0
213
+ elsif last and value
214
+ values << [type,value]
215
+ short |= 1<<(15-n)
216
+ end
217
+
218
+ write(:short, short)
219
+ short = 0
220
+ end
221
+
222
+ if value and !last
223
+ values << [type,value]
224
+ short |= 1<<(15-n)
225
+ end
226
+
227
+ short
228
+ end
229
+
230
+ values.each do |type, value|
231
+ write(type, value) unless type == :bit
232
+ end
233
+ else
234
+ raise InvalidType, "Cannot write data of type #{type}"
235
+ end
236
+
237
+ self
238
+ end
239
+
240
+ def extract
241
+ begin
242
+ cur_data, cur_pos = @data.clone, @pos
243
+ yield self
244
+ rescue Overflow
245
+ @data, @pos = cur_data, cur_pos
246
+ nil
247
+ end
248
+ end
249
+
250
+ def _read(size, pack = nil)
251
+ if @data.is_a?(Server)
252
+ raw = @data.read(size)
253
+ return raw if raw.nil? or pack.nil?
254
+ return raw.unpack(pack).first
255
+ end
256
+
257
+ if @pos + size > length
258
+ raise Overflow
259
+ else
260
+ data = @data[@pos,size]
261
+ @data[@pos,size] = ''
262
+ if pack
263
+ data = data.unpack(pack)
264
+ data = data.pop if data.size == 1
265
+ end
266
+ data
267
+ end
268
+ end
269
+
270
+ def _write data, pack = nil
271
+ data = [*data].pack(pack) if pack
272
+ @data[@pos,0] = data
273
+ @pos += data.length
274
+ end
275
+ end
276
+ end
277
+
278
+ if $0 =~ /bacon/ or $0 == __FILE__
279
+ require 'bacon'
280
+ include AMQP
281
+
282
+ describe Buffer do
283
+ before do
284
+ @buf = Buffer.new
285
+ end
286
+
287
+ should 'have contents' do
288
+ @buf.contents.should == ''
289
+ end
290
+
291
+ should 'initialize with data' do
292
+ @buf = Buffer.new('abc')
293
+ @buf.contents.should == 'abc'
294
+ end
295
+
296
+ should 'append raw data' do
297
+ @buf << 'abc'
298
+ @buf << 'def'
299
+ @buf.contents.should == 'abcdef'
300
+ end
301
+
302
+ should 'append other buffers' do
303
+ @buf << Buffer.new('abc')
304
+ @buf.data.should == 'abc'
305
+ end
306
+
307
+ should 'have a position' do
308
+ @buf.pos.should == 0
309
+ end
310
+
311
+ should 'have a length' do
312
+ @buf.length.should == 0
313
+ @buf << 'abc'
314
+ @buf.length.should == 3
315
+ end
316
+
317
+ should 'know the end' do
318
+ @buf.empty?.should == true
319
+ end
320
+
321
+ should 'read and write data' do
322
+ @buf._write('abc')
323
+ @buf.rewind
324
+ @buf._read(2).should == 'ab'
325
+ @buf._read(1).should == 'c'
326
+ end
327
+
328
+ should 'raise on overflow' do
329
+ lambda{ @buf._read(1) }.should.raise Buffer::Overflow
330
+ end
331
+
332
+ should 'raise on invalid types' do
333
+ lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
334
+ lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
335
+ end
336
+
337
+ { :octet => 0b10101010,
338
+ :short => 100,
339
+ :long => 100_000_000,
340
+ :longlong => 666_555_444_333_222_111,
341
+ :shortstr => 'hello',
342
+ :longstr => 'bye'*500,
343
+ :timestamp => time = Time.at(Time.now.to_i),
344
+ :table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
345
+ :bit => true
346
+ }.each do |type, value|
347
+
348
+ should "read and write a #{type}" do
349
+ @buf.write(type, value)
350
+ @buf.rewind
351
+ @buf.read(type).should == value
352
+ @buf.should.be.empty
353
+ end
354
+
355
+ end
356
+
357
+ should 'read and write multiple bits' do
358
+ bits = [true, false, false, true, true, false, false, true, true, false]
359
+ @buf.write(:bit, bits)
360
+ @buf.write(:octet, 100)
361
+
362
+ @buf.rewind
363
+
364
+ bits.map do
365
+ @buf.read(:bit)
366
+ end.should == bits
367
+ @buf.read(:octet).should == 100
368
+ end
369
+
370
+ should 'read and write properties' do
371
+ properties = ([
372
+ [:octet, 1],
373
+ [:shortstr, 'abc'],
374
+ [:bit, true],
375
+ [:bit, false],
376
+ [:shortstr, nil],
377
+ [:timestamp, nil],
378
+ [:table, { :a => 'hash' }],
379
+ ]*5).sort_by{rand}
380
+
381
+ @buf.write(:properties, properties)
382
+ @buf.rewind
383
+ @buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
384
+ @buf.should.be.empty
385
+ end
386
+
387
+ should 'do transactional reads with #extract' do
388
+ @buf.write :octet, 8
389
+ orig = @buf.to_s
390
+
391
+ @buf.rewind
392
+ @buf.extract do |b|
393
+ b.read :octet
394
+ b.read :short
395
+ end
396
+
397
+ @buf.pos.should == 0
398
+ @buf.data.should == orig
399
+ end
400
+ end
401
+ end
@@ -0,0 +1,51 @@
1
+ module Carrot::AMQP
2
+ class Exchange
3
+ attr_reader :server, :type, :name, :opts, :key, :carrot
4
+
5
+ def initialize(carrot, type, name, opts = {})
6
+ @server, @type, @name, @opts = carrot.server, type, name, opts
7
+ @key = opts[:key]
8
+ @carrot = carrot
9
+
10
+ unless name == "amq.#{type}" or name == ''
11
+ server.send_frame(
12
+ Protocol::Exchange::Declare.new(
13
+ { :exchange => name, :type => type, :nowait => true }.merge(opts)
14
+ )
15
+ )
16
+ end
17
+ end
18
+ attr_reader :name, :type, :key
19
+
20
+ def publish(data, opts = {})
21
+ out = []
22
+
23
+ out << Protocol::Basic::Publish.new(
24
+ { :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
25
+ )
26
+ data = data.to_s
27
+ out << Protocol::Header.new(
28
+ Protocol::Basic,
29
+ data.length, {
30
+ :content_type => 'application/octet-stream',
31
+ :delivery_mode => (opts.delete(:persistent) ? 2 : 1),
32
+ :priority => 0
33
+ }.merge(opts)
34
+ )
35
+ out << Frame::Body.new(data)
36
+
37
+ server.send_frame(*out)
38
+ end
39
+
40
+ def delete(opts = {})
41
+ server.send_frame(
42
+ Protocol::Exchange::Delete.new({ :exchange => name, :nowait => true }.merge(opts))
43
+ )
44
+ carrot.exchanges.delete(name)
45
+ end
46
+
47
+ def reset
48
+ initialize(server, type, name, opts)
49
+ end
50
+ end
51
+ end