qup 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.autotest +41 -0
  2. data/.gemtest +1 -0
  3. data/ADAPTER_API.rdoc +97 -0
  4. data/HISTORY.rdoc +9 -0
  5. data/Manifest.txt +52 -0
  6. data/README.rdoc +156 -0
  7. data/Rakefile +246 -0
  8. data/lib/qup.rb +48 -0
  9. data/lib/qup/adapter.rb +57 -0
  10. data/lib/qup/adapter/kestrel.rb +56 -0
  11. data/lib/qup/adapter/kestrel/destination.rb +54 -0
  12. data/lib/qup/adapter/kestrel/queue.rb +101 -0
  13. data/lib/qup/adapter/kestrel/topic.rb +68 -0
  14. data/lib/qup/adapter/maildir.rb +57 -0
  15. data/lib/qup/adapter/maildir/queue.rb +123 -0
  16. data/lib/qup/adapter/maildir/topic.rb +85 -0
  17. data/lib/qup/adapter/redis.rb +55 -0
  18. data/lib/qup/adapter/redis/connection.rb +32 -0
  19. data/lib/qup/adapter/redis/queue.rb +97 -0
  20. data/lib/qup/adapter/redis/topic.rb +76 -0
  21. data/lib/qup/consumer.rb +42 -0
  22. data/lib/qup/message.rb +18 -0
  23. data/lib/qup/producer.rb +28 -0
  24. data/lib/qup/publisher.rb +30 -0
  25. data/lib/qup/queue_api.rb +124 -0
  26. data/lib/qup/session.rb +111 -0
  27. data/lib/qup/subscriber.rb +28 -0
  28. data/lib/qup/topic_api.rb +92 -0
  29. data/spec/qup/adapter/kestrel/queue_spec.rb +9 -0
  30. data/spec/qup/adapter/kestrel/topic_spec.rb +9 -0
  31. data/spec/qup/adapter/kestrel_context.rb +8 -0
  32. data/spec/qup/adapter/kestrel_spec.rb +8 -0
  33. data/spec/qup/adapter/maildir/queue_spec.rb +9 -0
  34. data/spec/qup/adapter/maildir/topic_spec.rb +9 -0
  35. data/spec/qup/adapter/maildir_context.rb +10 -0
  36. data/spec/qup/adapter/maildir_spec.rb +8 -0
  37. data/spec/qup/adapter/redis/queue_spec.rb +9 -0
  38. data/spec/qup/adapter/redis/topic_spec.rb +9 -0
  39. data/spec/qup/adapter/redis_context.rb +6 -0
  40. data/spec/qup/adapter/redis_spec.rb +8 -0
  41. data/spec/qup/adapter_spec.rb +28 -0
  42. data/spec/qup/consumer_spec.rb +40 -0
  43. data/spec/qup/message_spec.rb +13 -0
  44. data/spec/qup/producer_spec.rb +18 -0
  45. data/spec/qup/queue_api_spec.rb +21 -0
  46. data/spec/qup/session_spec.rb +81 -0
  47. data/spec/qup/shared_adapter_examples.rb +29 -0
  48. data/spec/qup/shared_queue_examples.rb +71 -0
  49. data/spec/qup/shared_topic_examples.rb +57 -0
  50. data/spec/qup/topic_api_spec.rb +21 -0
  51. data/spec/qup_spec.rb +37 -0
  52. data/spec/spec_helper.rb +26 -0
  53. metadata +281 -0
@@ -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
@@ -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'