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
@@ -0,0 +1,62 @@
|
|
1
|
+
module Bunny
|
2
|
+
module Transport #:nodoc: all
|
3
|
+
class Frame
|
4
|
+
def initialize payload = nil, channel = 0
|
5
|
+
@channel, @payload = channel, payload
|
6
|
+
end
|
7
|
+
attr_accessor :channel, :payload
|
8
|
+
|
9
|
+
def id
|
10
|
+
self.class::ID
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_binary
|
14
|
+
buf = Transport::Buffer.new
|
15
|
+
buf.write :octet, id
|
16
|
+
buf.write :short, channel
|
17
|
+
buf.write :longstr, payload
|
18
|
+
buf.write :octet, Transport::Frame::FOOTER
|
19
|
+
buf.rewind
|
20
|
+
buf
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
to_binary.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def == frame
|
28
|
+
[ :id, :channel, :payload ].inject(true) do |eql, field|
|
29
|
+
eql and __send__(field) == frame.__send__(field)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Method
|
34
|
+
def initialize payload = nil, channel = 0
|
35
|
+
super
|
36
|
+
unless @payload.is_a? Protocol::Class::Method or @payload.nil?
|
37
|
+
@payload = Protocol.parse(@payload)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Header
|
43
|
+
def initialize payload = nil, channel = 0
|
44
|
+
super
|
45
|
+
unless @payload.is_a? Protocol::Header or @payload.nil?
|
46
|
+
@payload = Protocol::Header.new(@payload)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Body; end
|
52
|
+
|
53
|
+
def self.parse buf
|
54
|
+
buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
|
55
|
+
buf.extract do
|
56
|
+
id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
|
57
|
+
Transport::Frame.types[id].new(payload, channel) if footer == Transport::Frame::FOOTER
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/bunny_spec.rb
CHANGED
@@ -11,12 +11,12 @@ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
|
|
11
11
|
describe Bunny do
|
12
12
|
|
13
13
|
before(:each) do
|
14
|
-
@b = Bunny.new
|
14
|
+
@b = Bunny::Client.new
|
15
15
|
@b.start
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should connect to an AMQP server" do
|
19
|
-
@b.status.should ==
|
19
|
+
@b.status.should == :connected
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should be able to create an exchange" do
|
data/spec/exchange_spec.rb
CHANGED
@@ -11,12 +11,12 @@ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
|
|
11
11
|
describe Bunny::Exchange do
|
12
12
|
|
13
13
|
before(:each) do
|
14
|
-
@b = Bunny.new
|
14
|
+
@b = Bunny::Client.new
|
15
15
|
@b.start
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should raise an error if instantiated as non-existent type" do
|
19
|
-
lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(
|
19
|
+
lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(Bunny::ProtocolError)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should allow a default direct exchange to be instantiated by specifying :type" do
|
@@ -105,7 +105,7 @@ describe Bunny::Exchange do
|
|
105
105
|
it "should be able to be deleted" do
|
106
106
|
exch = @b.exchange('direct_exchange')
|
107
107
|
res = exch.delete
|
108
|
-
res.should ==
|
108
|
+
res.should == :delete_ok
|
109
109
|
@b.exchanges.has_key?('direct_exchange').should be false
|
110
110
|
end
|
111
111
|
|
data/spec/queue_spec.rb
CHANGED
@@ -11,7 +11,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
|
|
11
11
|
describe Bunny::Queue do
|
12
12
|
|
13
13
|
before(:each) do
|
14
|
-
@b = Bunny.new
|
14
|
+
@b = Bunny::Client.new
|
15
15
|
@b.start
|
16
16
|
end
|
17
17
|
|
@@ -22,25 +22,25 @@ describe Bunny::Queue do
|
|
22
22
|
it "should ignore the :nowait option when binding to an exchange" do
|
23
23
|
exch = @b.exchange('direct_exch')
|
24
24
|
q = @b.queue('test0')
|
25
|
-
q.bind(exch, :nowait => true).should ==
|
25
|
+
q.bind(exch, :nowait => true).should == :bind_ok
|
26
26
|
end
|
27
27
|
|
28
28
|
it "should be able to bind to an exchange" do
|
29
29
|
exch = @b.exchange('direct_exch')
|
30
30
|
q = @b.queue('test1')
|
31
|
-
q.bind(exch).should ==
|
31
|
+
q.bind(exch).should == :bind_ok
|
32
32
|
end
|
33
33
|
|
34
34
|
it "should ignore the :nowait option when unbinding from an exchange" do
|
35
35
|
exch = @b.exchange('direct_exch')
|
36
36
|
q = @b.queue('test0')
|
37
|
-
q.unbind(exch, :nowait => true).should ==
|
37
|
+
q.unbind(exch, :nowait => true).should == :unbind_ok
|
38
38
|
end
|
39
39
|
|
40
40
|
it "should be able to unbind from an exchange" do
|
41
41
|
exch = @b.exchange('direct_exch')
|
42
42
|
q = @b.queue('test1')
|
43
|
-
q.unbind(exch).should ==
|
43
|
+
q.unbind(exch).should == :unbind_ok
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should be able to publish a message" do
|
@@ -49,12 +49,13 @@ describe Bunny::Queue do
|
|
49
49
|
q.message_count.should == 1
|
50
50
|
end
|
51
51
|
|
52
|
-
it "should be able to pop a message complete with header" do
|
52
|
+
it "should be able to pop a message complete with header and delivery details" do
|
53
53
|
q = @b.queue('test1')
|
54
54
|
msg = q.pop(:header => true)
|
55
55
|
msg.should be_an_instance_of Hash
|
56
|
-
msg[:header].should be_an_instance_of Protocol::Header
|
56
|
+
msg[:header].should be_an_instance_of Bunny::Protocol::Header
|
57
57
|
msg[:payload].should == 'This is a test message'
|
58
|
+
msg[:delivery_details].should be_an_instance_of Hash
|
58
59
|
q.message_count.should == 0
|
59
60
|
end
|
60
61
|
|
@@ -71,13 +72,13 @@ describe Bunny::Queue do
|
|
71
72
|
q.publish('This is another test message')
|
72
73
|
q.pop
|
73
74
|
msg = q.pop
|
74
|
-
msg.should ==
|
75
|
+
msg.should == :queue_empty
|
75
76
|
end
|
76
77
|
|
77
78
|
it "should be able to be deleted" do
|
78
79
|
q = @b.queue('test1')
|
79
80
|
res = q.delete
|
80
|
-
res.should ==
|
81
|
+
res.should == :delete_ok
|
81
82
|
@b.queues.has_key?('test1').should be false
|
82
83
|
end
|
83
84
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bunny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Duncan
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-10 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -19,38 +19,38 @@ executables: []
|
|
19
19
|
|
20
20
|
extensions: []
|
21
21
|
|
22
|
-
extra_rdoc_files:
|
23
|
-
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
24
|
files:
|
25
|
+
- LICENSE
|
26
|
+
- README
|
25
27
|
- Rakefile
|
26
|
-
-
|
27
|
-
- lib/api_messages.rb
|
28
|
-
- lib/bunny.rb
|
29
|
-
- lib/engineroom/buffer.rb
|
30
|
-
- lib/engineroom/frame.rb
|
31
|
-
- lib/engineroom/protocol.rb
|
32
|
-
- lib/engineroom/spec.rb
|
33
|
-
- lib/bunny/exchange.rb
|
34
|
-
- lib/bunny/header.rb
|
35
|
-
- lib/bunny/queue.rb
|
36
|
-
- lib/bunny/client.rb
|
28
|
+
- bunny.gemspec
|
37
29
|
- examples/simple.rb
|
38
|
-
- examples/
|
30
|
+
- examples/simple_ack.rb
|
39
31
|
- examples/simple_consumer.rb
|
32
|
+
- examples/simple_fanout.rb
|
40
33
|
- examples/simple_publisher.rb
|
41
|
-
- examples/simple_ack.rb
|
42
34
|
- examples/simple_topic.rb
|
35
|
+
- ext/amqp-0.8.json
|
36
|
+
- ext/codegen.rb
|
37
|
+
- lib/bunny.rb
|
38
|
+
- lib/bunny/client.rb
|
39
|
+
- lib/bunny/exchange.rb
|
40
|
+
- lib/bunny/protocol/protocol.rb
|
41
|
+
- lib/bunny/protocol/spec.rb
|
42
|
+
- lib/bunny/queue.rb
|
43
|
+
- lib/bunny/transport/buffer.rb
|
44
|
+
- lib/bunny/transport/frame.rb
|
43
45
|
- spec/bunny_spec.rb
|
44
46
|
- spec/exchange_spec.rb
|
45
47
|
- spec/queue_spec.rb
|
46
|
-
- protocol/amqp-0.8.json
|
47
|
-
- protocol/codegen.rb
|
48
48
|
has_rdoc: true
|
49
49
|
homepage: http://github.com/celldee/bunny
|
50
50
|
post_install_message:
|
51
51
|
rdoc_options:
|
52
|
-
- --
|
53
|
-
-
|
52
|
+
- --main
|
53
|
+
- README
|
54
54
|
require_paths:
|
55
55
|
- lib
|
56
56
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -71,6 +71,6 @@ rubyforge_project: bunny-amqp
|
|
71
71
|
rubygems_version: 1.3.1
|
72
72
|
signing_key:
|
73
73
|
specification_version: 2
|
74
|
-
summary:
|
74
|
+
summary: A synchronous Ruby AMQP client that enables interaction with AMQP-compliant brokers/servers.
|
75
75
|
test_files: []
|
76
76
|
|
data/README.markdown
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
# Bunny: A synchronous Ruby AMQP client
|
2
|
-
|
3
|
-
Google Group: [http://groups.google.com/group/bunny-amqp](http://groups.google.com/group/bunny-amqp)
|
4
|
-
|
5
|
-
Mailing List: [http://rubyforge.org/mailman/listinfo/bunny-amqp-devel](http://rubyforge.org/mailman/listinfo/bunny-amqp-devel)
|
6
|
-
|
7
|
-
Rubyforge: [http://rubyforge.org/projects/bunny-amqp](http://rubyforge.org/projects/bunny-amqp)
|
8
|
-
|
9
|
-
Twitter: [http://twitter.com/bunny_amqp](https://twitter.com/bunny_amqp)
|
10
|
-
|
11
|
-
## Announcements
|
12
|
-
|
13
|
-
Bunny v0.2.0 is now available. The highlights are -
|
14
|
-
|
15
|
-
* Code has been re-organised enabling Bunny to play nicely with [amqp](http://github.com/tmm1/amqp) (thanks [Dan](http://github.com/danielsdeleo))
|
16
|
-
* When instantiating a default exchange (one beginning with ‘amq.’) the type will be inferred from the name.
|
17
|
-
* Fixed Queue#subscribe and included Queue#unsubscribe method. See examples/simple_consumer.rb for details
|
18
|
-
|
19
|
-
## About
|
20
|
-
|
21
|
-
*Bunny* is an [AMQP](http://www.amqp.org) (Advanced Message Queuing Protocol) client, written in Ruby, that is intended to allow you to interact with AMQP-compliant message brokers/servers such as [RabbitMQ](http://www.rabbitmq.com) in a synchronous fashion.
|
22
|
-
|
23
|
-
It is based on a great deal of fabulous code from [amqp](http://github.com/tmm1/amqp) by Aman Gupta and [Carrot](http://github.com/famoseagle/carrot) by Amos Elliston.
|
24
|
-
|
25
|
-
You can use *Bunny* to -
|
26
|
-
|
27
|
-
* Create and delete exchanges
|
28
|
-
* Create and delete queues
|
29
|
-
* Publish and consume messages
|
30
|
-
|
31
|
-
*Bunny* is known to work with RabbitMQ version 1.5.4 and version 0-8 of the AMQP specification. If you want to try to use it with other AMQP message brokers/servers please let me know how you get on.
|
32
|
-
|
33
|
-
## Quick Start
|
34
|
-
|
35
|
-
require 'bunny'
|
36
|
-
|
37
|
-
b = Bunny.new(:logging => true)
|
38
|
-
|
39
|
-
# start a communication session with the amqp server
|
40
|
-
b.start
|
41
|
-
|
42
|
-
# declare a queue
|
43
|
-
q = b.queue('test1')
|
44
|
-
|
45
|
-
# publish a message to the queue
|
46
|
-
q.publish('Hello everybody!')
|
47
|
-
|
48
|
-
# get message from the queue
|
49
|
-
msg = q.pop
|
50
|
-
|
51
|
-
puts 'This is the message: ' + msg + "\n\n"
|
52
|
-
|
53
|
-
# close the connection
|
54
|
-
b.close
|
55
|
-
|
56
|
-
## Bunny methods
|
57
|
-
|
58
|
-
These are the Bunny methods that you will probably want to use -
|
59
|
-
|
60
|
-
### Create a Bunny instance
|
61
|
-
Bunny#new({_options_})
|
62
|
-
|
63
|
-
### Start a communication session with the target server
|
64
|
-
Bunny#start
|
65
|
-
|
66
|
-
### Stop a communication session with the target server
|
67
|
-
Bunny#stop
|
68
|
-
|
69
|
-
### Create a Queue
|
70
|
-
Bunny#queue(_**name**_, {_options_})
|
71
|
-
|
72
|
-
### Create an Exchange
|
73
|
-
Bunny#exchange(_**name**_, {_options_})
|
74
|
-
|
75
|
-
### Return connection status ('CONNECTED' or 'NOT CONNECTED')
|
76
|
-
Bunny#status
|
77
|
-
|
78
|
-
### Publish a message to an exchange
|
79
|
-
Exchange#publish(_**data**_, {_options_})
|
80
|
-
|
81
|
-
### Delete an exchange from the target server
|
82
|
-
Exchange#delete({_options_})
|
83
|
-
|
84
|
-
### Bind a queue to an exchange
|
85
|
-
Queue#bind(_**exchange**_, {_options_})
|
86
|
-
|
87
|
-
### Unbind a queue from an exchange
|
88
|
-
Queue#unbind(_**exchange**_, {_options_})
|
89
|
-
|
90
|
-
### Publish a message to a queue
|
91
|
-
Queue#publish(_**data**_, {_options_})
|
92
|
-
|
93
|
-
### Pop a message off of a queue
|
94
|
-
Queue#pop({_options_})
|
95
|
-
|
96
|
-
### Subscribe to a queue
|
97
|
-
Queue#subscribe({_options_}, &blk)
|
98
|
-
|
99
|
-
### Unsubscribe from a queue
|
100
|
-
Queue#unsubscribe({_options_})
|
101
|
-
|
102
|
-
### Return queue message count
|
103
|
-
Queue#message_count
|
104
|
-
|
105
|
-
### Return queue consumer count
|
106
|
-
Queue#consumer_count
|
107
|
-
|
108
|
-
### Return queue status (hash {:message count, :consumer_count})
|
109
|
-
Queue#status
|
110
|
-
|
111
|
-
### Delete a queue from the target server
|
112
|
-
Queue#delete({_options_})
|
113
|
-
|
114
|
-
### Acknowledge receipt of a message
|
115
|
-
Queue#ack
|
116
|
-
|
117
|
-
## Acknowledgements
|
118
|
-
|
119
|
-
This project has borrowed heavily from the following two projects and owes their respective creators and collaborators a whole lot of gratitude:
|
120
|
-
|
121
|
-
1. **amqp** by *tmm1* [http://github.com/tmm1/amqp/tree/master](http://github.com/tmm1/amqp/tree/master)
|
122
|
-
2. **carrot** by *famoseagle* [http://github.com/famoseagle/carrot/tree/master](http://github.com/famoseagle/carrot/tree/master)
|
123
|
-
|
124
|
-
## LICENSE
|
125
|
-
|
126
|
-
Copyright (c) 2009 Chris Duncan; Published under The MIT License, see License
|
data/lib/api_messages.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module API
|
2
|
-
# return messages
|
3
|
-
CONNECTED = 'CONNECTED'
|
4
|
-
NOT_CONNECTED = 'NOT CONNECTED'
|
5
|
-
QUEUE_EMPTY = 'QUEUE EMPTY'
|
6
|
-
QUEUE_DELETED = 'QUEUE DELETED'
|
7
|
-
EXCHANGE_DELETED = 'EXCHANGE DELETED'
|
8
|
-
BIND_SUCCEEDED = 'BIND SUCCEEDED'
|
9
|
-
UNBIND_SUCCEEDED = 'UNBIND SUCCEEDED'
|
10
|
-
|
11
|
-
# specific error definitions
|
12
|
-
class ProtocolError < StandardError; end
|
13
|
-
class ServerDownError < StandardError; end
|
14
|
-
class BufferOverflowError < StandardError; end
|
15
|
-
class InvalidTypeError < StandardError; end
|
16
|
-
class ConnectionError < StandardError; end
|
17
|
-
class MessageError < StandardError; end
|
18
|
-
end
|
data/lib/bunny/header.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
module API
|
2
|
-
class Header
|
3
|
-
|
4
|
-
attr_reader :client
|
5
|
-
|
6
|
-
def initialize(client, header_obj)
|
7
|
-
@client = client
|
8
|
-
@header = header_obj
|
9
|
-
end
|
10
|
-
|
11
|
-
# Acknowledges the receipt of this message with the server.
|
12
|
-
def ack
|
13
|
-
client.send(Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag]))
|
14
|
-
end
|
15
|
-
|
16
|
-
# Reject this message (XXX currently unimplemented in rabbitmq)
|
17
|
-
# * :requeue => true | false (default false)
|
18
|
-
def reject(opts = {})
|
19
|
-
client.send(Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag])))
|
20
|
-
end
|
21
|
-
|
22
|
-
def method_missing(meth, *args, &blk)
|
23
|
-
@header.send(meth, *args, &blk)
|
24
|
-
end
|
25
|
-
|
26
|
-
def inspect
|
27
|
-
@header.inspect
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
data/lib/engineroom/buffer.rb
DELETED
@@ -1,274 +0,0 @@
|
|
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 Transport
|
12
|
-
class Buffer #:nodoc: all
|
13
|
-
|
14
|
-
def initialize data = ''
|
15
|
-
@data = data
|
16
|
-
@pos = 0
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_reader :pos
|
20
|
-
|
21
|
-
def data
|
22
|
-
@data.clone
|
23
|
-
end
|
24
|
-
alias :contents :data
|
25
|
-
alias :to_s :data
|
26
|
-
|
27
|
-
def << data
|
28
|
-
@data << data.to_s
|
29
|
-
self
|
30
|
-
end
|
31
|
-
|
32
|
-
def length
|
33
|
-
@data.length
|
34
|
-
end
|
35
|
-
|
36
|
-
def empty?
|
37
|
-
pos == length
|
38
|
-
end
|
39
|
-
|
40
|
-
def rewind
|
41
|
-
@pos = 0
|
42
|
-
end
|
43
|
-
|
44
|
-
def read_properties *types
|
45
|
-
types.shift if types.first == :properties
|
46
|
-
|
47
|
-
i = 0
|
48
|
-
values = []
|
49
|
-
|
50
|
-
while props = read(:short)
|
51
|
-
(0..14).each do |n|
|
52
|
-
# no more property types
|
53
|
-
break unless types[i]
|
54
|
-
|
55
|
-
# if flag is set
|
56
|
-
if props & (1<<(15-n)) != 0
|
57
|
-
if types[i] == :bit
|
58
|
-
# bit values exist in flags only
|
59
|
-
values << true
|
60
|
-
else
|
61
|
-
# save type name for later reading
|
62
|
-
values << types[i]
|
63
|
-
end
|
64
|
-
else
|
65
|
-
# property not set or is false bit
|
66
|
-
values << (types[i] == :bit ? false : nil)
|
67
|
-
end
|
68
|
-
|
69
|
-
i+=1
|
70
|
-
end
|
71
|
-
|
72
|
-
# bit(0) == 0 means no more property flags
|
73
|
-
break unless props & 1 == 1
|
74
|
-
end
|
75
|
-
|
76
|
-
values.map do |value|
|
77
|
-
value.is_a?(Symbol) ? read(value) : value
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def read *types
|
82
|
-
if types.first == :properties
|
83
|
-
return read_properties(*types)
|
84
|
-
end
|
85
|
-
|
86
|
-
values = types.map do |type|
|
87
|
-
case type
|
88
|
-
when :octet
|
89
|
-
_read(1, 'C')
|
90
|
-
when :short
|
91
|
-
_read(2, 'n')
|
92
|
-
when :long
|
93
|
-
_read(4, 'N')
|
94
|
-
when :longlong
|
95
|
-
upper, lower = _read(8, 'NN')
|
96
|
-
upper << 32 | lower
|
97
|
-
when :shortstr
|
98
|
-
_read read(:octet)
|
99
|
-
when :longstr
|
100
|
-
_read read(:long)
|
101
|
-
when :timestamp
|
102
|
-
Time.at read(:longlong)
|
103
|
-
when :table
|
104
|
-
t = Hash.new
|
105
|
-
|
106
|
-
table = Buffer.new(read(:longstr))
|
107
|
-
until table.empty?
|
108
|
-
key, type = table.read(:shortstr, :octet)
|
109
|
-
key = key.intern
|
110
|
-
t[key] ||= case type
|
111
|
-
when 83 # 'S'
|
112
|
-
table.read(:longstr)
|
113
|
-
when 73 # 'I'
|
114
|
-
table.read(:long)
|
115
|
-
when 68 # 'D'
|
116
|
-
exp = table.read(:octet)
|
117
|
-
num = table.read(:long)
|
118
|
-
num / 10.0**exp
|
119
|
-
when 84 # 'T'
|
120
|
-
table.read(:timestamp)
|
121
|
-
when 70 # 'F'
|
122
|
-
table.read(:table)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
t
|
127
|
-
when :bit
|
128
|
-
if (@bits ||= []).empty?
|
129
|
-
val = read(:octet)
|
130
|
-
@bits = (0..7).map{|i| (val & 1<<i) != 0 }
|
131
|
-
end
|
132
|
-
|
133
|
-
@bits.shift
|
134
|
-
else
|
135
|
-
raise API::InvalidTypeError, "Cannot read data of type #{type}"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
types.size == 1 ? values.first : values
|
140
|
-
end
|
141
|
-
|
142
|
-
def write type, data
|
143
|
-
case type
|
144
|
-
when :octet
|
145
|
-
_write(data, 'C')
|
146
|
-
when :short
|
147
|
-
_write(data, 'n')
|
148
|
-
when :long
|
149
|
-
_write(data, 'N')
|
150
|
-
when :longlong
|
151
|
-
lower = data & 0xffffffff
|
152
|
-
upper = (data & ~0xffffffff) >> 32
|
153
|
-
_write([upper, lower], 'NN')
|
154
|
-
when :shortstr
|
155
|
-
data = (data || '').to_s
|
156
|
-
_write([data.length, data], 'Ca*')
|
157
|
-
when :longstr
|
158
|
-
if data.is_a? Hash
|
159
|
-
write(:table, data)
|
160
|
-
else
|
161
|
-
data = (data || '').to_s
|
162
|
-
_write([data.length, data], 'Na*')
|
163
|
-
end
|
164
|
-
when :timestamp
|
165
|
-
write(:longlong, data.to_i)
|
166
|
-
when :table
|
167
|
-
data ||= {}
|
168
|
-
write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
|
169
|
-
table.write(:shortstr, key.to_s)
|
170
|
-
|
171
|
-
case value
|
172
|
-
when String
|
173
|
-
table.write(:octet, 83) # 'S'
|
174
|
-
table.write(:longstr, value.to_s)
|
175
|
-
when Fixnum
|
176
|
-
table.write(:octet, 73) # 'I'
|
177
|
-
table.write(:long, value)
|
178
|
-
when Float
|
179
|
-
table.write(:octet, 68) # 'D'
|
180
|
-
# XXX there's gotta be a better way to do this..
|
181
|
-
exp = value.to_s.split('.').last.length
|
182
|
-
num = value * 10**exp
|
183
|
-
table.write(:octet, exp)
|
184
|
-
table.write(:long, num)
|
185
|
-
when Time
|
186
|
-
table.write(:octet, 84) # 'T'
|
187
|
-
table.write(:timestamp, value)
|
188
|
-
when Hash
|
189
|
-
table.write(:octet, 70) # 'F'
|
190
|
-
table.write(:table, value)
|
191
|
-
end
|
192
|
-
|
193
|
-
table
|
194
|
-
end)
|
195
|
-
when :bit
|
196
|
-
[*data].to_enum(:each_slice, 8).each{|bits|
|
197
|
-
write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
|
198
|
-
byte |= 1<<i if bit
|
199
|
-
byte
|
200
|
-
})
|
201
|
-
}
|
202
|
-
when :properties
|
203
|
-
values = []
|
204
|
-
data.enum_with_index.inject(0) do |short, ((type, value), i)|
|
205
|
-
n = i % 15
|
206
|
-
last = i+1 == data.size
|
207
|
-
|
208
|
-
if (n == 0 and i != 0) or last
|
209
|
-
if data.size > i+1
|
210
|
-
short |= 1<<0
|
211
|
-
elsif last and value
|
212
|
-
values << [type,value]
|
213
|
-
short |= 1<<(15-n)
|
214
|
-
end
|
215
|
-
|
216
|
-
write(:short, short)
|
217
|
-
short = 0
|
218
|
-
end
|
219
|
-
|
220
|
-
if value and !last
|
221
|
-
values << [type,value]
|
222
|
-
short |= 1<<(15-n)
|
223
|
-
end
|
224
|
-
|
225
|
-
short
|
226
|
-
end
|
227
|
-
|
228
|
-
values.each do |type, value|
|
229
|
-
write(type, value) unless type == :bit
|
230
|
-
end
|
231
|
-
else
|
232
|
-
raise API::InvalidTypeError, "Cannot write data of type #{type}"
|
233
|
-
end
|
234
|
-
|
235
|
-
self
|
236
|
-
end
|
237
|
-
|
238
|
-
def extract
|
239
|
-
begin
|
240
|
-
cur_data, cur_pos = @data.clone, @pos
|
241
|
-
yield self
|
242
|
-
rescue API::BufferOverflowError
|
243
|
-
@data, @pos = cur_data, cur_pos
|
244
|
-
nil
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def _read(size, pack = nil)
|
249
|
-
if @data.is_a?(API::Client)
|
250
|
-
raw = @data.read(size)
|
251
|
-
return raw if raw.nil? or pack.nil?
|
252
|
-
return raw.unpack(pack).first
|
253
|
-
end
|
254
|
-
|
255
|
-
if @pos + size > length
|
256
|
-
raise API::BufferOverflowError
|
257
|
-
else
|
258
|
-
data = @data[@pos,size]
|
259
|
-
@data[@pos,size] = ''
|
260
|
-
if pack
|
261
|
-
data = data.unpack(pack)
|
262
|
-
data = data.pop if data.size == 1
|
263
|
-
end
|
264
|
-
data
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def _write data, pack = nil
|
269
|
-
data = [*data].pack(pack) if pack
|
270
|
-
@data[@pos,0] = data
|
271
|
-
@pos += data.length
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|