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,123 @@
1
+ require 'qup/producer'
2
+ require 'qup/consumer'
3
+
4
+ class Qup::Adapter::Maildir
5
+ #
6
+ # Internal: The Qup Implementation in the Maildir Adapter
7
+ #
8
+ class Queue
9
+ include Qup::QueueAPI
10
+ # Internal: the name of the Queue
11
+ attr_reader :name
12
+
13
+ # Internal: Create a new Queue
14
+ #
15
+ # root_path - the root_path for this Queue to create under
16
+ # name - the String name of the Queue
17
+ #
18
+ # Returns a new Queue.
19
+ def initialize( root_path, name )
20
+ @root_path = ::Pathname.new( root_path )
21
+ @name = name
22
+ @queue_path = @root_path + @name
23
+ @maildir = ::Maildir.new( @queue_path, true )
24
+ end
25
+
26
+
27
+ # Internal: Remove the Queue from the system
28
+ #
29
+ # Returns nothing.
30
+ def destroy
31
+ @queue_path.rmtree
32
+ end
33
+
34
+
35
+ # Internal: Remove all messages from the Queue
36
+ #
37
+ # Returns nothing.
38
+ def flush
39
+ ::Maildir::SUBDIRS.each do |sub|
40
+ dir = Pathname.new( File.join( @maildir.path, sub.to_s ))
41
+ dir.children.each do |p|
42
+ p.delete if p.file?
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+ # Internal: return the number of Messages on the Queue
49
+ #
50
+ # Returns an integer of the Queue depth
51
+ def depth
52
+ total = 0
53
+ %w[ new cur ].each do |subdir|
54
+ search_path = File.join( @maildir.path, subdir, '*' )
55
+ keys = Dir.glob( search_path )
56
+ total += keys.size
57
+ end
58
+ return total
59
+ end
60
+
61
+
62
+ # Internal: Put an item onto the Queue
63
+ #
64
+ # message - the data to put onto the queue.
65
+ #
66
+ # The 'message' that is passed in is wrapped in a Qup::Message before being
67
+ # stored.
68
+ #
69
+ # A user of the Qup API should use a Producer instance to put items onto the
70
+ # queue.
71
+ #
72
+ # Returns the Message that was put onto the Queue
73
+ def produce( message )
74
+ msg = @maildir.add( message )
75
+ return ::Qup::Message.new( msg.key, msg.data )
76
+ end
77
+
78
+
79
+ # Internal: Retrieve a Message from the Queue
80
+ #
81
+ # Yields a Message
82
+ #
83
+ # A user of the Qup API should use a Consumer instance to retrieve items
84
+ # from the Queue.
85
+ #
86
+ # Returns a Message
87
+ def consume(&block)
88
+ msg = @maildir.list(:new, :limit => 1).first
89
+ return nil if msg.nil?
90
+ msg.process
91
+ msg.seen!
92
+ q_message = ::Qup::Message.new( msg.key, msg.data )
93
+ if block_given? then
94
+ yield_message( q_message, &block )
95
+ else
96
+ return q_message
97
+ end
98
+ end
99
+
100
+
101
+ # Internal: Acknowledge that message is completed and remove it from the
102
+ # Queue.
103
+ #
104
+ # Returns nothing
105
+ def acknowledge( message )
106
+ md_message = @maildir.get( message.key )
107
+ msg = "Message #{message.key} has not been processed yet"
108
+ raise ::Qup::Error, msg unless md_message.dir == :cur
109
+ raise ::Qup::Error, msg unless md_message.seen?
110
+ md_message.destroy
111
+ end
112
+
113
+ #######
114
+ private
115
+ #######
116
+
117
+ def yield_message( message, &block )
118
+ yield message
119
+ ensure
120
+ acknowledge( message )
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,85 @@
1
+ class Qup::Adapter::Maildir
2
+ #
3
+ # Internal: A Topic 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
+ class Topic
8
+ include Qup::TopicAPI
9
+
10
+ # Internal: the name of the Topic
11
+ attr_reader :name
12
+
13
+ # Internal: Create a new Topic
14
+ #
15
+ # root_path - the Session this Topic is attached to
16
+ # name - the String name of the Topic
17
+ #
18
+ # Returns a new Topic.
19
+ def initialize( root_path, name )
20
+ @root_path = ::Pathname.new( root_path )
21
+ @name = name
22
+ @topic_path = @root_path + @name
23
+ @subscribers = Hash.new
24
+
25
+ FileUtils.mkdir_p( @topic_path )
26
+ end
27
+
28
+ # Internal: Destroy the Topic
29
+ #
30
+ # If possible remove the existence of the Topic from the System
31
+ #
32
+ # Returns nothing.
33
+ def destroy
34
+ @topic_path.rmtree
35
+ end
36
+
37
+ # Internal: Creates a Publisher for the Topic
38
+ #
39
+ # Returns a new Publisher
40
+ def publisher
41
+ ::Qup::Publisher.new( self )
42
+ end
43
+
44
+ # Internal: Create a subscriber for the Topic
45
+ #
46
+ # name - the String name of the subscriber
47
+ #
48
+ # Creating a subscriber creates a new Subscriber that will receive a copy of
49
+ # every message that is published to the Topic.
50
+ #
51
+ # Subscribers are unique by name, two subscribers with the same name will
52
+ # act as individual Consumers on a queue of their name.
53
+ #
54
+ # Returns a Subscriber
55
+ def subscriber( name )
56
+ ::Qup::Subscriber.new( self, sub_queue( name ) )
57
+ end
58
+
59
+ # Internal: Return the number of Subscribers to this Topic
60
+ #
61
+ # Returns integer
62
+ def subscriber_count
63
+ @subscribers.size
64
+ end
65
+
66
+ # Internal: Publish a Message to all the Subscribers
67
+ #
68
+ # message - the Object to send to all subscribers
69
+ #
70
+ # Returns nothing
71
+ def publish( message )
72
+ @subscribers.each do |name, sub|
73
+ sub.produce( message )
74
+ end
75
+ end
76
+
77
+ #######
78
+ private
79
+ #######
80
+
81
+ def sub_queue( name )
82
+ @subscribers[name] ||= ::Qup::Adapter::Maildir::Queue.new( @topic_path, name )
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,55 @@
1
+ require 'qup/adapter'
2
+ require 'redis'
3
+
4
+ class Qup::Adapter
5
+ # Internal: The backing adapter for Qup that uses Redis as the messaging
6
+ # infrastructure
7
+ class Redis < ::Qup::Adapter
8
+
9
+ # Register this adapter as :redis
10
+ register :redis
11
+
12
+ # Internal: Create a new Redis Adapter
13
+ #
14
+ # uri - the URI instance for this adapter to use
15
+ def initialize( uri, options = {} )
16
+ @uri = uri
17
+ @options = options
18
+ @closed = false
19
+ end
20
+
21
+ # Internal: Create a new Queue from this Adapter
22
+ #
23
+ # name - the String name of the Queue
24
+ #
25
+ # Returns a Qup::Queue
26
+ def queue( name )
27
+ Qup::Adapter::Redis::Queue.new( @uri, name )
28
+ end
29
+
30
+ # Internal: Create a new Topic from this Adapter
31
+ #
32
+ # name - the name of this Topic
33
+ #
34
+ # Returns a Qup::Topic
35
+ def topic( name )
36
+ Qup::Adapter::Redis::Topic.new( @uri, name )
37
+ end
38
+
39
+ # Internal: Close the Redis adapter
40
+ #
41
+ # Return nothing
42
+ def close
43
+ @closed = true
44
+ end
45
+
46
+ # Internal: Is the Redis Adapter closed
47
+ #
48
+ # Returns true or false
49
+ def closed?
50
+ @closed
51
+ end
52
+ end
53
+ end
54
+ require 'qup/adapter/redis/queue'
55
+ require 'qup/adapter/redis/topic'
@@ -0,0 +1,32 @@
1
+ class Qup::Adapter::Redis
2
+ #
3
+ # Internal: The Common base class for Redis Topic and Queue
4
+ #
5
+ class Connection
6
+
7
+ # Public: the name of the Queue or Topic
8
+ attr_reader :name
9
+
10
+ # Public: Create a new Connection
11
+ #
12
+ # uri - the connection uri for the Redis Client
13
+ # name - the String name of the Connection
14
+ #
15
+ # Returns a new Connection.
16
+ def initialize( uri, name )
17
+ @uri = uri
18
+ @client = Redis.new :host => @uri.host, :port => @uri.port
19
+ @name = name
20
+ end
21
+
22
+ # Public: destroy the connection
23
+ #
24
+ # Closes the redis client connection.
25
+ #
26
+ # Returns nothing.
27
+ def destroy
28
+ @client.client.disconnect
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,97 @@
1
+ require 'qup/adapter/redis/connection'
2
+
3
+ class Qup::Adapter::Redis
4
+ #
5
+ # Internal: The Qup implementation for a Redis Queue
6
+ #
7
+ class Queue < Connection
8
+ include Qup::QueueAPI
9
+
10
+ # Internal: create a new Queue
11
+ #
12
+ # uri - the connection uri for the Redis Client
13
+ # name - the String name of the Queue
14
+ #
15
+ # Returns a new Queue.
16
+ def initialize( uri, name )
17
+ super
18
+ @open_messages = {}
19
+ end
20
+
21
+ # Internal: Destroy the queue
22
+ #
23
+ # Removes the list from redis.
24
+ #
25
+ # Returns nothing.
26
+ def destroy
27
+ @client.del name
28
+ end
29
+
30
+ # Internal: Empty the queue
31
+ #
32
+ # In redis, this is accomplished by merely deleting the list. If anything
33
+ # new is added to it, the key is re-created on the server
34
+ #
35
+ # Returns nothing
36
+ alias :flush :destroy
37
+
38
+ # Internal: return the number of Messages on the Queue
39
+ #
40
+ # Returns an integer of the Queue depth
41
+ def depth
42
+ @client.llen name
43
+ end
44
+
45
+ # Internal: Acknowledge that message is completed and remove it from the
46
+ # Queue.
47
+ #
48
+ # In redis, this doesn't do anything at all. The tracking is only performed
49
+ # to meet the API requirements.
50
+ #
51
+ # Returns nothing.
52
+ def acknowledge( message )
53
+ open_msg = @open_messages.delete( message.key )
54
+ raise Qup::Error, "Message #{message.key} is not currently being consumed" unless open_msg
55
+ end
56
+
57
+ # Internal: Put an item onto the Queue
58
+ #
59
+ # message - the data to put onto the queue.
60
+ #
61
+ # The 'message' that is passed in is wrapped in a Qup::Message before being
62
+ # stored.
63
+ #
64
+ # Returns the Message that was put onto the Queue
65
+ def produce( message )
66
+ @client.lpush name, message
67
+ return ::Qup::Message.new( message.object_id, message )
68
+ end
69
+
70
+ # Internal: Retrieve a Message from the Queue
71
+ #
72
+ # Yields a Message
73
+ #
74
+ # Returns a Message
75
+ def consume(&block)
76
+ queue_name, data = @client.brpop @name, 0 # blocking pop
77
+ message = ::Qup::Message.new( data.object_id, data )
78
+ @open_messages[message.key] = message
79
+ if block_given? then
80
+ yield_message( message, &block )
81
+ else
82
+ return message
83
+ end
84
+ end
85
+
86
+ #######
87
+ private
88
+ #######
89
+
90
+ def yield_message( message, &block )
91
+ yield message
92
+ ensure
93
+ acknowledge( message )
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,76 @@
1
+ require 'qup/adapter/redis/connection'
2
+
3
+ class Qup::Adapter::Redis
4
+ #
5
+ # Internal: The Qup implementation for a Redis Topic
6
+ #
7
+ # Based on the API requirements, this is not implemented with Redis pub/sub,
8
+ # rather it guarantees durability of all published messages by using
9
+ # sub-queues internally to deliver messages to subscribers.
10
+ #
11
+ class Topic < Connection
12
+ include Qup::TopicAPI
13
+
14
+ # Internal: create a new Topic
15
+ #
16
+ # uri - the connection uri for the Redis Client
17
+ # name - the String name of the Topic
18
+ #
19
+ # Returns a new Topic.
20
+ def initialize(uri, name)
21
+ super
22
+ @subscribers = Hash.new
23
+ end
24
+
25
+ # Internal: Creates a Publisher for the Topic
26
+ #
27
+ # Returns a new Publisher
28
+ def publisher
29
+ ::Qup::Publisher.new( self )
30
+ end
31
+
32
+ # Internal: Create a subscriber for the Topic
33
+ #
34
+ # name - the String name of the subscriber
35
+ #
36
+ # Creating a subscriber creates a new Subscriber that will receive a copy of
37
+ # every message that is published to the Topic.
38
+ #
39
+ # Returns a Subscriber
40
+ def subscriber(name)
41
+ ::Qup::Subscriber.new( self, subscriber_queue_for(name) )
42
+ end
43
+
44
+ # Internal: Return the number of Subscribers to this Topic
45
+ #
46
+ # Returns integer
47
+ def subscriber_count
48
+ @subscribers.size
49
+ end
50
+
51
+ # Internal: Publish a Message to all the Subscribers
52
+ #
53
+ # message - the Object to send to all subscribers
54
+ #
55
+ # Returns nothing
56
+ def publish( message )
57
+ @subscribers.values.each do |subscriber|
58
+ subscriber.produce message
59
+ end
60
+ end
61
+
62
+ #######
63
+ private
64
+ #######
65
+
66
+ # Private: create and register new sub-Queue for the given subscriber
67
+ #
68
+ # name - the name of the subscriber
69
+ #
70
+ # Returns a Queue
71
+ def subscriber_queue_for(name)
72
+ @subscribers[name] ||=
73
+ ::Qup::Adapter::Redis::Queue.new(@uri, "#{@name}-#{name}")
74
+ end
75
+ end
76
+ end