backnob 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ == 0.0.2 2007-09-24
2
+
3
+ * Added singleton workers for processing queues
4
+
1
5
  == 0.0.1 2007-09-18
2
6
 
3
7
  * 1 major enhancement:
data/Manifest.txt CHANGED
@@ -4,6 +4,8 @@ Manifest.txt
4
4
  README.txt
5
5
  Rakefile
6
6
  bin/backnob
7
+ config/hoe.rb
8
+ config/requirements.rb
7
9
  examples/backnob.yml
8
10
  lib/backnob.rb
9
11
  lib/backnob/client.rb
@@ -13,11 +15,32 @@ lib/backnob/server.rb
13
15
  lib/backnob/version.rb
14
16
  lib/backnob/worker.rb
15
17
  lib/backnob_control.rb
18
+ log/debug.log
19
+ plugin/LICENSE
20
+ plugin/README
21
+ plugin/conf/backnob.yml
22
+ plugin/conf/test_worker.rb
23
+ plugin/init.rb
24
+ plugin/lib/backnob_ext.rb
25
+ plugin/tasks/backnob_tasks.rake
26
+ script/destroy
27
+ script/generate
28
+ script/txt2html
16
29
  scripts/txt2html
17
30
  setup.rb
31
+ tasks/deployment.rake
32
+ tasks/environment.rake
33
+ tasks/website.rake
18
34
  test/backnob.yml
35
+ test/single_worker.rb
19
36
  test/test_client.rb
20
37
  test/test_controller.rb
21
38
  test/test_helper.rb
22
39
  test/test_server.rb
40
+ test/test_worker.rb
23
41
  test/worker.rb
42
+ website/index.html
43
+ website/index.txt
44
+ website/javascripts/rounded_corners_lite.inc.js
45
+ website/stylesheets/screen.css
46
+ website/template.rhtml
data/README.txt CHANGED
@@ -1,3 +1,182 @@
1
- README for backnob
2
- ==================
1
+ = README for backnob
3
2
 
