qup 1.1.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/.autotest +41 -0
- data/.gemtest +1 -0
- data/ADAPTER_API.rdoc +97 -0
- data/HISTORY.rdoc +9 -0
- data/Manifest.txt +52 -0
- data/README.rdoc +156 -0
- data/Rakefile +246 -0
- data/lib/qup.rb +48 -0
- data/lib/qup/adapter.rb +57 -0
- data/lib/qup/adapter/kestrel.rb +56 -0
- data/lib/qup/adapter/kestrel/destination.rb +54 -0
- data/lib/qup/adapter/kestrel/queue.rb +101 -0
- data/lib/qup/adapter/kestrel/topic.rb +68 -0
- data/lib/qup/adapter/maildir.rb +57 -0
- data/lib/qup/adapter/maildir/queue.rb +123 -0
- data/lib/qup/adapter/maildir/topic.rb +85 -0
- data/lib/qup/adapter/redis.rb +55 -0
- data/lib/qup/adapter/redis/connection.rb +32 -0
- data/lib/qup/adapter/redis/queue.rb +97 -0
- data/lib/qup/adapter/redis/topic.rb +76 -0
- data/lib/qup/consumer.rb +42 -0
- data/lib/qup/message.rb +18 -0
- data/lib/qup/producer.rb +28 -0
- data/lib/qup/publisher.rb +30 -0
- data/lib/qup/queue_api.rb +124 -0
- data/lib/qup/session.rb +111 -0
- data/lib/qup/subscriber.rb +28 -0
- data/lib/qup/topic_api.rb +92 -0
- data/spec/qup/adapter/kestrel/queue_spec.rb +9 -0
- data/spec/qup/adapter/kestrel/topic_spec.rb +9 -0
- data/spec/qup/adapter/kestrel_context.rb +8 -0
- data/spec/qup/adapter/kestrel_spec.rb +8 -0
- data/spec/qup/adapter/maildir/queue_spec.rb +9 -0
- data/spec/qup/adapter/maildir/topic_spec.rb +9 -0
- data/spec/qup/adapter/maildir_context.rb +10 -0
- data/spec/qup/adapter/maildir_spec.rb +8 -0
- data/spec/qup/adapter/redis/queue_spec.rb +9 -0
- data/spec/qup/adapter/redis/topic_spec.rb +9 -0
- data/spec/qup/adapter/redis_context.rb +6 -0
- data/spec/qup/adapter/redis_spec.rb +8 -0
- data/spec/qup/adapter_spec.rb +28 -0
- data/spec/qup/consumer_spec.rb +40 -0
- data/spec/qup/message_spec.rb +13 -0
- data/spec/qup/producer_spec.rb +18 -0
- data/spec/qup/queue_api_spec.rb +21 -0
- data/spec/qup/session_spec.rb +81 -0
- data/spec/qup/shared_adapter_examples.rb +29 -0
- data/spec/qup/shared_queue_examples.rb +71 -0
- data/spec/qup/shared_topic_examples.rb +57 -0
- data/spec/qup/topic_api_spec.rb +21 -0
- data/spec/qup_spec.rb +37 -0
- data/spec/spec_helper.rb +26 -0
- metadata +281 -0
data/lib/qup.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Qup
|
2
|
+
# The Current Version of the library
|
3
|
+
VERSION = '1.1.0'
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Public: Create a new Session using the given provider URI
|
8
|
+
#
|
9
|
+
# uri - the String representing the provider to talk to
|
10
|
+
#
|
11
|
+
# Yields the created Session. When the block returns, the session is closed
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# session = Qup.open( 'kestrel://localhost:22133' )
|
16
|
+
# session = Qup.open( 'maildir:///tmp/qup' )
|
17
|
+
#
|
18
|
+
# Returns a Session.
|
19
|
+
def self.open( uri, &block )
|
20
|
+
Qup::Session.open( uri, &block )
|
21
|
+
end
|
22
|
+
|
23
|
+
KNOWN_ADAPTERS = {
|
24
|
+
# require => gem
|
25
|
+
'maildir' => 'maildir',
|
26
|
+
'kestrel' => 'kestrel-client',
|
27
|
+
'redis' => 'redis'
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'qup/adapter'
|
32
|
+
require 'qup/consumer'
|
33
|
+
require 'qup/message'
|
34
|
+
require 'qup/producer'
|
35
|
+
require 'qup/publisher'
|
36
|
+
require 'qup/queue_api'
|
37
|
+
require 'qup/session'
|
38
|
+
require 'qup/subscriber'
|
39
|
+
require 'qup/topic_api'
|
40
|
+
|
41
|
+
# Load the known adapters, print a warning if $VERBOSE is set
|
42
|
+
Qup::KNOWN_ADAPTERS.each do |adapter, gemname|
|
43
|
+
begin
|
44
|
+
require "qup/adapter/#{adapter}"
|
45
|
+
rescue LoadError
|
46
|
+
warn "Install the '#{gemname}' gem of you want to use the #{adapter} adapter" if $VERBOSE
|
47
|
+
end
|
48
|
+
end
|
data/lib/qup/adapter.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Qup
|
2
|
+
# The list of registered Adapters
|
3
|
+
Adapters = Hash.new
|
4
|
+
|
5
|
+
#
|
6
|
+
# Public: The module that Qup Adapters must extend
|
7
|
+
#
|
8
|
+
# Any backing system that implements the Qup API must have an entry point that
|
9
|
+
# inherits from Adapter.
|
10
|
+
class Adapter
|
11
|
+
|
12
|
+
# Public: Register the child as an Adapter
|
13
|
+
#
|
14
|
+
# name - the name of the adapter. This will be the URI scheme value
|
15
|
+
#
|
16
|
+
# Return nothing
|
17
|
+
def self.register( name )
|
18
|
+
Adapters[name.to_s] ||= self
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Public: Create a Queue with the given name
|
23
|
+
#
|
24
|
+
# name - then name of the Queue to create
|
25
|
+
#
|
26
|
+
# Returns a Qup::QueueAPI compatible object
|
27
|
+
def queue( name )
|
28
|
+
raise NotImplementedError, "please implement 'queue'"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Public: Create a Topic with the given name
|
33
|
+
#
|
34
|
+
# name - then name of the Topic to create
|
35
|
+
#
|
36
|
+
# Returns a Qup::TopicAPI compatible object
|
37
|
+
def topic( name )
|
38
|
+
raise NotImplementedError, "please implement 'topic'"
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Public: close the Adapter for further use
|
43
|
+
#
|
44
|
+
# Returns nothing
|
45
|
+
def close
|
46
|
+
raise NotImplementedError, "please implement 'close'"
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# Public: is the Adapter closed
|
51
|
+
#
|
52
|
+
# Returns true or false
|
53
|
+
def closed?
|
54
|
+
raise NotImplementedError, "please implement 'closed?'"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'qup/adapter'
|
2
|
+
require 'kestrel-client'
|
3
|
+
|
4
|
+
class Qup::Adapter
|
5
|
+
# Internal: The backing adapter for Qup that uses Kestrel as the messaging
|
6
|
+
# infrastructure
|
7
|
+
class Kestrel < ::Qup::Adapter
|
8
|
+
|
9
|
+
# Register this adapter as :kestrel
|
10
|
+
register :kestrel
|
11
|
+
|
12
|
+
# Internal: Create a new Kestrel Adapter
|
13
|
+
#
|
14
|
+
# uri - the URI instance for this adapter to use
|
15
|
+
def initialize( uri, options = {} )
|
16
|
+
@uri = uri
|
17
|
+
@addr = "#{@uri.host}:#{@uri.port}"
|
18
|
+
@options = options
|
19
|
+
@closed = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Internal: Create a new Queue from this Adapter
|
23
|
+
#
|
24
|
+
# name - the String name of the Queue
|
25
|
+
#
|
26
|
+
# Returns a Qup::Queue
|
27
|
+
def queue( name )
|
28
|
+
Qup::Adapter::Kestrel::Queue.new( @addr, name )
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Create a new Topic from this Adapter
|
32
|
+
#
|
33
|
+
# name - the name of this Topic
|
34
|
+
#
|
35
|
+
# Returns a Qup::Topic
|
36
|
+
def topic( name )
|
37
|
+
Qup::Adapter::Kestrel::Topic.new( @addr, name )
|
38
|
+
end
|
39
|
+
|
40
|
+
# Internal: Close the Kestrel adapter
|
41
|
+
#
|
42
|
+
# Return nothing
|
43
|
+
def close
|
44
|
+
@closed = true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Internal: Is the Kestrel Adapter closed
|
48
|
+
#
|
49
|
+
# Returns true or false
|
50
|
+
def closed?
|
51
|
+
@closed
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
require 'qup/adapter/kestrel/queue'
|
56
|
+
require 'qup/adapter/kestrel/topic'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Qup::Adapter::Kestrel
|
2
|
+
#
|
3
|
+
# Internal: The Common base class for Kestrel Topic and Queue
|
4
|
+
#
|
5
|
+
class Destination
|
6
|
+
|
7
|
+
# Internal: the name of the Queue or Topic
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# Internal: Create a new Topic or Queue
|
11
|
+
#
|
12
|
+
# address - the Connection Address string for the Kestrel Client
|
13
|
+
# name - the String name of the Topic
|
14
|
+
#
|
15
|
+
# Returns a new Topic.
|
16
|
+
def initialize( address, name )
|
17
|
+
@address = address
|
18
|
+
@client = blocking_transactional_client( @address )
|
19
|
+
@admin_client = regular_client( @address )
|
20
|
+
@name = name
|
21
|
+
ping
|
22
|
+
end
|
23
|
+
|
24
|
+
# Internal: Destroy the Topic or Queue
|
25
|
+
#
|
26
|
+
# If possible remove the existence of the Topic from the System
|
27
|
+
#
|
28
|
+
# Returns nothing.
|
29
|
+
def destroy
|
30
|
+
@admin_client.delete( name )
|
31
|
+
@admin_client.delete( name+"_errors" )
|
32
|
+
end
|
33
|
+
|
34
|
+
# Internal: Make sure the Topic or Queue exists
|
35
|
+
#
|
36
|
+
# Returns nothing
|
37
|
+
def ping
|
38
|
+
@admin_client.peek( name )
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
#######
|
43
|
+
private
|
44
|
+
#######
|
45
|
+
|
46
|
+
def regular_client( addr )
|
47
|
+
Kestrel::Client.new( addr )
|
48
|
+
end
|
49
|
+
|
50
|
+
def blocking_transactional_client( addr )
|
51
|
+
Kestrel::Client::Blocking.new( Kestrel::Client::Transactional.new( regular_client(addr) ) )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'qup/adapter/kestrel/destination'
|
2
|
+
|
3
|
+
class Qup::Adapter::Kestrel
|
4
|
+
#
|
5
|
+
# Internal: The Implementation of Queue in the Kestrel Adapter
|
6
|
+
#
|
7
|
+
class Queue < Destination
|
8
|
+
include Qup::QueueAPI
|
9
|
+
|
10
|
+
# Internal: Create a new Queue
|
11
|
+
#
|
12
|
+
# address - the Connection Address string for the Kestrel Client
|
13
|
+
# name - the String name of the Topic
|
14
|
+
#
|
15
|
+
# Returns a new Queue
|
16
|
+
def initialize( address, name )
|
17
|
+
super(address, name)
|
18
|
+
@open_messages = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: The name of the Queue
|
22
|
+
attr_reader :name
|
23
|
+
|
24
|
+
# Internal: Remove all messages from the Queue
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
def flush
|
28
|
+
@admin_client.flush(@name)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Internal: return the number of Messages on the Queue
|
33
|
+
#
|
34
|
+
# Returns an integer of the Queue depth
|
35
|
+
def depth
|
36
|
+
stats = @admin_client.stat( @name )
|
37
|
+
return stats['items']
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Internal: Put an item onto the Queue
|
42
|
+
#
|
43
|
+
# message - the data to put onto the queue.
|
44
|
+
#
|
45
|
+
# The 'message' that is passed in is wrapped in a Qup::Message before being
|
46
|
+
# stored.
|
47
|
+
#
|
48
|
+
# A user of the Qup API should use a Producer instance to put items onto the
|
49
|
+
# queue.
|
50
|
+
#
|
51
|
+
# Returns the Message that was put onto the Queue
|
52
|
+
def produce( message )
|
53
|
+
@client.set( @name, message )
|
54
|
+
return ::Qup::Message.new( message.object_id, message )
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Internal: Retrieve a Message from the Queue
|
59
|
+
#
|
60
|
+
# Yields a Message
|
61
|
+
#
|
62
|
+
# A user of the Qup API should use a Consumer instance to retrieve items
|
63
|
+
# from the Queue.
|
64
|
+
#
|
65
|
+
# Returns a Message
|
66
|
+
def consume(&block)
|
67
|
+
data = @client.get( @name )
|
68
|
+
q_message = ::Qup::Message.new( data.object_id, data )
|
69
|
+
@open_messages[q_message.key] = q_message
|
70
|
+
if block_given? then
|
71
|
+
yield_message( q_message, &block )
|
72
|
+
else
|
73
|
+
return q_message
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Internal: Acknowledge that message is completed and remove it from the
|
79
|
+
# Queue.
|
80
|
+
#
|
81
|
+
# For Kestrel, this really just closes the last message, the message that is
|
82
|
+
# sent in does not matter.
|
83
|
+
#
|
84
|
+
# Returns nothing
|
85
|
+
def acknowledge( message )
|
86
|
+
open_msg = @open_messages.delete( message.key )
|
87
|
+
raise Qup::Error, "Message #{message.key} is not currently being consumed" unless open_msg
|
88
|
+
@client.close_last_transaction
|
89
|
+
end
|
90
|
+
|
91
|
+
#######
|
92
|
+
private
|
93
|
+
#######
|
94
|
+
|
95
|
+
def yield_message( message, &block )
|
96
|
+
yield message
|
97
|
+
ensure
|
98
|
+
acknowledge( message )
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'qup/adapter/kestrel/destination'
|
2
|
+
class Qup::Adapter::Kestrel
|
3
|
+
#
|
4
|
+
# Internal: The Topic implementation for the Kestrel Adapter
|
5
|
+
#
|
6
|
+
# The topic delivers each Message that it is give to each and every Subscriber
|
7
|
+
#
|
8
|
+
class Topic < Destination
|
9
|
+
include Qup::TopicAPI
|
10
|
+
|
11
|
+
# Internal : Creates a Publisher for the Topic
|
12
|
+
#
|
13
|
+
# Returns a new Publisher
|
14
|
+
def publisher
|
15
|
+
::Qup::Publisher.new( self )
|
16
|
+
end
|
17
|
+
|
18
|
+
# Internal: Create a subscriber for the Topic
|
19
|
+
#
|
20
|
+
# name - the String name of the subscriber
|
21
|
+
#
|
22
|
+
# Creating a subscriber creates a new Subscriber that will receive a copy of
|
23
|
+
# every message that is published to the Topic.
|
24
|
+
#
|
25
|
+
# Subscribers are unique by name, two subscribers with the same name will
|
26
|
+
# act as individual Consumers on a queue of their name.
|
27
|
+
#
|
28
|
+
# Returns a Subscriber
|
29
|
+
def subscriber( name )
|
30
|
+
::Qup::Subscriber.new( self, subscriber_queue( name ) )
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Internal: Return the number of Subscribers to this Topic
|
35
|
+
#
|
36
|
+
# Returns integer
|
37
|
+
def subscriber_count
|
38
|
+
c = 0
|
39
|
+
@client.stats['queues'].keys.each do |k|
|
40
|
+
next if k =~ /errors$/
|
41
|
+
c += 1 if k =~ /^#{@name}\+/
|
42
|
+
end
|
43
|
+
return c
|
44
|
+
end
|
45
|
+
|
46
|
+
# Internal: Publish a Message to all the Subscribers
|
47
|
+
#
|
48
|
+
# message - the Object to send to all subscribers
|
49
|
+
#
|
50
|
+
# Returns nothing
|
51
|
+
def publish( message )
|
52
|
+
@client.set( @name, message )
|
53
|
+
end
|
54
|
+
|
55
|
+
#######
|
56
|
+
private
|
57
|
+
#######
|
58
|
+
|
59
|
+
def subscriber_queue( sub_name )
|
60
|
+
sname = subscriber_queue_name( sub_name )
|
61
|
+
::Qup::Adapter::Kestrel::Queue.new( @address, sname )
|
62
|
+
end
|
63
|
+
|
64
|
+
def subscriber_queue_name( sub_name )
|
65
|
+
"#{@name}+#{sub_name}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'qup/adapter'
|
2
|
+
require 'maildir'
|
3
|
+
|
4
|
+
class Qup::Adapter
|
5
|
+
# Internal: The backing adapter for Qup that uses Maildir as the messaging
|
6
|
+
# infrastructure
|
7
|
+
class Maildir < ::Qup::Adapter
|
8
|
+
|
9
|
+
# Register this adapter as :maildir
|
10
|
+
register :maildir
|
11
|
+
|
12
|
+
# Internal: Create a new Maildir Adapter
|
13
|
+
#
|
14
|
+
# uri - the URI instance for this adapter to use
|
15
|
+
def initialize( uri, options = {} )
|
16
|
+
@uri = uri
|
17
|
+
@options = options
|
18
|
+
@root_path = uri.path
|
19
|
+
@closed = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Internal: Create a new Queue from this Adapter
|
23
|
+
#
|
24
|
+
# name - the String name of the Queue
|
25
|
+
#
|
26
|
+
# Returns a Qup::Queue
|
27
|
+
def queue( name )
|
28
|
+
Qup::Adapter::Maildir::Queue.new( @root_path, name )
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Create a new Topic from this Adapter
|
32
|
+
#
|
33
|
+
# name - the name of this Topic
|
34
|
+
#
|
35
|
+
# Returns a Qup::Topic
|
36
|
+
def topic( name )
|
37
|
+
Qup::Adapter::Maildir::Topic.new( @root_path, name )
|
38
|
+
end
|
39
|
+
|
40
|
+
# Internal: Close the Maildir adapter
|
41
|
+
#
|
42
|
+
# Return nothing
|
43
|
+
def close
|
44
|
+
@closed = true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Internal: Is the Maildir Adapter closed
|
48
|
+
#
|
49
|
+
# Returns true or false
|
50
|
+
def closed?
|
51
|
+
@closed
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
require 'qup/adapter/maildir/queue'
|
57
|
+
require 'qup/adapter/maildir/topic'
|