qup 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|