3
+ Backnob is a really simple background processor. Once started workers can be added. When created workers will fork off to do their processing, and return a hash of results for later inspection.
4
+
5
+ Server Usage: backnob start|stop|restart|run
6
+ [start:] Start the server
7
+ [stop:] Stop the server
8
+ [restart:] Restart the server
9
+ [run:] Run the server in the foreground
10
+
11
+ Client Usage: backnob create|results
12
+ [create:] Create and start a worker. Returns the worker key
13
+ [results:] Get the results for a worker key.
14
+
15
+ Options are:
16
+ [-h, --help] Show this help message.
17
+ [-c, --configuration=FILE] Load a configuration file
18
+ [-l, --listen=ADDRESS] Set the listen address. Defaults to 127.0.0.1:6444
19
+ [-v, --version] Show the version
20
+
21
+ == Configuration File Options
22
+
23
+ [:workers] Array of worker files or directories
24
+ [:listen] Address to listen on. Default is 127.0.0.1:6444
25
+
26
+
27
+ == Using the library
28
+
29
+ A simple worker example (simple.rb):
30
+
31
+ class SimpleWorker < Backnob::Worker
32
+ def execute
33
+ contents = File.read(@options[:file])
34
+ File.open('simple.txt', 'a') do |file|
35
+ file.write contents
36
+ end
37
+ results(:size, File.size('simple.txt'))
38
+ end
39
+ end
40
+
41
+ Now start up a backnob server instance by running
42
+
43
+ backnob start
44
+
45
+ Using the worker from irb:
46
+
47
+ require 'rubygems'
48
+ require 'backnob/client'
49
+
50
+ client = Backnob::Client.new
51
+ client.add_worker('simple.rb')
52
+ key = client.create_worker('simple', :file => '/tmp/to_be_read.txt')
53
+ while !client.results(key, :finished)
54
+ sleep 1
55
+ end
56
+
57
+ unless client.results(key, :error)
58
+ puts "File length is now: #{client.results(key, :size)}"
59
+ end
60
+
61
+ = Using from Rails
62
+
63
+ Install the plugin:
64
+
65
+ svn co svn://rubyforge.org/var/svn/backnob/trunk/plugin vendor/plugins/backnob
66
+ rake backnob:setup
67
+
68
+ Edit config/backnob.yml as appropriate. Create workers in lib/backnob_workers. Workers must
69
+ specify "rails true" for the rails environment to be required in. It's not a great idea
70
+ to require the environment manually as this will load rails into the server as well as the
71
+ worker.
72
+
73
+ A simple example of a rails worker that imports a CSV file formatted "email, name" into a users table:
74
+
75
+ require 'csv'
76
+
77
+ class ImportWorker < Backnob:Worker
78
+ rails true
79
+
80
+ def execute
81
+ file = @options[:file]
82
+ return unless File.exists? file
83
+
84
+ records = CSV.parse(File.read(file))
85
+
86
+ records.each_with_index do |record, index|
87
+ results(:progress, (index.to_f / records.length) * 100)
88
+
89
+ unless User.find_by_email record[0]
90
+ User.create(:email => record[0],
91
+ :name => record[1],
92
+ :active => true)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ Now in a controller:
99
+
100
+ class ImportController < ApplicationController
101
+ def import
102
+ return unless request.post?
103
+
104
+ file = Tempfile.new('import')
105
+ file.write params[:file].read
106
+ file.close
107
+
108
+ session[:import_key] = backnob.create_worker('import', :file => file.path)
109
+ render :action => 'wait'
110
+ end
111
+
112
+ def ajax_check_import_progress
113
+ results = backnob.results(session[:import_key])
114
+ unless results[:finished]
115
+ render :update do |page|
116
+ page.replace_html 'progress', results[:progress].to_i.to_s
117
+ end
118
+ else
119
+ render :update do |page|
120
+ page.redirect_to :action => 'success.rhtml'
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ Views, import.rhtml, wait.rhtml and success.rhtml:
127
+
128
+ # import.rhtml
129
+ <%- form_tag do %>
130
+ <%= file_field_tag :file %>
131
+ <%= submit_tag %>
132
+ <% end -%>
133
+
134
+
135
+ # wait.rhtml
136
+ <p> Importing <span id='progress'>0</span>% complete, please wait... <%= image_tag('indicator.gif') %> </p>
137
+ <%= update_page_tag do |page|
138
+ page << periodically_call_remote :url => {:action => 'ajax_check_import_progress'}, :frequency => 5
139
+ end %>
140
+
141
+
142
+ # success.rhtml
143
+ <p> File imported successfully </p>
144
+
145
+ = Single Workers
146
+
147
+ A single worker will stay running after execution so you can queue as much work up as you want and it will
148
+ execute each bit of work sequentially. Each bit of work still has its own key and results.
149
+
150
+ An example worker:
151
+
152
+ class SingleWorker < Backnob::Worker
153
+ single true
154
+
155
+ def execute
156
+ @times_run ||= 0
157
+ @times_run += 1
158
+
159
+ work = @options[:work]
160
+ logger.info work
161
+
162
+ results(:run, @times_run)
163
+ end
164
+ end
165
+
166
+ Client:
167
+
168
+ client = Backnob::Client.new
169
+ client.add_worker('single_worker.rb')
170
+ key1 = client.create_worker('single', :work => "hello")
171
+ key2 = client.create_worker('single', :work => "world")
172
+
173
+ while !client.results(key1, :finished)
174
+ sleep 0.25
175
+ end
176
+
177
+ while !client.results(key2, :finished)
178
+ sleep 0.25
179
+ end
180
+
181
+ puts client.results(key1, :run) => 1
182
+ puts client.results(key2, :run) => 2
data/Rakefile CHANGED
@@ -1,125 +1,8 @@
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
-
12
- include FileUtils
13
- require File.join(File.dirname(__FILE__), 'lib', 'backnob', 'version')
14
-
15
- AUTHOR = 'Jeremy Wells' # can also be an array of Authors
16
- EMAIL = "jeremy@boost.co.nz"
17
- DESCRIPTION = "Background workers"
18
- GEM_NAME = 'backnob' # what ppl will type to install your gem
19
-
20
- @config_file = "~/.rubyforge/user-config.yml"
21
- @config = nil
22
- def rubyforge_username
23
- unless @config
24
- begin
25
- @config = YAML.load(File.read(File.expand_path(@config_file)))
26
- rescue
27
- puts <<-EOS
28
- ERROR: No rubyforge config file found: #{@config_file}"
29
- Run 'rubyforge setup' to prepare your env for access to Rubyforge
30
- - See http://newgem.rubyforge.org/rubyforge.html for more details
31
- EOS
32
- exit
33
- end
34
- end
35
- @rubyforge_username ||= @config["username"]
36
- end
37
-
38
- RUBYFORGE_PROJECT = 'backnob' # The unix name for your project
39
- HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
40
- DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
41
-
42
- NAME = "backnob"
43
- REV = nil
44
- # UNCOMMENT IF REQUIRED:
45
- # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
46
- VERS = Backnob::VERSION::STRING + (REV ? ".#{REV}" : "")
47
- CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
48
- RDOC_OPTS = ['--quiet', '--title', 'backnob documentation',
49
- "--opname", "index.html",
50
- "--line-numbers",
51
- "--main", "README",
52
- "--inline-source"]
53
-
54
- class Hoe
55
- def extra_deps
56
- @extra_deps.reject { |x| Array(x).first == 'hoe' }
57
- end
58
- end
59
-
60
- # Generate all the Rake tasks
61
- # Run 'rake -T' to see list of generated tasks (from gem root directory)
62
- hoe = Hoe.new(GEM_NAME, VERS) do |p|
63
- p.author = AUTHOR
64
- p.description = DESCRIPTION
65
- p.email = EMAIL
66
- p.summary = DESCRIPTION
67
- p.url = HOMEPATH
68
- p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
69
- p.test_globs = ["test/**/test_*.rb"]
70
- p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
71
-
72
- # == Optional
73
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
74
- #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
75
- #p.spec_extras = {} # A hash of extra values to set in the gemspec.
76
- end
77
-
78
- CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
79
- PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
80
- hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
81
-
82
- desc 'Generate website files'
83
- task :website_generate do
84
- Dir['website/**/*.txt'].each do |txt|
85
- sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
86
- end
87
- end
88
-
89
- desc 'Upload website files to rubyforge'
90
- task :website_upload do
91
- host = "#{rubyforge_username}@rubyforge.org"
92
- remote_dir = "/var/www/gforge-projects/#{PATH}/"
93
- local_dir = 'website'
94
- sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
95
- end
96
-
97
- desc 'Generate and upload website files'
98
- task :website => [:website_generate, :website_upload, :publish_docs]
99
-
100
- desc 'Release the website and new gem version'
101
- task :deploy => [:check_version, :website, :release] do
102
- puts "Remember to create SVN tag:"
103
- puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
104
- "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
105
- puts "Suggested comment:"
106
- puts "Tagging release #{CHANGES}"
107
- end
108
-
109
- desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
110
- task :local_deploy => [:website_generate, :install_gem]
111
-
112
- task :check_version do
113
- unless ENV['VERSION']
114
- puts 'Must pass a VERSION=x.y.z release version'
115
- exit
116
- end
117
- unless ENV['VERSION'] == VERS
118
- puts "Please update your version.rb to match the release version, currently #{VERS}"
119
- exit
120
- end
121
- end
122
-
123
- task :test do
124
- system('ruby lib/backnob_control.rb stop')
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
5
+
6
+ task :test do
7
+ system('ruby lib/backnob_control.rb stop')
125
8
  end
