easy_q 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +0 -0
- data/Manifest.txt +14 -0
- data/README.txt +84 -0
- data/Rakefile +56 -0
- data/bin/easy_q +8 -0
- data/db/001_create_messages.rb +18 -0
- data/lib/easy_q/commands.rb +144 -0
- data/lib/easy_q/easy_q_service.rb +96 -0
- data/lib/easy_q/models/message.rb +12 -0
- data/lib/easy_q/version.rb +9 -0
- data/lib/easy_q.rb +1 -0
- data/setup.rb +1585 -0
- data/test/easy_q_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +59 -0
data/CHANGELOG.txt
ADDED
File without changes
|
data/Manifest.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Rakefile
|
2
|
+
README.txt
|
3
|
+
CHANGELOG.txt
|
4
|
+
Manifest.txt
|
5
|
+
setup.rb
|
6
|
+
db/001_create_messages.rb
|
7
|
+
bin/easy_q
|
8
|
+
lib/easy_q/version.rb
|
9
|
+
lib/easy_q/commands.rb
|
10
|
+
lib/easy_q/easy_q_service.rb
|
11
|
+
lib/easy_q/models/message.rb
|
12
|
+
lib/easy_q.rb
|
13
|
+
test/test_helper.rb
|
14
|
+
test/easy_q_test.rb
|
data/README.txt
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
== EasyQ - A nice little message queue
|
2
|
+
|
3
|
+
|
4
|
+
EasyQ is my attempt to make a simple and uncomplicated message queue.
|
5
|
+
It requires an ActiveRecord compatible database. It isn't very fast,
|
6
|
+
and it doesn't support transactions and it only string messages.
|
7
|
+
It's not perfect but it works for me. I didn't need those things
|
8
|
+
for my purposes, and this was built for my purposes, but you're
|
9
|
+
welcome to use it any. Are you sold?
|
10
|
+
|
11
|
+
Oh ya...I made this on Windows and haven't tried it on *nix.
|
12
|
+
|
13
|
+
One more thing...I'm new to ruby, and I don't know anything about
|
14
|
+
RDoc. Deal.
|
15
|
+
|
16
|
+
Thanks to Assaf (reliable-msg) for the idea.
|
17
|
+
|
18
|
+
== Installation
|
19
|
+
|
20
|
+
gem install easy-q
|
21
|
+
|
22
|
+
== Setup
|
23
|
+
|
24
|
+
create a database on whatever db server your working with.
|
25
|
+
|
26
|
+
create a YAML config file. Call it whatever you want, and place it anywhere one the install machine.
|
27
|
+
|
28
|
+
sample config:
|
29
|
+
|
30
|
+
---
|
31
|
+
:database:
|
32
|
+
:adapter: mysql
|
33
|
+
:username: root
|
34
|
+
:host: localhost
|
35
|
+
:port: 3306
|
36
|
+
:password: password
|
37
|
+
:database: easy_q
|
38
|
+
:service:
|
39
|
+
:address: 0.0.0.0
|
40
|
+
:port: 2222
|
41
|
+
:acl:
|
42
|
+
- deny
|
43
|
+
- all
|
44
|
+
- allow
|
45
|
+
- localhost
|
46
|
+
- allow
|
47
|
+
- 127.0.0.1
|
48
|
+
|
49
|
+
|
50
|
+
== Usage
|
51
|
+
|
52
|
+
* Command Line Usage:
|
53
|
+
# installs the database schema
|
54
|
+
% easy_q install -c config.yaml
|
55
|
+
|
56
|
+
# starts a server
|
57
|
+
% easy_q start -c config.yaml
|
58
|
+
|
59
|
+
# stops a server
|
60
|
+
% easy_q stop -c config.yaml
|
61
|
+
|
62
|
+
# shows a list of queues and current message count for a server
|
63
|
+
% easy_q stats -c config.yaml
|
64
|
+
|
65
|
+
# peek at a few messages from the top or bottom of a queue on the server
|
66
|
+
% easy_q peek queue_name 10 top -c config.yaml
|
67
|
+
% easy_q peek queue_name 10 bottom -c config.yaml
|
68
|
+
|
69
|
+
* Pushing and Popping messages:
|
70
|
+
|
71
|
+
require 'drb'
|
72
|
+
|
73
|
+
o = DRbObject.new(nil,"druby://localhost:2222")
|
74
|
+
|
75
|
+
# push some messages into the queue
|
76
|
+
# :queue => 'name_of_queue' (required)
|
77
|
+
# :ttl => 1000 (seconds to live, not required)
|
78
|
+
# :body => 'hello!!' (the message body, bust be a String class)
|
79
|
+
|
80
|
+
100.times {o.push({:queue => "test", :body => Time.now.to_s})}
|
81
|
+
|
82
|
+
# get messages, returns nil or a hash
|
83
|
+
m = o.pop('text')
|
84
|
+
puts m[:body] if !m.nil?
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'easy_q', 'version')
|
13
|
+
Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].each { |ext| load ext }
|
14
|
+
AUTHOR = "Brandon" # can also be an array of Authors
|
15
|
+
EMAIL = "brandon@loudcity.net"
|
16
|
+
DESCRIPTION = "a simple little message queue"
|
17
|
+
GEM_NAME = "easy_q" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "easy-q" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
|
21
|
+
|
22
|
+
NAME = "easy_q"
|
23
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
24
|
+
VERS = ENV['VERSION'] || (EasyQ::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
25
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
26
|
+
RDOC_OPTS = ['--quiet', '--title', "easy_q documentation",
|
27
|
+
"--opname", "index.html",
|
28
|
+
"--line-numbers",
|
29
|
+
"--main", "README",
|
30
|
+
"--inline-source"]
|
31
|
+
|
32
|
+
class Hoe
|
33
|
+
def extra_deps
|
34
|
+
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate all the Rake tasks
|
39
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
40
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
41
|
+
p.author = AUTHOR
|
42
|
+
p.description = DESCRIPTION
|
43
|
+
p.email = EMAIL
|
44
|
+
p.summary = DESCRIPTION
|
45
|
+
p.url = HOMEPATH
|
46
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
47
|
+
p.test_globs = ["test/**/*_test.rb"]
|
48
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
49
|
+
|
50
|
+
# == Optional
|
51
|
+
#p.changes - A description of the release's latest changes.
|
52
|
+
#p.extra_deps = ["uuid","activerecord"]
|
53
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
54
|
+
p.need_tar = false
|
55
|
+
p.need_zip = false
|
56
|
+
end
|
data/bin/easy_q
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateMessages < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :messages, :id => false do |t|
|
4
|
+
t.column :id, :string, :limit => 36, :null => false
|
5
|
+
t.column :queue, :string, :limit => 25, :null => false
|
6
|
+
t.column :ttl, :integer
|
7
|
+
t.column :body, :text
|
8
|
+
t.column :created_at, :datetime
|
9
|
+
end
|
10
|
+
|
11
|
+
execute "ALTER TABLE messages ADD PRIMARY KEY (id)"
|
12
|
+
add_index :messages, :queue
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :messages
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
require 'drb'
|
4
|
+
require 'optparse'
|
5
|
+
require 'rdoc/usage'
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
module EasyQ
|
9
|
+
class Commands
|
10
|
+
|
11
|
+
USAGE = <<-EOF
|
12
|
+
usage: easy_q command [args] [options]
|
13
|
+
|
14
|
+
To see list of available commands and options
|
15
|
+
easy_q help
|
16
|
+
EOF
|
17
|
+
|
18
|
+
HELP = <<-EOF
|
19
|
+
usage: queues command [args] [options]
|
20
|
+
|
21
|
+
Reliable messaging queue manager, version #{VERSION}
|
22
|
+
|
23
|
+
Available commands:
|
24
|
+
|
25
|
+
help
|
26
|
+
Display this help message.
|
27
|
+
|
28
|
+
start
|
29
|
+
Start the easy_q as a standalone server
|
30
|
+
|
31
|
+
stop
|
32
|
+
Stop the easy_q server.
|
33
|
+
|
34
|
+
empty <queue>
|
35
|
+
Empty (delete all messages from) the named queue.
|
36
|
+
|
37
|
+
install -c config.yml
|
38
|
+
this creates the queue db schema
|
39
|
+
|
40
|
+
Available options:
|
41
|
+
|
42
|
+
-v --version
|
43
|
+
Show version number.
|
44
|
+
|
45
|
+
-c --config <path>
|
46
|
+
Points to the queue manager configuration file.
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
EOF
|
51
|
+
|
52
|
+
class InvalidUsage < Exception #:nodoc:
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
end
|
57
|
+
|
58
|
+
def run
|
59
|
+
#begin
|
60
|
+
config_file = nil
|
61
|
+
opts = OptionParser.new
|
62
|
+
opts.on("-c FILE", "--config FILE", String) { |value| config_file = value }
|
63
|
+
opts.on("-v", "--version") do
|
64
|
+
puts "Reliable messaging queue manager, version #{VERSION}"
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-h", "--help") do
|
69
|
+
puts HELP
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
args = opts.parse(ARGV)
|
74
|
+
|
75
|
+
raise InvalidUsage if args.length < 1
|
76
|
+
|
77
|
+
if config_file.nil?
|
78
|
+
puts "Specify a config file. -c settings.yml"
|
79
|
+
exit
|
80
|
+
end
|
81
|
+
|
82
|
+
@settings = YAML::load_file(config_file)
|
83
|
+
|
84
|
+
case args[0]
|
85
|
+
when 'help'
|
86
|
+
puts HELP
|
87
|
+
when 'install'
|
88
|
+
puts "installing..."
|
89
|
+
do_install
|
90
|
+
when 'start'
|
91
|
+
service = Service.new(@settings)
|
92
|
+
service.start
|
93
|
+
begin
|
94
|
+
while true
|
95
|
+
exit if service.shut_down
|
96
|
+
sleep 3
|
97
|
+
end
|
98
|
+
rescue Interrupt
|
99
|
+
puts exit
|
100
|
+
end
|
101
|
+
when 'stop'
|
102
|
+
service = DRbObject.new(nil, "druby://localhost:#{@settings[:service][:port]}")
|
103
|
+
service.stop
|
104
|
+
when 'stats'
|
105
|
+
service = DRbObject.new(nil, "druby://localhost:#{@settings[:service][:port]}")
|
106
|
+
service.stats.each do |q,c|
|
107
|
+
puts "#{q}: #{c}"
|
108
|
+
end
|
109
|
+
when 'peek'
|
110
|
+
queue = args[1]
|
111
|
+
number = args[2].to_i unless args[2].nil?
|
112
|
+
direction = args[3] unless args[3].nil?
|
113
|
+
service = DRbObject.new(nil, "druby://localhost:#{@settings[:service][:port]}")
|
114
|
+
service.peek(queue,number,direction).each do |m|
|
115
|
+
puts "####"
|
116
|
+
puts "id: #{m[:id]}"
|
117
|
+
puts "queue: #{m[:queue]}"
|
118
|
+
puts "ttl: #{m[:ttl]}"
|
119
|
+
puts "created_at #{m[:created_at]}"
|
120
|
+
puts "body: #{m[:body]}"
|
121
|
+
end
|
122
|
+
when 'empty'
|
123
|
+
queue = args[1]
|
124
|
+
service = DRbObject.new(nil, "druby://localhost:#{@settings[:service][:port]}")
|
125
|
+
service.empty(queue)
|
126
|
+
end
|
127
|
+
#rescue Exception => ex
|
128
|
+
# puts ex
|
129
|
+
#end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def do_install
|
134
|
+
@logger = Logger.new $stderr
|
135
|
+
ActiveRecord::Base.logger = @logger
|
136
|
+
ActiveRecord::Base.colorize_logging = false
|
137
|
+
|
138
|
+
ActiveRecord::Base.establish_connection(@settings)
|
139
|
+
|
140
|
+
ActiveRecord::Migrator.migrate("#{File.dirname(__FILE__)}/../../db/", nil)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'drb'
|
2
|
+
require 'drb/acl'
|
3
|
+
require 'thread'
|
4
|
+
require 'yaml'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'uuid'
|
7
|
+
|
8
|
+
module EasyQ
|
9
|
+
class Service
|
10
|
+
attr_accessor :shut_down
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
@shut_down = false
|
14
|
+
@settings = options
|
15
|
+
@mutex = Mutex.new
|
16
|
+
@logger = Logger.new $stderr
|
17
|
+
@cache = []
|
18
|
+
ActiveRecord::Base.logger = @logger
|
19
|
+
ActiveRecord::Base.colorize_logging = false
|
20
|
+
ActiveRecord::Base.establish_connection(options[:database])
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
@mutex.synchronize do
|
26
|
+
#to do - add code to check if service is already running
|
27
|
+
uri = "druby://#{@settings[:service][:address]}:#{@settings[:service][:port]}"
|
28
|
+
DRb.install_acl(ACL.new(@settings[:service][:acl],ACL::DENY_ALLOW))
|
29
|
+
@drb_service = DRb.start_service(uri, self)
|
30
|
+
puts "service started and listening at #{uri}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
@mutex.synchronize do
|
36
|
+
@shut_down = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def stats
|
41
|
+
return Message.count(:group=>:queue)
|
42
|
+
end
|
43
|
+
|
44
|
+
def peek(queue_name, number = 10, direction = 'top')
|
45
|
+
return_value = []
|
46
|
+
case direction
|
47
|
+
when 'top'
|
48
|
+
results = Message.find(:all, :limit => number, :conditions => ["queue = ?",queue_name])
|
49
|
+
when 'bottom'
|
50
|
+
results = Message.find(:all, :limit => number, :conditions => ["queue = ?",queue_name], :order => :created_at)
|
51
|
+
end
|
52
|
+
results.each do |r|
|
53
|
+
return_value << r.to_hash
|
54
|
+
end
|
55
|
+
return return_value
|
56
|
+
end
|
57
|
+
|
58
|
+
def empty(queue_name)
|
59
|
+
Message.delete_all "queue = '#{queue_name}'"
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def push(options={})
|
64
|
+
raise Exception, "missing options[:queue]" if !options.key?(:queue)
|
65
|
+
raise Exception, "missing options[:body]" if !options.key?(:body)
|
66
|
+
|
67
|
+
if options.key?(:queue)
|
68
|
+
raise Exception, "options[:queue] cannot be empty" if options[:queue].empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
if options.key?(:body)
|
72
|
+
raise Exception, "options[:body] cannot be empty" if options[:body].empty?
|
73
|
+
raise Exception, "options[:body] can only be a String" if options[:body].class.to_s != "String"
|
74
|
+
end
|
75
|
+
|
76
|
+
Message.create(options)
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def pop(queue_name)
|
81
|
+
@mutex.synchronize do
|
82
|
+
message = Message.find(:first,
|
83
|
+
:conditions => ["(queue = ? and (created_at + ttl) > ?) or (queue = ? and ttl is null)",
|
84
|
+
queue_name, Time.now, queue_name]
|
85
|
+
)
|
86
|
+
|
87
|
+
if !message.nil?
|
88
|
+
message.destroy
|
89
|
+
return message.to_hash
|
90
|
+
else
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/easy_q.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.join(File.dirname(__FILE__), 'easy_q/**/*.rb')].sort.each { |lib| require lib }
|