backnob 0.0.1 → 0.0.2

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/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)