tinyq 0.5.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.
@@ -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: []