amq-client 0.5.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.
Files changed (108) hide show
  1. data/.gitignore +8 -0
  2. data/.gitmodules +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/.yardopts +1 -0
  6. data/CONTRIBUTORS +3 -0
  7. data/Gemfile +27 -0
  8. data/LICENSE +20 -0
  9. data/README.textile +61 -0
  10. data/amq-client.gemspec +34 -0
  11. data/bin/jenkins.sh +23 -0
  12. data/bin/set_test_suite_realms_up.sh +24 -0
  13. data/examples/coolio_adapter/basic_consume.rb +49 -0
  14. data/examples/coolio_adapter/basic_consume_with_acknowledgements.rb +43 -0
  15. data/examples/coolio_adapter/basic_consume_with_rejections.rb +43 -0
  16. data/examples/coolio_adapter/basic_publish.rb +35 -0
  17. data/examples/coolio_adapter/channel_close.rb +24 -0
  18. data/examples/coolio_adapter/example_helper.rb +39 -0
  19. data/examples/coolio_adapter/exchange_declare.rb +28 -0
  20. data/examples/coolio_adapter/kitchen_sink1.rb +48 -0
  21. data/examples/coolio_adapter/queue_bind.rb +32 -0
  22. data/examples/coolio_adapter/queue_purge.rb +32 -0
  23. data/examples/coolio_adapter/queue_unbind.rb +37 -0
  24. data/examples/eventmachine_adapter/authentication/plain_password_with_custom_role_credentials.rb +36 -0
  25. data/examples/eventmachine_adapter/authentication/plain_password_with_default_role_credentials.rb +27 -0
  26. data/examples/eventmachine_adapter/authentication/plain_password_with_incorrect_credentials.rb +18 -0
  27. data/examples/eventmachine_adapter/basic_cancel.rb +49 -0
  28. data/examples/eventmachine_adapter/basic_consume.rb +51 -0
  29. data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +45 -0
  30. data/examples/eventmachine_adapter/basic_consume_with_rejections.rb +45 -0
  31. data/examples/eventmachine_adapter/basic_get.rb +57 -0
  32. data/examples/eventmachine_adapter/basic_get_with_empty_queue.rb +53 -0
  33. data/examples/eventmachine_adapter/basic_publish.rb +38 -0
  34. data/examples/eventmachine_adapter/basic_qos.rb +29 -0
  35. data/examples/eventmachine_adapter/basic_recover.rb +29 -0
  36. data/examples/eventmachine_adapter/basic_return.rb +34 -0
  37. data/examples/eventmachine_adapter/channel_close.rb +24 -0
  38. data/examples/eventmachine_adapter/channel_flow.rb +36 -0
  39. data/examples/eventmachine_adapter/channel_level_exception_handling.rb +44 -0
  40. data/examples/eventmachine_adapter/example_helper.rb +39 -0
  41. data/examples/eventmachine_adapter/exchange_declare.rb +54 -0
  42. data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +31 -0
  43. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +56 -0
  44. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +46 -0
  45. data/examples/eventmachine_adapter/kitchen_sink1.rb +50 -0
  46. data/examples/eventmachine_adapter/queue_bind.rb +32 -0
  47. data/examples/eventmachine_adapter/queue_declare.rb +34 -0
  48. data/examples/eventmachine_adapter/queue_purge.rb +32 -0
  49. data/examples/eventmachine_adapter/queue_unbind.rb +37 -0
  50. data/examples/eventmachine_adapter/tx_commit.rb +29 -0
  51. data/examples/eventmachine_adapter/tx_rollback.rb +29 -0
  52. data/examples/eventmachine_adapter/tx_select.rb +27 -0
  53. data/examples/socket_adapter/basics.rb +19 -0
  54. data/examples/socket_adapter/connection.rb +53 -0
  55. data/examples/socket_adapter/multiple_connections.rb +17 -0
  56. data/irb.rb +66 -0
  57. data/lib/amq/client.rb +15 -0
  58. data/lib/amq/client/adapter.rb +356 -0
  59. data/lib/amq/client/adapters/coolio.rb +221 -0
  60. data/lib/amq/client/adapters/event_machine.rb +228 -0
  61. data/lib/amq/client/adapters/socket.rb +89 -0
  62. data/lib/amq/client/channel.rb +338 -0
  63. data/lib/amq/client/connection.rb +246 -0
  64. data/lib/amq/client/entity.rb +117 -0
  65. data/lib/amq/client/exceptions.rb +86 -0
  66. data/lib/amq/client/exchange.rb +163 -0
  67. data/lib/amq/client/extensions/rabbitmq.rb +5 -0
  68. data/lib/amq/client/extensions/rabbitmq/basic.rb +36 -0
  69. data/lib/amq/client/extensions/rabbitmq/confirm.rb +254 -0
  70. data/lib/amq/client/framing/io/frame.rb +32 -0
  71. data/lib/amq/client/framing/string/frame.rb +62 -0
  72. data/lib/amq/client/logging.rb +56 -0
  73. data/lib/amq/client/mixins/anonymous_entity.rb +21 -0
  74. data/lib/amq/client/mixins/status.rb +62 -0
  75. data/lib/amq/client/protocol/get_response.rb +55 -0
  76. data/lib/amq/client/queue.rb +450 -0
  77. data/lib/amq/client/settings.rb +83 -0
  78. data/lib/amq/client/version.rb +5 -0
  79. data/spec/benchmarks/adapters.rb +77 -0
  80. data/spec/client/framing/io_frame_spec.rb +57 -0
  81. data/spec/client/framing/string_frame_spec.rb +57 -0
  82. data/spec/client/protocol/get_response_spec.rb +79 -0
  83. data/spec/integration/coolio/basic_ack_spec.rb +41 -0
  84. data/spec/integration/coolio/basic_get_spec.rb +73 -0
  85. data/spec/integration/coolio/basic_return_spec.rb +33 -0
  86. data/spec/integration/coolio/channel_close_spec.rb +26 -0
  87. data/spec/integration/coolio/channel_flow_spec.rb +46 -0
  88. data/spec/integration/coolio/spec_helper.rb +31 -0
  89. data/spec/integration/coolio/tx_commit_spec.rb +40 -0
  90. data/spec/integration/coolio/tx_rollback_spec.rb +44 -0
  91. data/spec/integration/eventmachine/basic_ack_spec.rb +40 -0
  92. data/spec/integration/eventmachine/basic_get_spec.rb +73 -0
  93. data/spec/integration/eventmachine/basic_return_spec.rb +35 -0
  94. data/spec/integration/eventmachine/channel_close_spec.rb +26 -0
  95. data/spec/integration/eventmachine/channel_flow_spec.rb +32 -0
  96. data/spec/integration/eventmachine/spec_helper.rb +22 -0
  97. data/spec/integration/eventmachine/tx_commit_spec.rb +47 -0
  98. data/spec/integration/eventmachine/tx_rollback_spec.rb +35 -0
  99. data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +59 -0
  100. data/spec/spec_helper.rb +24 -0
  101. data/spec/unit/client/adapter_spec.rb +49 -0
  102. data/spec/unit/client/entity_spec.rb +49 -0
  103. data/spec/unit/client/logging_spec.rb +60 -0
  104. data/spec/unit/client/mixins/status_spec.rb +72 -0
  105. data/spec/unit/client/settings_spec.rb +27 -0
  106. data/spec/unit/client_spec.rb +11 -0
  107. data/tasks.rb +11 -0
  108. metadata +202 -0
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/exceptions"
4
+
5
+ module AMQ
6
+ module Client
7
+ module Framing
8
+ module IO
9
+
10
+ class Frame < AMQ::Protocol::Frame
11
+ def self.decode(io)
12
+ header = io.read(7)
13
+ type, channel, size = self.decode_header(header)
14
+ data = io.read(size + 1)
15
+ payload, frame_end = data[0..-2], data[-1, 1]
16
+ # TODO: this will hang if the size is bigger than expected or it'll leave there some chars -> make it more error-proof:
17
+ # BTW: socket#eof?
18
+ raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
19
+ self.new(type, payload, channel)
20
+ end # self.from
21
+
22
+ end # Frame
23
+ end # IO
24
+ end # Framing
25
+ end # Client
26
+ end # AMQ
27
+
28
+ class AMQ::Protocol::Frame
29
+ def final?
30
+ true ####### HACK for testing, implement & move to amq-protocol!
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ # This will be probably used by all the async libraries like EventMachine.
4
+ # It expects the whole frame as one string, so if library of your choice
5
+ # gives you input chunk-by-chunk, you'll need to have something like this:
6
+ #
7
+ # class Client
8
+ # include EventMachine::Deferrable
9
+ #
10
+ # def receive_data(chunk)
11
+ # if @payload.nil?
12
+ # self.decode_from_string(chunk[0..6])
13
+ # @payload = ""
14
+ # elsif @payload && chunk[-1] != FINAL_OCTET
15
+ # @payload += chunk
16
+ # @size += chunk.bytesize
17
+ # else
18
+ # check_size(@size, @payload.bytesize)
19
+ # Frame.decode(@payload) # we need the whole payload
20
+ # @size, @payload = nil
21
+ # end
22
+ # end
23
+ #
24
+ # NOTE: the client should also implement waiting for another frames, in case that some header/body frames are expected.
25
+ # end
26
+
27
+ require "amq/client/exceptions"
28
+
29
+ module AMQ
30
+ module Client
31
+ module Framing
32
+ module String
33
+ class Frame < AMQ::Protocol::Frame
34
+ ENCODINGS_SUPPORTED = defined? Encoding
35
+ HEADER_SLICE = (0..6).freeze
36
+ DATA_SLICE = (7..-1).freeze
37
+ PAYLOAD_SLICE = (0..-2).freeze
38
+
39
+ def self.decode(string)
40
+ header = string[HEADER_SLICE]
41
+ type, channel, size = self.decode_header(header)
42
+ data = string[DATA_SLICE]
43
+ payload = data[PAYLOAD_SLICE]
44
+ frame_end = data[-1, 1]
45
+
46
+ frame_end.force_encoding(AMQ::Protocol::Frame::FINAL_OCTET.encoding) if ENCODINGS_SUPPORTED
47
+
48
+ # 1) the size is miscalculated
49
+ if payload.bytesize != size
50
+ raise BadLengthError.new(size, payload.bytesize)
51
+ end
52
+
53
+ # 2) the size is OK, but the string doesn't end with FINAL_OCTET
54
+ raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
55
+
56
+ self.new(type, payload, channel)
57
+ end # self.from
58
+ end # Frame
59
+ end # String
60
+ end # Framing
61
+ end # Client
62
+ end # AMQ
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ # You can use arbitrary logger which responds to #debug, #info, #error and #fatal methods, so for example the logger from standard library will work fine:
4
+ #
5
+ # AMQ::Client.logging = true
6
+ # AMQ::Client.logger = MyLogger.new(STDERR)
7
+ #
8
+ # AMQ::Client.logger defaults to a new instance of Ruby stdlib logger.
9
+ #
10
+ # If you want to be able to log messages just from specified classes or even instances, just make the instance respond to #logging and set it to desired value. So for example <tt>Queue.class_eval { def logging; true; end }</tt> will turn on logging for the whole Queue class whereas <tt>queue = Queue.new; def queue.logging; false; end</tt> will disable logging for given Queue instance.
11
+
12
+ module AMQ
13
+ module Client
14
+ module Logging
15
+ def self.included(klass)
16
+ unless klass.method_defined?(:client)
17
+ raise NotImplementedError.new("Class #{klass} has to provide #client method!")
18
+ end
19
+ end
20
+
21
+ def self.logging
22
+ @logging ||= false
23
+ end
24
+
25
+ def self.logging=(boolean)
26
+ @logging = boolean
27
+ end
28
+
29
+ REQUIRED_METHODS = [:debug, :info, :error, :fatal].freeze
30
+
31
+ def debug(message)
32
+ log(:debug, message)
33
+ end
34
+
35
+ def info(message)
36
+ log(:info, message)
37
+ end
38
+
39
+ def error(message)
40
+ log(:error, message)
41
+ end
42
+
43
+ def fatal(message)
44
+ log(:fatal, message)
45
+ end
46
+
47
+ protected
48
+ def log(method, message)
49
+ if self.respond_to?(:logging) ? self.logging : AMQ::Client::Logging.logging
50
+ self.client.logger.__send__(method, message)
51
+ message
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQ
4
+ module Client
5
+ # Common behavior of AMQ entities that can be either client or server-named, for example, exchanges and queues.
6
+ module AnonymousEntityMixin
7
+
8
+ # @return [Boolean] true if this entity is anonymous (server-named)
9
+ def anonymous?
10
+ @name.nil? or @name.empty?
11
+ end
12
+
13
+ def dup
14
+ if anonymous?
15
+ raise RuntimeError.new("You can't clone anonymous queue until it receives back the name in Queue.Declare-Ok response. Move the code with #dup to the callback for the #declare method.") # TODO: that's not true in all cases, imagine the user didn't call #declare yet.
16
+ end
17
+ super
18
+ end
19
+ end # AnonymousEntityMixin
20
+ end # Client
21
+ end # AMQ
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQ
4
+ module Client
5
+ module StatusMixin
6
+ VALUES = [:opened, :closed, :opening, :closing].freeze
7
+
8
+ class ImproperStatusError < ArgumentError
9
+ def initialize(value)
10
+ super("Value #{value.inspect} isn't permitted. Choose one of: #{AMQ::Client::StatusMixin::VALUES.inspect}")
11
+ end
12
+ end
13
+
14
+ attr_reader :status
15
+ def status=(value)
16
+ if VALUES.include?(value)
17
+ @status = value
18
+ else
19
+ raise ImproperStatusError.new(value)
20
+ end
21
+ end
22
+
23
+ def opened?
24
+ @status == :opened
25
+ end
26
+ alias open? opened?
27
+
28
+ def closed?
29
+ @status == :closed
30
+ end
31
+
32
+
33
+
34
+ def opening?
35
+ @status == :opening
36
+ end
37
+
38
+ def closing?
39
+ @status == :closing
40
+ end
41
+
42
+
43
+ def opened!
44
+ @status = :opened
45
+ end # opened!
46
+
47
+ def closed!
48
+ @status = :closed
49
+ end # closed!
50
+
51
+
52
+
53
+ def opening!
54
+ @status = :opening
55
+ end # opening!
56
+
57
+ def closing!
58
+ @status = :closing
59
+ end # closing!
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ # Purpose of this class is to simplify work with GetOk and GetEmpty.
4
+ module AMQ
5
+ module Protocol
6
+ class GetResponse
7
+ attr_reader :method
8
+ def initialize(method)
9
+ @method = method
10
+ end
11
+
12
+ def empty?
13
+ @method.is_a?(::AMQ::Protocol::Basic::GetEmpty)
14
+ end
15
+
16
+ # GetOk attributes
17
+ def delivery_tag
18
+ if @method.respond_to?(:delivery_tag)
19
+ @method.delivery_tag
20
+ end
21
+ end
22
+
23
+ def redelivered
24
+ if @method.respond_to?(:redelivered)
25
+ @method.redelivered
26
+ end
27
+ end
28
+
29
+ def exchange
30
+ if @method.respond_to?(:exchange)
31
+ @method.exchange
32
+ end
33
+ end
34
+
35
+ def routing_key
36
+ if @method.respond_to?(:routing_key)
37
+ @method.routing_key
38
+ end
39
+ end
40
+
41
+ def message_count
42
+ if @method.respond_to?(:message_count)
43
+ @method.message_count
44
+ end
45
+ end
46
+
47
+ # GetEmpty attributes
48
+ def cluster_id
49
+ if @method.respond_to?(:cluster_id)
50
+ @method.cluster_id
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,450 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/entity"
4
+ require "amq/client/adapter"
5
+ require "amq/client/mixins/anonymous_entity"
6
+ require "amq/client/protocol/get_response"
7
+
8
+ module AMQ
9
+ module Client
10
+ class Queue < Entity
11
+
12
+ #
13
+ # Behaviors
14
+ #
15
+
16
+ include AnonymousEntityMixin
17
+
18
+
19
+ #
20
+ # API
21
+ #
22
+
23
+ # Qeueue name. May be server-generated or assigned directly.
24
+ attr_reader :name
25
+
26
+ # Channel this queue belongs to.
27
+ attr_reader :channel
28
+
29
+ # @param [AMQ::Client::Adapter] AMQ networking adapter to use.
30
+ # @param [AMQ::Client::Channel] AMQ channel this queue object uses.
31
+ # @param [String] Queue name. Please note that AMQP spec does not require brokers to support Unicode for queue names.
32
+ # @api public
33
+ def initialize(client, channel, name = AMQ::Protocol::EMPTY_STRING)
34
+ super(client)
35
+
36
+ @name = name
37
+ @channel = channel
38
+ end
39
+
40
+ def dup
41
+ if @name.empty?
42
+ raise RuntimeError.new("You can't clone anonymous queue until it receives server-generated name. Move the code with #dup to the callback for the #declare method.")
43
+ end
44
+
45
+ o = super
46
+ o.reset_consumer_tag!
47
+ o
48
+ end
49
+
50
+
51
+ # @return [Boolean] true if this queue was declared as durable (will survive broker restart).
52
+ # @api public
53
+ def durable?
54
+ @durable
55
+ end # durable?
56
+
57
+ # @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
58
+ # @api public
59
+ def exclusive?
60
+ @exclusive
61
+ end # exclusive?
62
+
63
+ # @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
64
+ # @api public
65
+ def auto_delete?
66
+ @auto_delete
67
+ end # auto_delete?
68
+
69
+
70
+ # Declares this queue.
71
+ #
72
+ #
73
+ # @return [Queue] self
74
+ #
75
+ # @api public
76
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.1.)
77
+ def declare(passive = false, durable = false, exclusive = false, auto_delete = false, nowait = false, arguments = nil, &block)
78
+ raise ArgumentError, "declaration with nowait does not make sense for server-named queues! Either specify name other than empty string or use #declare without nowait" if nowait && self.anonymous?
79
+
80
+ @durable = durable
81
+ @exclusive = exclusive
82
+ @auto_delete = auto_delete
83
+
84
+ nowait = true if !block && !@name.empty?
85
+ @client.send(Protocol::Queue::Declare.encode(@channel.id, @name, passive, durable, exclusive, auto_delete, nowait, arguments))
86
+
87
+ if !nowait
88
+ self.append_callback(:declare, &block)
89
+ @channel.queues_awaiting_declare_ok.push(self)
90
+ end
91
+
92
+ if @client.sync?
93
+ @client.read_until_receives(Protocol::Queue::DeclareOk) unless nowait
94
+ end
95
+
96
+ self
97
+ end
98
+
99
+ # Deletes this queue.
100
+ #
101
+ # @param [Boolean] if_unused delete only if queue has no consumers (subscribers).
102
+ # @param [Boolean] if_empty delete only if queue has no messages in it.
103
+ # @param [Boolean] nowait Don't wait for reply from broker.
104
+ # @return [Queue] self
105
+ #
106
+ # @api public
107
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.9.)
108
+ def delete(if_unused = false, if_empty = false, nowait = false, &block)
109
+ nowait = true unless block
110
+ @client.send(Protocol::Queue::Delete.encode(@channel.id, @name, if_unused, if_empty, nowait))
111
+
112
+ if !nowait
113
+ self.append_callback(:delete, &block)
114
+
115
+ # TODO: delete itself from queues cache
116
+ @channel.queues_awaiting_delete_ok.push(self)
117
+ end
118
+
119
+ self
120
+ end # delete(channel, queue, if_unused, if_empty, nowait, &block)
121
+
122
+ #
123
+ # @return [Queue] self
124
+ #
125
+ # @api public
126
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.3.)
127
+ def bind(exchange, routing_key = AMQ::Protocol::EMPTY_STRING, nowait = false, arguments = nil, &block)
128
+ nowait = true unless block
129
+ exchange_name = if exchange.respond_to?(:name)
130
+ exchange.name
131
+ else
132
+
133
+ exchange
134
+ end
135
+
136
+ @client.send(Protocol::Queue::Bind.encode(@channel.id, @name, exchange_name, routing_key, nowait, arguments))
137
+
138
+ if !nowait
139
+ self.append_callback(:bind, &block)
140
+
141
+ # TODO: handle channel & connection-level exceptions
142
+ @channel.queues_awaiting_bind_ok.push(self)
143
+ end
144
+
145
+ self
146
+ end
147
+
148
+ #
149
+ # @return [Queue] self
150
+ #
151
+ # @api public
152
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.5.)
153
+ def unbind(exchange, routing_key = AMQ::Protocol::EMPTY_STRING, arguments = nil, &block)
154
+ exchange_name = if exchange.respond_to?(:name)
155
+ exchange.name
156
+ else
157
+
158
+ exchange
159
+ end
160
+
161
+ @client.send(Protocol::Queue::Unbind.encode(@channel.id, @name, exchange_name, routing_key, arguments))
162
+
163
+ self.append_callback(:unbind, &block)
164
+ # TODO: handle channel & connection-level exceptions
165
+ @channel.queues_awaiting_unbind_ok.push(self)
166
+
167
+ self
168
+ end
169
+
170
+
171
+ #
172
+ # @return [Queue] self
173
+ #
174
+ # @api public
175
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.3.)
176
+ def consume(no_ack = false, exclusive = false, nowait = false, no_local = false, arguments = nil, &block)
177
+ raise RuntimeError.new("This instance is already being consumed! Create another one using #dup.") if @consumer_tag
178
+
179
+ nowait = true unless block
180
+ @consumer_tag = generate_consumer_tag(name)
181
+ @client.send(Protocol::Basic::Consume.encode(@channel.id, @name, @consumer_tag, no_local, no_ack, exclusive, nowait, arguments))
182
+
183
+ @channel.consumers[@consumer_tag] = self
184
+
185
+ if !nowait
186
+ # unlike #get, here it is reasonable to expect more than one callback
187
+ # so we use #append_callback
188
+ self.append_callback(:consume, &block)
189
+
190
+ @channel.queues_awaiting_consume_ok.push(self)
191
+ end
192
+
193
+ self
194
+ end
195
+
196
+ # Unique string supposed to be used as a consumer tag.
197
+ #
198
+ # @return [String] Unique string.
199
+ # @api plugin
200
+ def generate_consumer_tag(name)
201
+ "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
202
+ end
203
+
204
+ # Resets consumer tag by setting it to nil.
205
+ # @return [String] Consumer tag this queue previously used.
206
+ #
207
+ # @api plugin
208
+ def reset_consumer_tag!
209
+ ct = @consumer_tag.dup
210
+ @consumer_tag = nil
211
+
212
+ ct
213
+ end
214
+
215
+
216
+ #
217
+ # @return [Queue] self
218
+ #
219
+ # @api public
220
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.10.)
221
+ def get(no_ack = false, &block)
222
+ @client.send(Protocol::Basic::Get.encode(@channel.id, @name, no_ack))
223
+
224
+ # most people only want one callback per #get call. Consider the following example:
225
+ #
226
+ # 100.times { queue.get { ... } }
227
+ #
228
+ # most likely you won't expect 100 callback runs per messages here. MK.
229
+ self.redefine_callback(:get, &block)
230
+ @channel.queues_awaiting_get_response.push(self)
231
+
232
+ self
233
+ end # get(no_ack = false, &block)
234
+
235
+ #
236
+ # @return [Queue] self
237
+ #
238
+ # @api public
239
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.5.)
240
+ def cancel(nowait = false, &block)
241
+ raise "This instance isn't being consumed!" if @consumer_tag.nil?
242
+
243
+ @client.send(Protocol::Basic::Cancel.encode(@channel.id, @consumer_tag, nowait))
244
+ @consumer_tag = nil
245
+ self.clear_callbacks(:delivery)
246
+ self.clear_callbacks(:consume)
247
+
248
+ if !nowait
249
+ self.redefine_callback(:cancel, &block)
250
+ @channel.queues_awaiting_cancel_ok.push(self)
251
+ end
252
+
253
+ self
254
+ end # cancel(&block)
255
+
256
+ #
257
+ # @return [Queue] self
258
+ #
259
+ # @api public
260
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.7.)
261
+ def purge(nowait = false, &block)
262
+ nowait = true unless block
263
+ @client.send(Protocol::Queue::Purge.encode(@channel.id, @name, nowait))
264
+
265
+ if !nowait
266
+ self.redefine_callback(:purge, &block)
267
+ # TODO: handle channel & connection-level exceptions
268
+ @channel.queues_awaiting_purge_ok.push(self)
269
+ end
270
+
271
+ self
272
+ end # purge(nowait = false, &block)
273
+
274
+ #
275
+ # @return [Queue] self
276
+ #
277
+ # @api public
278
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.13.)
279
+ def acknowledge(delivery_tag)
280
+ @channel.acknowledge(delivery_tag)
281
+
282
+ self
283
+ end # acknowledge(delivery_tag)
284
+
285
+ #
286
+ # @return [Queue] self
287
+ #
288
+ # @api public
289
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.14.)
290
+ def reject(delivery_tag, requeue = true)
291
+ @channel.reject(delivery_tag, requeue)
292
+
293
+ self
294
+ end # reject(delivery_tag, requeue = true)
295
+
296
+
297
+
298
+ # @api public
299
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Sections 1.8.3.9)
300
+ def on_delivery(&block)
301
+ self.append_callback(:delivery, &block)
302
+ end # on_delivery(&block)
303
+
304
+
305
+
306
+ #
307
+ # Implementation
308
+ #
309
+
310
+ def handle_declare_ok(method)
311
+ @name = method.queue if self.anonymous?
312
+ @channel.register_queue(self)
313
+
314
+ self.exec_callback_once_yielding_self(:declare, method)
315
+ end
316
+
317
+ def handle_delete_ok(method)
318
+ self.exec_callback(:delete, method)
319
+ end # handle_delete_ok(method)
320
+
321
+ def handle_consume_ok(method)
322
+ self.exec_callback(:consume, method)
323
+ end # handle_consume_ok(method)
324
+
325
+ def handle_purge_ok(method)
326
+ self.exec_callback(:purge, method)
327
+ end # handle_purge_ok(method)
328
+
329
+ def handle_bind_ok(method)
330
+ self.exec_callback(:bind)
331
+ end # handle_bind_ok(method)
332
+
333
+ def handle_unbind_ok(method)
334
+ self.exec_callback(:unbind)
335
+ end # handle_unbind_ok(method)
336
+
337
+ def handle_delivery(method, header, payload)
338
+ self.exec_callback(:delivery, method, header, payload)
339
+ end # handle_delivery
340
+
341
+ def handle_cancel_ok(method)
342
+ @consumer_tag = nil
343
+ self.exec_callback(:cancel, method)
344
+ end # handle_cancel_ok(method)
345
+
346
+ def handle_get_ok(method, header, payload)
347
+ method = Protocol::GetResponse.new(method)
348
+ self.exec_callback(:get, method, header, payload)
349
+ end # handle_get_ok(method, header, payload)
350
+
351
+ def handle_get_empty(method)
352
+ method = Protocol::GetResponse.new(method)
353
+ self.exec_callback(:get, method)
354
+ end # handle_get_empty(method)
355
+
356
+
357
+
358
+ # Get the first queue which didn't receive Queue.Declare-Ok yet and run its declare callback.
359
+ # The cache includes only queues with {nowait: false}.
360
+ self.handle(Protocol::Queue::DeclareOk) do |client, frame|
361
+ method = frame.decode_payload
362
+
363
+ channel = client.connection.channels[frame.channel]
364
+ queue = channel.queues_awaiting_declare_ok.shift
365
+
366
+ queue.handle_declare_ok(method)
367
+ end
368
+
369
+
370
+ self.handle(Protocol::Queue::DeleteOk) do |client, frame|
371
+ channel = client.connection.channels[frame.channel]
372
+ queue = channel.queues_awaiting_delete_ok.shift
373
+ queue.handle_delete_ok(frame.decode_payload)
374
+ end
375
+
376
+
377
+ self.handle(Protocol::Queue::BindOk) do |client, frame|
378
+ channel = client.connection.channels[frame.channel]
379
+ queue = channel.queues_awaiting_bind_ok.shift
380
+
381
+ queue.handle_bind_ok(frame.decode_payload)
382
+ end
383
+
384
+
385
+ self.handle(Protocol::Queue::UnbindOk) do |client, frame|
386
+ channel = client.connection.channels[frame.channel]
387
+ queue = channel.queues_awaiting_unbind_ok.shift
388
+
389
+ queue.handle_unbind_ok(frame.decode_payload)
390
+ end
391
+
392
+
393
+ self.handle(Protocol::Basic::ConsumeOk) do |client, frame|
394
+ channel = client.connection.channels[frame.channel]
395
+ queue = channel.queues_awaiting_consume_ok.shift
396
+
397
+ queue.handle_consume_ok(frame.decode_payload)
398
+ end
399
+
400
+
401
+ self.handle(Protocol::Basic::CancelOk) do |client, frame|
402
+ channel = client.connection.channels[frame.channel]
403
+ queue = channel.queues_awaiting_cancel_ok.shift
404
+
405
+ queue.handle_consume_ok(frame.decode_payload)
406
+ end
407
+
408
+
409
+ # Basic.Deliver
410
+ self.handle(Protocol::Basic::Deliver) do |client, method_frame, content_frames|
411
+ channel = client.connection.channels[method_frame.channel]
412
+ method = method_frame.decode_payload
413
+ queue = channel.consumers[method.consumer_tag]
414
+
415
+ header = content_frames.shift
416
+ body = content_frames.map { |frame| frame.payload }.join
417
+ queue.handle_delivery(method, header, body)
418
+ # TODO: ack if necessary
419
+ end
420
+
421
+
422
+ self.handle(Protocol::Queue::PurgeOk) do |client, frame|
423
+ channel = client.connection.channels[frame.channel]
424
+ queue = channel.queues_awaiting_purge_ok.shift
425
+
426
+ queue.handle_purge_ok(frame.decode_payload)
427
+ end
428
+
429
+
430
+ self.handle(Protocol::Basic::GetOk) do |client, frame, content_frames|
431
+ channel = client.connection.channels[frame.channel]
432
+ queue = channel.queues_awaiting_get_response.shift
433
+ method = frame.decode_payload
434
+
435
+ header = content_frames.shift
436
+ body = content_frames.map {|frame| frame.payload }.join
437
+
438
+ queue.handle_get_ok(method, header, body) if queue
439
+ end
440
+
441
+
442
+ self.handle(Protocol::Basic::GetEmpty) do |client, frame|
443
+ channel = client.connection.channels[frame.channel]
444
+ queue = channel.queues_awaiting_get_response.shift
445
+
446
+ queue.handle_get_empty(frame.decode_payload)
447
+ end
448
+ end # Queue
449
+ end # Client
450
+ end # AMQ