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,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