easy_q 0.6.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.
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 }