data/config/hoe.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'backnob/version'
2
+
3
+ AUTHOR = 'Jeremy Wells' # can also be an array of Authors
4
+ EMAIL = "jeremy@boost.co.nz"
5
+ DESCRIPTION = "Simple background worker library"
6
+ GEM_NAME = 'backnob' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'backnob' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "jemmyw"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Backnob::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'backnob documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ p.extra_deps = [['slave', '>= 1.2.1']] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'backnob'
@@ -4,23 +4,36 @@ require 'pathname'
4
4
  module Backnob
5
5
  SERVER_URI = 'druby://127.0.0.1:6444'
6
6
 
7
- class Client
7
+ class Client
8
+ # Create a new client to connect to the server on the passed uri.
9
+ # The uri defaults to druby://127.0.0.1:6444
8
10
  def initialize(uri = nil)
9
11
  @uri = uri || SERVER_URI
10
12
  end
11
13
 
14
+ # Create and start a new worker. Pass the short name of the worker.
15
+ # For example if you have ImportWorker use 'import'
16
+ #
17
+ # The options hash is passed to the worker. The worker key is returned.
18
+ # client.create_worker('import', {:file => '/tmp/import.csv'})
12
19
  def create_worker(name, options = {})
13
20
  server do |s|
14
21
  s.create_worker(name, options)
