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,42 @@
1
+ module Qup
2
+ # Public: Consumes items for a queue
3
+ #
4
+ # A Consumer is created from a Queue and consumes messages from the queue.
5
+ #
6
+ #
7
+ class Consumer
8
+ # Public: Create a new Consumer
9
+ #
10
+ # queue - the Queue this producer is for
11
+ #
12
+ # Returns a new Consumer
13
+ def initialize( queue )
14
+ @queue = queue
15
+ end
16
+
17
+ # Public: Consume a message for the queue
18
+ #
19
+ # Yields the message
20
+ #
21
+ # Most of the time you will want to call this message with a block as the
22
+ # Message will be auto-acknowledged. If you do not consume messages with a
23
+ # block, then you are required to acknowledge the messages on your own.
24
+ #
25
+ # Returns a Message
26
+ def consume(&block)
27
+ @queue.consume(&block)
28
+ end
29
+
30
+ # Public: Acknowledge a consumed message
31
+ #
32
+ # message - The message you are acknowledging
33
+ #
34
+ # A consumed message must be acknowledge so the back end system can
35
+ # be assured that the message has been a fully processed.
36
+ #
37
+ # Returns nothing.
38
+ def acknowledge( message )
39
+ @queue.acknowledge( message )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module Qup
2
+ # Public: A Message is an item that may be put on and taken off the queue.
3
+ #
4
+ class Message
5
+
6
+ # Public: The unique identifier of this message
7
+ attr_reader :key
8
+
9
+ # Public: The data in this Message
10
+ attr_reader :data
11
+
12
+ # Public: Create a Message from the given data
13
+ def initialize( key, data )
14
+ @key = key
15
+ @data = data
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Qup
2
+ # Public: Produces items for a queue
3
+ #
4
+ # Examples:
5
+ #
6
+ # producer = queue.producer
7
+ # producer.produce( my_message )
8
+ #
9
+ class Producer
10
+ # Public: Create a new Producer
11
+ #
12
+ # queue - the Queue this producer is for
13
+ #
14
+ # Returns a new Producer
15
+ def initialize( queue )
16
+ @queue = queue
17
+ end
18
+
19
+ # Public: Produce a message for the queue
20
+ #
21
+ # message - the Object to put onto the queue
22
+ #
23
+ # Returns nothing.
24
+ def produce( message )
25
+ @queue.produce( message )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Qup
2
+ # Public: A Publisher produces messages on a Topic
3
+ #
4
+ # Example:
5
+ #
6
+ # pub = topic.publisher
7
+ # pub.publish( my_message )
8
+ #
9
+ class Publisher
10
+
11
+ # Public: The Topic this Publisher publishes to.
12
+ attr_reader :topic
13
+
14
+ # Public: Create a new Publisher for a Topic
15
+ #
16
+ # Returns a Publisher
17
+ def initialize( topic )
18
+ @topic = topic
19
+ end
20
+
21
+ # Public: Publish a Message to all the Subscribers
22
+ #
23
+ # message - the Object to send to all Subscribers.
24
+ #
25
+ # Returns nothing
26
+ def publish( message )
27
+ @topic.publish( message )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,124 @@
1
+ require 'qup/producer'
2
+ require 'qup/consumer'
3
+ module Qup
4
+ #
5
+ # Public: A QueueAPI for use in a point-to-point messaging pattern.
6
+ #
7
+ # The Queue guarantees that each Message that is on the Queue is delivered and
8
+ # acknowledged only once.
9
+ #
10
+ # A very common pattern for Queue usage is a worker pattern where you have a
11
+ # Producer putting job Messages on the Queue and a collection of Consumers
12
+ # which work on those job Messages.
13
+ #
14
+ # This is the API that MUST be implemented in the adapter.
15
+ #
16
+ # Example:
17
+ #
18
+ # session = Session.new( uri )
19
+ # queue = session.queue( 'my_queue' )
20
+ #
21
+ # queue.producer # => Producer
22
+ # queue.consumer # => Consumer
23
+ # queue.depth # => 4
24
+ #
25
+ module QueueAPI
26
+
27
+ # Public: create a Producer for this Queue
28
+ #
29
+ # Returns a new Producer
30
+ def producer
31
+ Producer.new( self )
32
+ end
33
+
34
+ # Public: create a Consumer for this Queue
35
+ #
36
+ # Returns a new Consumer
37
+ def consumer
38
+ Consumer.new( self )
39
+ end
40
+
41
+
42
+ #--------------------------------------------------------------------------
43
+ # The API that Adapters must implement
44
+ #--------------------------------------------------------------------------
45
+
46
+ # Public: the name of the Queue
47
+ #
48
+ # Returns the String name
49
+ def name
50
+ super
51
+ rescue NoMethodError
52
+ raise NotImplementedError, "please implement 'name'"
53
+ end
54
+
55
+
56
+ # Public: return the number of Messages on the Queue
57
+ #
58
+ # Returns an integer of the Queue depth
59
+ def depth
60
+ raise NotImplementedError, "please implement 'depth'"
61
+ end
62
+
63
+
64
+ # Public: empty all the messages from the Queue, this does not consume them,
65
+ # this removes the from the Queue
66
+ #
67
+ # Returns nothing
68
+ def flush
69
+ raise NotImplementedError, "please implement 'flush'"
70
+ end
71
+
72
+
73
+ # Public: destroy the Queue if possible
74
+ #
75
+ # This will clear the Queue and remove it from the system if possible
76
+ #
77
+ # Returns nothing.
78
+ def destroy
79
+ super
80
+ rescue NoMethodError
81
+ raise NotImplementedError, "please implement 'destroy'"
82
+ end
83
+
84
+
85
+ # Internal: Put an item onto the Queue
86
+ #
87
+ # message - the data to put onto the queue
88
+ #
89
+ # A user of the Qup API should use a Producer instance to put items onto the
90
+ # queue.
91
+ #
92
+ # Returns the Message that was put onto the Queue
93
+ def produce( message )
94
+ raise NotImplementedError, "please implement 'produce'"
95
+ end
96
+
97
+
98
+ # Internal: Retrieve an item from the Queue
99
+ #
100
+ # options - a Hash of options determining how long to wait for a Message
101
+ # :block - should we block until a Message is available
102
+ # (default: true )
103
+ # :timeout - how long to wait for a Message, only valid if
104
+ # :block is false
105
+ #
106
+ # A user of the Qup API should use a Consumer instance to retrieve items
107
+ # from the Queue.
108
+ #
109
+ # Returns a Message
110
+ def consume(&block)
111
+ raise NotImplementedError, "please implement 'consume'"
112
+ end
113
+
114
+
115
+ # Internal: Acknowledge that message is completed and remove it from the
116
+ # Queue.
117
+ #
118
+ # Returns nothing
119
+ def acknowledge( message )
120
+ raise NotImplementedError, "please implement 'acknowledge'"
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,111 @@
1
+ require 'uri'
2
+ require 'pathname'
3
+ module Qup
4
+ # Public: Manage communicating with a provider
5
+ #
6
+ # Examples:
7
+ #
8
+ # Session.new( 'maildir:///tmp/qup' )
9
+ # Session.new( 'kestrel://user:pass@host:port/' )
10
+ #
11
+ # session.queue( 'foo' ) # => Queue
12
+ # session.topic( 'bar' ) # => Topic
13
+ #
14
+ # At the moment, a Session is not considered thread safe, so each Thread should
15
+ # create its own Session.
16
+ class Session
17
+ class ClosedError < Qup::Error; end
18
+
19
+ # Public: The URI of this Session
20
+ attr_reader :uri
21
+
22
+ # Public: Create a new Session
23
+ #
24
+ # uri - The connection String used to connect to appropriate provider
25
+ # options - The Hash of options that are passed to the underlying Adapter
26
+ #
27
+ # Yields the created Session
28
+ #
29
+ # If a session is yielded, it is closed at the end of the block
30
+ #
31
+ # Returns a new Session
32
+ def self.open( uri, options = {}, &block )
33
+ session = ::Qup::Session.new( uri, options )
34
+ return session unless block_given?
35
+ yield session
36
+ ensure
37
+ session.close if block_given?
38
+ end
39
+
40
+ # Public: Create a new Session
41
+ #
42
+ # uri - The connection String used to connect to appropriate provider
43
+ # options - The Hash of options that are passed to the underlying Adapter
44
+ #
45
+ # Returns a new Session
46
+ def initialize( uri, options = {} )
47
+ @uri = URI.parse( uri )
48
+ @root_path = Pathname.new( @uri.path )
49
+
50
+ adapter_klass = Qup::Adapters[@uri.scheme]
51
+ @adapter = adapter_klass.new( @uri, options )
52
+
53
+ @queues = Hash.new
54
+ @topics = Hash.new
55
+ end
56
+
57
+ # Public: Allocate a new Queue
58
+ #
59
+ # Connect to an existing, or Create a new Queue with the given name and
60
+ # options.
61
+ #
62
+ # Yields the Queue.
63
+ #
64
+ # name - The String name of the Queue to connect/create
65
+ #
66
+ # Returns a new Queue instance
67
+ def queue( name, &block )
68
+ destination( name, :queue, @queues, &block )
69
+ end
70
+
71
+ # Public: Allocate a new Topic
72
+ #
73
+ # Connect to an existing, or Create a new Topic with the given name and
74
+ # options.
75
+ #
76
+ # Yields the Topic.
77
+ #
78
+ # name - The String name of the Topic to connect/create
79
+ #
80
+ # Returns a new Topic instance
81
+ def topic( name, &block )
82
+ destination( name, :topic, @topics, &block )
83
+ end
84
+
85
+ # Public: Close the Session
86
+ #
87
+ # Calling closed on an already closed Session does nothing.
88
+ #
89
+ # Returns nothing
90
+ def close
91
+ @adapter.close
92
+ end
93
+
94
+ # Public: Is the Session closed?
95
+ #
96
+ # Returns true if the session is closed, false otherwise.
97
+ def closed?
98
+ @adapter.closed?
99
+ end
100
+
101
+ #######
102
+ private
103
+ #######
104
+ def destination( name, type, collection, &block )
105
+ raise Qup::Session::ClosedError, "Session connected to #{@uri} is closed" if closed?
106
+ d = (collection[name] ||= @adapter.send( type, name ))
107
+ return d unless block_given?
108
+ yield d
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,28 @@
1
+ module Qup
2
+ # Public: Receives Messages from a Topic
3
+ #
4
+ # All Subscribers to a Topic will receive each Message that is published to
5
+ # the Topic.
6
+ #
7
+ class Subscriber < Consumer
8
+ # Public: The name of this subscriber
9
+ attr_reader :name
10
+
11
+ # Public: Create a new Subscriber on a Topic
12
+ #
13
+ # Returns a Subscriber
14
+ def initialize( topic, queue )
15
+ super( queue )
16
+ @topic = topic
17
+ @name = @queue.name
18
+ end
19
+
20
+ # Public: Remove the Subscriber from the Topic
21
+ #
22
+ # This just means to destroy the Queue that it is attached to
23
+ #
24
+ def unsubscribe
25
+ @queue.destroy
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,92 @@
1
+ module Qup
2
+ #
3
+ # Public: A TopicAPI for use in a publish-subscribe Messaging
4
+ #
5
+ # The Topic delivers each Message that it is give to each and every Subscriber
6
+ #
7
+ # This API MUST be implemented in each Adapter
8
+ #
9
+ # Example:
10
+ #
11
+ # session = Qup::Session.new( uri )
12
+ # topic = session.topic( 'news' )
13
+ #
14
+ # pub = topic.publisher
15
+ #
16
+ # sub1 = topic.subscriber( 'sub1' )
17
+ # sub2 = topic.subscriber( 'sub2' )
18
+ #
19
+ # pub.publish( 'some news' )
20
+ #
21
+ # message1 = sub1.consume
22
+ # message2 = sub2.consume
23
+ #
24
+ module TopicAPI
25
+ #--------------------------------------------------------------------------
26
+ # The API that Adapters must implement
27
+ #--------------------------------------------------------------------------
28
+
29
+ # Public: Creates a Publisher for the Topic
30
+ #
31
+ # Returns a new Publisher
32
+ def publisher
33
+ raise NotImplementedError, "please implement 'publisher'"
34
+ end
35
+
36
+
37
+ # Public: Create a subscriber for the Topic
38
+ #
39
+ # name - the String name of the subscriber
40
+ #
41
+ # Creating a subscriber creates a new Subscriber that will receive a copy of
42
+ # every message that is published to the Topic.
43
+ #
44
+ # Subscribers are unique by name, two subscribers with the same name will
45
+ # act as individual Consumers on a queue of their name.
46
+ #
47
+ # Returns a Subscriber
48
+ def subscriber( name )
49
+ raise NotImplementedError, "please implement 'subscriber'"
50
+ end
51
+
52
+
53
+ # Public: the name of the Topic
54
+ #
55
+ # Returns the String name
56
+ def name
57
+ super
58
+ rescue NoMethodError
59
+ raise NotImplementedError, "please implement 'name'"
60
+ end
61
+
62
+
63
+ # Public: destroy the Topic if possible
64
+ #
65
+ # This will remove the Topic from the system if possible
66
+ #
67
+ # Returns nothing.
68
+ def destroy
69
+ super
70
+ rescue NoMethodError
71
+ raise NotImplementedError, "please implement 'destroy'"
72
+ end
73
+
74
+
75
+ # Public: Return the number of Subscribers to this Topic
76
+ #
77
+ # Returns integer
78
+ def subscriber_count
79
+ raise NotImplementedError, "please implement 'subscriber_count'"
80
+ end
81
+
82
+
83
+ # Internal: Publish a Message to all the Subscribers
84
+ #
85
+ # message - the Object to send to all subscribers
86
+ #
87
+ # Returns nothing
88
+ def publish( message )
89
+ raise NotImplementedError, "please implement 'publish'"
90
+ end
91
+ end
92
+ end