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 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,8 @@
1
+ begin
2
+ require 'easy_q'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'easy_q'
6
+ end
7
+ EasyQ::Commands.new.run
8
+
@@ -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
@@ -0,0 +1,12 @@
1
+ module EasyQ
2
+ class Message < ActiveRecord::Base
3
+ def before_save
4
+ self.id = UUID.new
5
+ end
6
+
7
+ def to_hash
8
+ return message = {:id => self.id, :queue => self.queue, :created_at => self.created_at, :body => self.body}
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,9 @@
1
+ module EasyQ #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 6
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ 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 }