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.
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'