15
22
  end
16
23
  end
17
24
 
25
+ # Add a worker file or directory. For example:
26
+ # client.add_worker('workers/import_worker.rb')
27
+ # Will add just the import worker file, whereas:
28
+ # client.add_worker('workers')
29
+ # Will add all worker files in the directory workers.
18
30
  def add_worker(file)
19
31
  server do |s|
20
32
  s.add_file(Pathname.new(file).realpath.to_s)
21
33
  end
22
34
  end
23
35
 
36
+ # Test to see if the server is available
24
37
  def available?
25
38
  begin
26
39
  server do |s|
@@ -31,12 +44,23 @@ module Backnob
31
44
  end
32
45
  end
33
46
 
47
+ # Retrieve as results hash for a given worker key. Can also
48
+ # take a key to retrieve just the results on a given key.
34
49
  def results(key, hk = nil)
35
50
  server do |s|
36
51
  s.results(key, hk)
37
52
  end
38
53
  end
39
54
 
55
+ # Return a DRb object reference to the server. If passed
56
+ # a block this will open the connection, yield then close
57
+ # the connection.
58
+ #
59
+ # client.server do |server|
60
+ # server.ping
61
+ # end
62
+ #
63
+ # This will return true
40
64
  def server
41
65
  unless block_given?
42
66
  DRb.start_service
data/lib/backnob/hash.rb CHANGED
@@ -1,4 +1,4 @@
1
- class Hash
1
+ class Hash # :nodoc: all
2
2
  # Destructively convert all keys to symbols.
3
3
  def symbolize_keys!
4
4
  keys.each do |key|
@@ -9,4 +9,14 @@ class Hash
9
9
  end
10
10
  self
11
11
  end
12
+ end
13
+
14
+ class AttrHash < Hash
15
+ def method_missing(symbol, *args)
16
+ if self.has_key?(symbol)
17
+ self[symbol]
18
+ else
19
+ super
20
+ end
21
+ end
12
22
  end
@@ -1,8 +1,8 @@
1
1
  require 'pathname'
2
2
  require File.dirname(__FILE__) + '/hash'
3
3
 
4
- module Backnob
5
- module Options
4
+ module Backnob # :nodoc:
5
+ module Options # :nodoc: all
6
6
  def sanitize!
7
7
  self.symbolize_keys!
8
8
  self[:workers] = [self[:workers]].compact unless self[:workers].is_a?(Array)