tinyq 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.dirname($0) + "/../lib"
4
+
5
+ require 'rubygems'
6
+ require 'tinyq.rb'
7
+
8
+ TinyQ::Server.new.main(ARGV)
@@ -0,0 +1,37 @@
1
+ # Install
2
+ # gem install eventmachine json uuid
3
+ #
4
+ #
5
+ # TODO
6
+ # capped - for bucket and funnel
7
+ # - overflow or round-robin
8
+ #
9
+ # permanent - for bucket and funnel
10
+ # stream - for GetMessages
11
+ # (basically if count = 0 then send 1 message at a time, but never remove subscriber
12
+
13
+ require 'rubygems'
14
+ require 'eventmachine'
15
+ require 'json'
16
+ require 'logger'
17
+ require 'time'
18
+ require 'uuid'
19
+ require 'getopt/std'
20
+ require 'singleton'
21
+ require 'daemons'
22
+
23
+ require 'tinyq/subscriber'
24
+ require 'tinyq/funnel'
25
+ require 'tinyq/bucket'
26
+ require 'tinyq/server'
27
+ require 'tinyq/connection'
28
+ require 'tinyq/permanent'
29
+ require 'tinyq/client'
30
+ require 'tinyq/version'
31
+ require 'tinyq/defs'
32
+ require 'tinyq/journal'
33
+
34
+
35
+ $LOG = Logger.new(STDOUT)
36
+ $LOG.level = Logger::INFO
37
+
@@ -0,0 +1,138 @@
1
+ module TinyQ
2
+ #
3
+ # Bucket, messages get dropped into the bucket and forwarded
4
+ # to the different funnels connected to that bucket
5
+ #
6
+ class Bucket
7
+ attr_accessor :name
8
+
9
+ attr_reader :messages
10
+ attr_reader :message_ids
11
+ attr_reader :references
12
+ attr_reader :pendings
13
+
14
+ attr_accessor :funnels
15
+
16
+ attr_accessor :permanent
17
+
18
+ def initialize(n, p = false)
19
+ @name = n
20
+ @permanent = p
21
+ @messages = {}
22
+ @message_ids = []
23
+ @references = {}
24
+ @pendings = {}
25
+ @funnels = {}
26
+ @uuid = UUID.new
27
+ end
28
+
29
+ def put_message(message)
30
+ message_id = @uuid.generate
31
+ #message[:__id] = message_id
32
+ #message[:__sent] = Time.now.iso8601
33
+
34
+ # If permantent bucket, then store
35
+ Permanent.store message,"#{message_id}.dat", { :gzip => true } unless !@permanent
36
+
37
+ @messages[message_id] = message
38
+ @message_ids.push(message_id)
39
+ @references[message_id] = @funnels.keys
40
+ @pendings[message_id] = []
41
+
42
+ if !@funnels.empty?
43
+ # Put message in each funnel
44
+ @funnels.each do |n,funnel|
45
+ $LOG.debug("Bucket #{@name} - Notifying funnel #{funnel.name}")
46
+ funnel.notify(self)
47
+ end
48
+ end
49
+ end
50
+
51
+ def dequeue(funnel)
52
+ if !@messages.empty?
53
+ message_id = nil
54
+
55
+ @message_ids.each do |mid|
56
+ $LOG.debug("Bucket #{@name} - #{mid} references: #{@references[mid]}")
57
+ if @references[mid].count(funnel.name) > 0
58
+ # That message was not de-referenced yet
59
+ $LOG.debug("Bucket #{@name} - #{mid} -> #{funnel.name}")
60
+ message_id = mid
61
+ break
62
+ end
63
+ end
64
+
65
+ if nil != message_id
66
+ $LOG.debug("Bucket #{@name} - Sending #{message_id} to funnel #{funnel.name}")
67
+ message = @messages[message_id]
68
+
69
+ # Remove the given funnel from a reference
70
+ @references[message_id].delete(funnel.name)
71
+ # Add the given funnel to the pending list for that message
72
+ @pendings[message_id].push(funnel.name)
73
+
74
+ [message, message_id]
75
+ else
76
+ $LOG.debug("Bucket #{@name} - No more messages for funnel #{funnel.name}")
77
+ [nil, nil]
78
+ end
79
+ else
80
+ [nil, nil]
81
+ end
82
+ end
83
+
84
+ def message_sent(funnel, message_id)
85
+ $LOG.debug("Bucket #{@name} - Message #{message_id} sent on #{funnel.name}")
86
+ @pendings[message_id].delete(funnel.name)
87
+
88
+ # If no funnels are either pending or referenced, then message can be removed
89
+ if @pendings[message_id].empty? && @references[message_id].empty?
90
+ $LOG.debug("Bucket #{@name} - Purge message #{message_id}")
91
+ # No more references, message can be deleted
92
+ Permanent.remove "#{message_id}.dat" unless !@permanent
93
+ @messages.delete(message_id)
94
+ @message_ids.delete(message_id)
95
+ @references.delete(message_id)
96
+ @pendings.delete(message_id)
97
+ true
98
+ end
99
+
100
+ false
101
+ end
102
+
103
+ def funnel(name, broadcaster = false)
104
+ funnel = @funnels[name]
105
+ if nil == funnel
106
+ $LOG.info("Bucket #{@name} - Creating funnel #{name}")
107
+ funnel = Funnel.new(name, broadcaster)
108
+ feed_funnel(funnel)
109
+ end
110
+ # Update potential settings
111
+ funnel.broadcaster = broadcaster
112
+
113
+ funnel
114
+ end
115
+
116
+ def feed_funnel(funnel)
117
+ @funnels[funnel.name] = funnel
118
+ # Add ourself to the list of buckets for that funnel
119
+ if funnel.buckets.count self == 0
120
+ funnel.buckets.push(self)
121
+ end
122
+
123
+ # If we have cached messages, notify new funnel
124
+ if !@messages.empty?
125
+ # Add the current funnels as a reference
126
+ @message_ids.each do |mid|
127
+ @references[mid].push(funnel.name)
128
+ end
129
+ $LOG.debug("Bucket #{@name} - Notifying funnel #{funnel.name}")
130
+ funnel.notify(self)
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+ end
137
+
138
+
@@ -0,0 +1,59 @@
1
+ module TinyQ
2
+ class ClientConnection < EventMachine::Connection
3
+ include EventMachine::Protocols::LineText2
4
+
5
+ attr_accessor :client
6
+
7
+ def initialize
8
+ set_delimiter TinyQ::DELIMITER
9
+ end
10
+
11
+ def receive_line message
12
+ begin
13
+ reply = JSON.parse(message)
14
+ @client.callbacks.each { |c| c.call(reply) }
15
+ rescue Exception => e
16
+ puts e
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ class Client
23
+ attr_accessor :callbacks
24
+
25
+ def initialize
26
+ @callbacks = []
27
+ end
28
+
29
+ def connect(host, port)
30
+ @host, @port = host, port
31
+ @connection = EM.connect @host, @port, TinyQ::ClientConnection
32
+ @connection.client = self
33
+ end
34
+
35
+ #
36
+ # Public API
37
+ #
38
+ def put_message(bucket, message)
39
+ command = {:Command => "PutMessages", :Bucket => bucket, :Message => message}
40
+ @connection.send_data("#{command.to_json}#{TinyQ::DELIMITER}")
41
+ end
42
+
43
+ def get_message(funnel)
44
+ command = {:Command => "GetMessages", :Funnel => funnel}
45
+ @connection.send_data("#{command.to_json}#{TinyQ::DELIMITER}")
46
+ end
47
+
48
+ def onreply(&block)
49
+ @callbacks << block
50
+ end
51
+
52
+
53
+ def feed_funnel(bucket, funnel)
54
+ command = {:Command => "FeedFunnel", :Bucket => bucket, :Funnel => funnel}
55
+ @connection.send_data("#{command.to_json}#{TinyQ::DELIMITER}")
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,160 @@
1
+ module TinyQ
2
+ class Connection < EventMachine::Connection
3
+ attr_accessor :server
4
+ attr_reader :ip
5
+ attr_reader :port
6
+
7
+ include EventMachine::Protocols::LineText2
8
+
9
+
10
+ def initialize
11
+ set_delimiter TinyQ::DELIMITER
12
+ end
13
+
14
+ def post_init
15
+ @port, *ip_parts = get_peername[2,6].unpack "nC4"
16
+ @ip = ip_parts.join('.')
17
+
18
+ $LOG.info("#{@ip}:#{@port} connected")
19
+ end
20
+
21
+ def receive_line line
22
+ process_message line
23
+ end
24
+
25
+ def reply rsp
26
+ send_data("#{rsp.to_json}#{TinyQ::DELIMITER}")
27
+ end
28
+
29
+ def ok
30
+ response = {:Status => "OK"}
31
+ reply(response)
32
+ end
33
+
34
+ def failed errmsg
35
+ response = {:Status => "Failed", :Message => errmsg}
36
+ reply(response)
37
+ end
38
+
39
+
40
+ def process_message data
41
+ begin
42
+ request = JSON.parse(data)
43
+ $LOG.debug("Request: #{request}")
44
+ if request.has_key? 'Command'
45
+ $LOG.debug("Command - #{request['Command']}")
46
+
47
+ case request['Command']
48
+ when "PutMessages"
49
+ # Put message in a bucket
50
+ if request.has_key? 'Bucket'
51
+ bucket = @server.bucket(request['Bucket'])
52
+
53
+ if request['Message'].kind_of?(Array)
54
+ request['Message'].each do |message|
55
+ Journal.instance.event({:Event=>"PutMessage"})
56
+ bucket.put_message(message)
57
+ end
58
+ else
59
+ Journal.instance.event({:Event=>"PutMessage"})
60
+ bucket.put_message(request['Message'])
61
+ end
62
+
63
+ ok()
64
+ else
65
+ failed("Parameter Bucket missing")
66
+ end
67
+ when "FeedFunnel"
68
+ # Feed Bucket to Funnel
69
+ if request.has_key? 'Bucket' and request.has_key? 'Funnel'
70
+ # Hook up a funnel to a bucket
71
+ bucket = @server.bucket(request['Bucket'])
72
+ funnel = @server.funnels[request['Funnel']]
73
+ if nil != funnel
74
+ bucket.feed_funnel(funnel)
75
+ else
76
+ if request.has_key? 'Broadcaster'
77
+ funnel = bucket.funnel(request['Funnel'], request['Broadcaster'])
78
+ else
79
+ funnel = bucket.funnel(request['Funnel'])
80
+ end
81
+ @server.funnels[request['Funnel']] = funnel
82
+ end
83
+
84
+ ok()
85
+ else
86
+ failed("Parameter Bucket or Funnel missing")
87
+ end
88
+ when "GetMessages"
89
+ # Get Messages from Funnel
90
+ if request.has_key? 'Funnel'
91
+ funnel = @server.funnels[request['Funnel']]
92
+ if nil == funnel
93
+ $LOG.warn("Funnel was never fed...")
94
+ failed("Funnel was never fed")
95
+ else
96
+ if request.has_key? 'Ack'
97
+ ok()
98
+ end
99
+
100
+ if request.has_key? 'Count'
101
+ funnel.add_connection(self, request['Count'])
102
+ else
103
+ funnel.add_connection(self)
104
+ end
105
+ end
106
+ else
107
+ failed("Parameter Funnel missing")
108
+ end
109
+ when "GetInfo"
110
+ if request.has_key? 'Bucket'
111
+ bucket = @server.bucket(request['Bucket'])
112
+
113
+ response = {
114
+ :Status => 'OK',
115
+ :Bucket => {
116
+ :Name => bucket.name,
117
+ :MessageCount => bucket.messages.size,
118
+ :Funnels => [],
119
+ }
120
+ }
121
+
122
+ bucket.funnels.each do |n,funnel|
123
+ response[:Bucket][:Funnels].push({
124
+ :Name => funnel.name,
125
+ :MessageCount => funnel.messages.size,
126
+ :SubscriberCount => funnel.subscribers.size,
127
+ })
128
+ end
129
+
130
+ reply(response)
131
+
132
+ elsif request.has_key? 'Funnel'
133
+ else
134
+ failed("Parameter Funnel or Bucket missing")
135
+ end
136
+ else
137
+ # Unsupported command
138
+ failed("Unsupported command")
139
+ end
140
+
141
+ else
142
+ # Did not understand
143
+ failed("Parameter Command missing")
144
+ end
145
+ rescue Exception => e
146
+ $LOG.error("Failed #{e}")
147
+ failed("Internal error #{e}")
148
+ end
149
+ end
150
+
151
+ def unbind
152
+ $LOG.info("#{@ip}:#{@port} disconnected")
153
+
154
+ @server.remove_connection(self)
155
+ end
156
+
157
+ end
158
+ end
159
+
160
+
@@ -0,0 +1,4 @@
1
+ module TinyQ
2
+ DELIMITER = "\0"
3
+ #DELIMITER = "\n"
4
+ end
@@ -0,0 +1,91 @@
1
+ module TinyQ
2
+ #
3
+ # Funnel will receive messages on the internal queue and will dispatch
4
+ # to subscribers
5
+ #
6
+ class Funnel
7
+ attr_accessor :name
8
+ attr_reader :queue
9
+ attr_accessor :buckets
10
+ attr_accessor :subscribers
11
+ attr_accessor :broadcaster
12
+
13
+ def initialize(n, b = false)
14
+ @name = n
15
+ @broadcaster = b
16
+ @queue = EventMachine::Queue.new
17
+ @subscribers = {}
18
+ @buckets = []
19
+
20
+ cb = Proc.new do |event|
21
+ $LOG.debug("Funnel #{@name} - Callback")
22
+ if !@subscribers.empty?
23
+ # OK we can dequeue from bucket since we have somewhere to send it
24
+ bucket = event[:Bucket]
25
+ message,message_id = bucket.dequeue(self)
26
+
27
+ $LOG.debug("Funnel #{@name} - Callback got '#{message_id}'")
28
+
29
+ if message != nil
30
+ if @broadcaster
31
+ $LOG.debug("Funnel #{@name} - Broadcasting #{message_id}")
32
+ @subscribers.each do |c,subscriber|
33
+ if subscriber.put_message(bucket, self, message, message_id)
34
+ # Subscriber is done removing
35
+ $LOG.debug("Funnel #{@name} - Subscriber received requested count")
36
+ self.remove_connection(c)
37
+ end
38
+ end
39
+ else
40
+ $LOG.debug("Funnel #{@name} - Unicasting #{message_id}")
41
+ c = @subscribers.keys[0]
42
+ subscriber = @subscribers[c]
43
+ $LOG.debug("Funnel #{@name} - Unicasting to #{subscriber.connection.ip}:#{subscriber.connection.port}")
44
+ if subscriber.put_message(bucket, self, message, message_id)
45
+ # Subscriber is done removing
46
+ $LOG.debug("Funnel #{@name} - Subscriber #{subscriber.connection.ip}:#{subscriber.connection.port} received requested count")
47
+ self.remove_connection(c)
48
+ end
49
+ end
50
+ else
51
+ $LOG.debug("Funnel #{@name} - Callback noop")
52
+ end
53
+ end
54
+
55
+ # Wait for next event
56
+ @queue.pop &cb
57
+ end
58
+
59
+ @queue.pop &cb
60
+ end
61
+
62
+ # Method called by bucket when messages are available
63
+ def notify(bucket)
64
+ @queue.push({:Event => "New Message", :Bucket => bucket})
65
+ end
66
+
67
+ def add_connection(c,n = 1)
68
+ subscriber = @subscribers[c]
69
+ if nil == subscriber
70
+ subscriber = Subscriber.new(c,n)
71
+ @subscribers[c] = subscriber
72
+ end
73
+
74
+ # At this point, we need to see if any buckets we are connected to
75
+ # have pending messages
76
+ @buckets.each do |bucket|
77
+ if !bucket.messages.empty?
78
+ self.notify(bucket)
79
+ end
80
+ end
81
+ end
82
+
83
+ def remove_connection(c)
84
+ $LOG.debug("Funnel #{@name} - Removing subscriber #{c.ip}:#{c.port}")
85
+ @subscribers.delete(c)
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+
@@ -0,0 +1,21 @@
1
+ module TinyQ
2
+ class Journal
3
+ include Singleton
4
+
5
+ attr_accessor :enabled
6
+
7
+ def initialize
8
+ @journal = File.open("journal.txt", "a")
9
+ @enabled = false
10
+ end
11
+
12
+ def event(event)
13
+ if @enabled
14
+ now=Time.now.iso8601
15
+ @journal.puts("#{now},#{event[:Event]}")
16
+ @journal.fsync
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ require 'zlib'
2
+
3
+ module TinyQ
4
+ class Permanent
5
+
6
+
7
+ def self.store obj, filename, options = {}
8
+ dump = Marshal.dump(obj)
9
+ file = File.new(filename, 'w')
10
+ file = Zlib::GzipWriter.new(file) unless options[:gzip] == false
11
+ file.write dump
12
+ file.close
13
+ return obj
14
+ end
15
+
16
+ def self.load filename
17
+ begin
18
+ file = Zlib::GzipReader.open(filename)
19
+ rescue Zlib::GzipFile::Error
20
+ file = File.open(filename, 'r')
21
+ ensure
22
+ obj = Marshal.load file.read
23
+ file.close
24
+ return obj
25
+ end
26
+ end
27
+
28
+ def self.remove filename
29
+ File.delete(file)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,106 @@
1
+ module TinyQ
2
+ class Server
3
+ attr_accessor :connections
4
+
5
+ attr_accessor :buckets
6
+ attr_accessor :funnels
7
+
8
+ def initialize
9
+ @connections = []
10
+ @buckets = {}
11
+ @funnels = {}
12
+ end
13
+
14
+ def usage
15
+ puts "#{$0}"
16
+ puts "version: #{TinyQ::VERSION}"
17
+ puts " -b go into background/daemonize"
18
+ puts " -p <port> port number to listen to (default: 64321)"
19
+ puts " -i <ip> ip to bind to (default: 127.0.0.1)"
20
+ puts " -d turn on debug"
21
+ puts " -h help, this message"
22
+ end
23
+
24
+ def main(args)
25
+ opt = Getopt::Std.getopts("bp:i:dh")
26
+
27
+ if opt['h']
28
+ usage
29
+ exit
30
+ end
31
+
32
+ @ip = opt['i'] || '127.0.0.1'
33
+ @port = opt['p'] || 64321
34
+
35
+ if opt['d']
36
+ $LOG.level = Logger::DEBUG
37
+ end
38
+
39
+ if opt['b']
40
+ puts "Going into background"
41
+ Daemons.daemonize
42
+ end
43
+
44
+ start
45
+ end
46
+
47
+ def self.start
48
+ new().start
49
+ end
50
+
51
+ def start
52
+ EventMachine::run {
53
+ trap("TERM") { stop }
54
+ trap("INT") { stop }
55
+ EventMachine.epoll
56
+ @signature = EventMachine.start_server(@ip, @port, Connection) do |con|
57
+ con.server = self
58
+ # We actually do not want to wait on clients
59
+ #@connections.push(con)
60
+ end
61
+ $LOG.info("TinyQ listening on 0.0.0.0:64321")
62
+ }
63
+ end
64
+
65
+
66
+ def stop
67
+ $LOG.info("TinyQ Exiting")
68
+ EventMachine.stop_server(@signature)
69
+
70
+ unless wait_for_connections_and_stop
71
+ EventMachine.add_periodic_timer(1) { wait_for_connections_and_stop }
72
+ end
73
+ end
74
+
75
+ def wait_for_connections_and_stop
76
+ if @connections.empty?
77
+ EventMachine.stop
78
+ true
79
+ else
80
+ $LOG.info("Waiting for #{@connections.size} connection(s) to finish...")
81
+ false
82
+ end
83
+ end
84
+
85
+ def bucket(name)
86
+ bucket = @buckets[name]
87
+ if nil == bucket
88
+ $LOG.info("Server - Creating bucket #{name}")
89
+ bucket = Bucket.new(name)
90
+ @buckets[name] = bucket
91
+ end
92
+
93
+ bucket
94
+ end
95
+
96
+ def remove_connection(c)
97
+ @connections.delete(c)
98
+ @funnels.each do |name,funnel|
99
+ funnel.remove_connection(c)
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+
@@ -0,0 +1,63 @@
1
+ module TinyQ
2
+ class Subscriber
3
+ attr_accessor :connection
4
+ attr_reader :queue
5
+ attr_reader :requested
6
+ attr_accessor :count
7
+ attr_reader :messages
8
+ attr_reader :message_ids
9
+
10
+ def initialize(c,n = 1)
11
+ @connection = c
12
+ @requested = @count = n
13
+ @queue = EventMachine::Queue.new
14
+ @messages = []
15
+ @message_ids = {}
16
+
17
+ cb = Proc.new do |event|
18
+ $LOG.debug("Subscriber #{@connection.ip}:#{@connection.port} - Queue callback")
19
+
20
+ response = { :Messages => @messages }
21
+
22
+ connection.reply(response)
23
+
24
+ # Only at that point should the message be removed from the bucket!
25
+ @message_ids.each do |message_id,info|
26
+ bucket = info[:Bucket]
27
+ funnel = info[:Funnel]
28
+
29
+ bucket.message_sent(funnel, message_id)
30
+ end
31
+
32
+
33
+ @queue.pop &cb
34
+ end
35
+
36
+ @queue.pop &cb
37
+ end
38
+
39
+ def put_message(bucket, funnel, message, message_id)
40
+ @messages.push(message)
41
+ @message_ids[message_id] = {
42
+ :Funnel => funnel,
43
+ :Bucket => bucket
44
+ }
45
+
46
+ if @messages.size == @requested
47
+ # We have everything now, so we can send all messages to subscriber
48
+ event = {
49
+ :Event => "New Message",
50
+ :Funnel => funnel,
51
+ :Bucket => bucket,
52
+ :MessageID => message_id
53
+ }
54
+ @queue.push(event)
55
+ true
56
+ else
57
+ false
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+
@@ -0,0 +1,3 @@
1
+ module TinyQ
2
+ VERSION = "0.5.0"
3
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tinyq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jerome Poichet
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-15 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: &70364366145240 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70364366145240
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ requirement: &70364366144820 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70364366144820
36
+ - !ruby/object:Gem::Dependency
37
+ name: uuid
38
+ requirement: &70364366144400 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70364366144400
47
+ - !ruby/object:Gem::Dependency
48
+ name: getopt
49
+ requirement: &70364366172140 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70364366172140
58
+ - !ruby/object:Gem::Dependency
59
+ name: daemons
60
+ requirement: &70364366171720 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70364366171720
69
+ description: TinyQ is a message broker with a simple JSON based protocol
70
+ email:
71
+ - poitch@gmail.com
72
+ executables:
73
+ - tinyq
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - lib/tinyq.rb
78
+ - lib/tinyq/bucket.rb
79
+ - lib/tinyq/client.rb
80
+ - lib/tinyq/connection.rb
81
+ - lib/tinyq/defs.rb
82
+ - lib/tinyq/funnel.rb
83
+ - lib/tinyq/journal.rb
84
+ - lib/tinyq/permanent.rb
85
+ - lib/tinyq/server.rb
86
+ - lib/tinyq/subscriber.rb
87
+ - lib/tinyq/version.rb
88
+ - bin/tinyq
89
+ homepage: https://github.com/poitch/TinyQ
90
+ licenses: []
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.6
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Ruby/TinyQ
114
+ test_files: []