genki-background_fu 1.0.8.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 ADDED
@@ -0,0 +1,49 @@
1
+ === 1.0.8.1 / 2008-09-7
2
+
3
+ * Suppress the warning on Rails-2.1.1.
4
+
5
+ === Master / 2008-07-31
6
+
7
+ * Make it clear that generated views are meant to be used when bonus features are enabled. [Trevor Turk, ncr]
8
+
9
+ === 1.0.8 / 2008-07-30
10
+
11
+ * Reverted back to "text" column for storing job results so it works on PostgreSQL. [Trevor Turk, ncr]
12
+
13
+ === 1.0.7 / 2008-07-16
14
+
15
+ * Updated docs and fixed a bug regarding started_at and Time.now.utc (the lack of #utc in some places resulted in jobs not starting). [Jeff Berg, ncr]
16
+ * Fixed and refactored scaffolding. [ncr]
17
+ * Removed deprecated Dependencies usage. [ncr]
18
+ * Updated links in footer. [ncr]
19
+
20
+ === 1.0.6 / 2008-07-03
21
+
22
+ * Updated docs to mention config/routes.rb. [Joe Goldwasser, ncr]
23
+
24
+ === 1.0.5 / 2008-06-02
25
+
26
+ * Fixed enqueue method (now properly returns enqueued job). [Jake Yoon, ncr]
27
+ * Fixed background.rb generator so it doens't blow up if Bonus Features are disabled. [Jake Yoon, ncr]
28
+
29
+ === 1.0.4 / 2008-05-28
30
+
31
+ * GitHub is now generating a gem. [ncr]
32
+ * Making sure that it will work as a plugin when installed as a gem. [ncr]
33
+
34
+ === 1.0.2 / 2008-05-27
35
+
36
+ * Fiddling with gemspec to make it work on GitHub. [ncr]
37
+
38
+ === 1.0.1 / 2008-05-27
39
+
40
+ * Updaed README with install instructions. [ncr]
41
+
42
+ === 1.0.0 / 2008-05-27
43
+
44
+ * BackgroundFu is now also a gem. You should check Rails 2.1 and the super sexy gems vendoring features!
45
+
46
+ * Updated README.txt so now it looks like those from gems. [ncr]
47
+ * Previous changes before becoming a gem:
48
+ * 2008-05-12 Added excessive logging and changed default priority to 0 (Negative priorities allowed). [ncr]
49
+ * 2008-05-11 Added three columns to Job model: priority (execute jobs in priority descending order), start_at (execute jobs after start_at), lock_version (ensure job is executed by a single daemon). [ncr]
data/Manifest.txt ADDED
@@ -0,0 +1,30 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ background-fu.gemspec
6
+ generators/background/USAGE
7
+ generators/background/background_generator.rb
8
+ generators/background/templates/background.rb
9
+ generators/background/templates/background_ctl
10
+ generators/background/templates/daemons
11
+ generators/background/templates/daemons.yml
12
+ generators/background/templates/example_monitored_worker.rb
13
+ generators/background/templates/example_worker.rb
14
+ generators/background/templates/migration.rb
15
+ generators/background/templates/scaffold/_form.html.erb
16
+ generators/background/templates/scaffold/_job.html.erb
17
+ generators/background/templates/scaffold/_job_deleted.html.erb
18
+ generators/background/templates/scaffold/_progress_indicator.html.erb
19
+ generators/background/templates/scaffold/application_controller.rb
20
+ generators/background/templates/scaffold/background_fu.css
21
+ generators/background/templates/scaffold/index.html.erb
22
+ generators/background/templates/scaffold/jobs.html.erb
23
+ generators/background/templates/scaffold/jobs_controller.rb
24
+ generators/background/templates/scaffold/jobs_helper.rb
25
+ generators/background/templates/scaffold/new.html.erb
26
+ lib/background_fu.rb
27
+ lib/background_fu/worker_monitoring.rb
28
+ lib/job.rb
29
+ lib/job/bonus_features.rb
30
+ rails/init.rb
data/README.txt ADDED
@@ -0,0 +1,176 @@
1
+ = BackgroundFu
2
+
3
+ * http://github.com/ncr/background-fu
4
+ * git://github.com/ncr/background-fu.git
5
+ * http://trix.lighthouseapp.com/projects/9809-backgroundfu
6
+ * ncr@trix.pl
7
+ * http://trix.pl
8
+
9
+ == DESCRIPTION:
10
+
11
+ Background tasks in Ruby On Rails made dead simple.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Running long background tasks outside of request-response cycle.
16
+ * Very easy to setup and fun to use (See examples below).
17
+ * Clean and straightforward approach (database-based priority queue).
18
+ * Uses database table (migration included) to store jobs reliably.
19
+ * Capistrano tasks included.
20
+ * Generators with migrations and example views included (to be used with concurrency enabled).
21
+ * Multiple worker daemons available.
22
+ * Easy to deploy in distributed environments.
23
+ * Enables prioritizing and simple scheduling.
24
+ * Optional worker monitoring (good for AJAX progress bars).
25
+ * Proven its stability and reliability in production use.
26
+
27
+ == SYNOPSIS:
28
+
29
+ ruby ./script/generate background
30
+ rake db:migrate
31
+
32
+ # to run in production mode use RAILS_ENV=production ruby ./script/daemons start
33
+ ruby ./script/daemons start
34
+
35
+ # then try in console:
36
+
37
+ job_id = Job.enqueue!(ExampleWorker, :add, 1, 2).id
38
+
39
+ # after few seconds when background daemon completes the job
40
+
41
+ Job.find(job_id).result # result of the job should equal 3
42
+
43
+ If you want to use default generated views, update your config/routes.rb:
44
+
45
+ map.namespace "admin" do |admin|
46
+ admin.resources :jobs
47
+ end
48
+
49
+ Then you can point your browser to http://localhost:3000/admin/jobs
50
+
51
+ == EXAMPLES:
52
+
53
+ # In lib/workers/example_worker.rb:
54
+
55
+ # Simple, non-monitored worker.
56
+ class ExampleWorker
57
+
58
+ def add(a, b)
59
+ a + b
60
+ end
61
+
62
+ end
63
+
64
+ # In lib/workers/example_monitored_worker.rb:
65
+
66
+ # Remeber to include BackgroundFu::WorkerMonitoring.
67
+ class ExampleMonitoredWorker
68
+
69
+ include BackgroundFu::WorkerMonitoring
70
+
71
+ def long_and_monitored
72
+ my_progress = 0
73
+
74
+ record_progress(my_progress)
75
+
76
+ while(my_progress < 100)
77
+ my_progress += 1
78
+ record_progress(my_progress)
79
+ sleep 1
80
+ end
81
+
82
+ record_progress(100)
83
+ end
84
+
85
+ end
86
+
87
+ # In a controller:
88
+
89
+ def create
90
+ session[:job_id] = Job.enqueue!(ExampleWorker, :add, 1, 2).id
91
+ # or try the monitored worker: session[:job_id] = Job.enqueue!(ExampleMonitoredWorker, :long_and_monitored)
92
+ end
93
+
94
+ def show
95
+ @job = Job.find(session[:job_id])
96
+ @result = @job.result if @job.finished?
97
+ # or check progress if your worker is monitored: @progress = @job.progress
98
+ end
99
+
100
+ def index
101
+ @jobs = Job.find(:all)
102
+ end
103
+
104
+ def destroy
105
+ Job.find(session[:job_id]).destroy
106
+ end
107
+
108
+ == HANDY CAPISTRANO TASKS:
109
+
110
+ namespace :deploy do
111
+
112
+ desc "Run this after every successful deployment"
113
+ task :after_default do
114
+ restart_background_fu
115
+ end
116
+
117
+ end
118
+
119
+ desc "Restart BackgroundFu daemon"
120
+ task :restart_background_fu do
121
+ run "RAILS_ENV=production ruby #{current_path}/script/daemons stop"
122
+ run "RAILS_ENV=production ruby #{current_path}/script/daemons start"
123
+ end
124
+
125
+ == BONUS FEATURES:
126
+
127
+ There are bonus features available if you set
128
+ config.active_record.allow_concurrency = true
129
+ in your environment.
130
+
131
+ These features are:
132
+ * monitoring progress (perfect for ajax progress bars)
133
+ * stopping a running worker in a merciful way.
134
+
135
+ Generated admin views are meant to be used only when bonus features (concurrency) are enabled.
136
+
137
+ Read the code (short and easy) to discover them.
138
+
139
+ == REQUIREMENTS:
140
+
141
+ * rails
142
+ * daemons
143
+
144
+ == INSTALL:
145
+
146
+ * As a Rails plugin: ./script/plugin install git://github.com/ncr/background-fu.git
147
+ * As as a gem to be 'vendorized' starting from Rails 2.1: refer to documentation on rake gems:unpack:dependencies.
148
+
149
+ == CONTRIBUTING:
150
+
151
+ If you want to help improve this plugin, feel free to contact me. Fork the project on GitHub, implement a feature, send me a pull request.
152
+
153
+ == LICENSE:
154
+
155
+ (The MIT License)
156
+
157
+ Copyright (c) Jacek Becela
158
+
159
+ Permission is hereby granted, free of charge, to any person obtaining
160
+ a copy of this software and associated documentation files (the
161
+ 'Software'), to deal in the Software without restriction, including
162
+ without limitation the rights to use, copy, modify, merge, publish,
163
+ distribute, sublicense, and/or sell copies of the Software, and to
164
+ permit persons to whom the Software is furnished to do so, subject to
165
+ the following conditions:
166
+
167
+ The above copyright notice and this permission notice shall be
168
+ included in all copies or substantial portions of the Software.
169
+
170
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
171
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
172
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
173
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
174
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
175
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
176
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/background_fu.rb'
4
+
5
+ Hoe.new('background-fu', BackgroundFu::VERSION) do |p|
6
+ p.rubyforge_name = 'background-fu'
7
+ p.developer('Jacek Becela', 'jacek.becela@gmail.com')
8
+ end
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{background_fu}
3
+ s.version = "1.0.8.2"
4
+
5
+ s.specification_version = 2 if s.respond_to? :specification_version=
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jacek Becela"]
9
+ s.date = %q{2008-05-28}
10
+ s.description = %q{Background tasks in Ruby On Rails made dead simple.}
11
+ s.email = ["jacek.becela@gmail.com"]
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
13
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "background-fu.gemspec", "generators/background/USAGE", "generators/background/background_generator.rb", "generators/background/templates/background.rb", "generators/background/templates/background_ctl", "generators/background/templates/daemons", "generators/background/templates/daemons.yml", "generators/background/templates/example_monitored_worker.rb", "generators/background/templates/example_worker.rb", "generators/background/templates/migration.rb", "generators/background/templates/scaffold/_job.html.erb", "generators/background/templates/scaffold/_job_deleted.html.erb", "generators/background/templates/scaffold/_progress_indicator.html.erb", "generators/background/templates/scaffold/background_fu.css", "generators/background/templates/scaffold/index.html.erb", "generators/background/templates/scaffold/jobs.html.erb", "generators/background/templates/scaffold/jobs_controller.rb", "generators/background/templates/scaffold/jobs_helper.rb", "lib/background_fu.rb", "lib/background_fu/worker_monitoring.rb", "lib/job.rb", "lib/job/bonus_features.rb", "rails/init.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/ncr/background-fu}
16
+ s.rdoc_options = ["--main", "README.txt"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{background-fu}
19
+ s.rubygems_version = %q{1.1.1}
20
+ s.summary = %q{Background tasks in Ruby On Rails made dead simple.}
21
+
22
+ s.add_dependency(%q<daemons>, [">= 1.0.10"])
23
+ end
@@ -0,0 +1,3 @@
1
+ Examples:
2
+ ./script/generate background
3
+
@@ -0,0 +1,61 @@
1
+ class BackgroundGenerator < Rails::Generator::Base
2
+
3
+ default_options :skip_migration => false, :skip_scaffold => false, :skip_examples => false
4
+
5
+ def manifest
6
+ record do |m|
7
+ unless options[:skip_migration]
8
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "create_jobs"
9
+ end
10
+
11
+ m.directory "lib/daemons"
12
+ m.file 'background.rb', 'lib/daemons/background.rb'
13
+ m.file 'background_ctl', 'lib/daemons/background_ctl'
14
+
15
+ m.directory "lib/workers"
16
+
17
+ unless options[:skip_examples]
18
+ m.file 'example_worker.rb', 'lib/workers/example_worker.rb'
19
+ m.file 'example_monitored_worker.rb', 'lib/workers/example_monitored_worker.rb'
20
+ end
21
+
22
+ m.file 'daemons.yml', 'config/daemons.yml'
23
+ m.file 'daemons', 'script/daemons'
24
+
25
+ unless options[:skip_scaffold]
26
+ m.directory 'app/controllers/admin'
27
+ m.directory 'app/helpers/admin'
28
+ m.directory 'app/views/admin/jobs'
29
+ m.directory 'app/views/layouts/admin'
30
+
31
+ m.file 'scaffold/application_controller.rb', 'app/controllers/admin/application_controller.rb'
32
+ m.file 'scaffold/jobs_controller.rb', 'app/controllers/admin/jobs_controller.rb'
33
+ m.file 'scaffold/jobs_helper.rb', 'app/helpers/admin/jobs_helper.rb'
34
+
35
+ m.file 'scaffold/index.html.erb', 'app/views/admin/jobs/index.html.erb'
36
+ m.file 'scaffold/new.html.erb', 'app/views/admin/jobs/new.html.erb'
37
+ m.file 'scaffold/_form.html.erb', 'app/views/admin/jobs/_form.html.erb'
38
+ m.file 'scaffold/_job.html.erb', 'app/views/admin/jobs/_job.html.erb'
39
+ m.file 'scaffold/_job_deleted.html.erb', 'app/views/admin/jobs/_job_deleted.html.erb'
40
+ m.file 'scaffold/_progress_indicator.html.erb', 'app/views/admin/jobs/_progress_indicator.html.erb'
41
+
42
+ m.file 'scaffold/jobs.html.erb', 'app/views/layouts/admin/jobs.html.erb'
43
+
44
+ m.file "scaffold/background_fu.css", "public/stylesheets/background_fu.css"
45
+
46
+
47
+ end
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ def add_options!(opt)
54
+ opt.separator ''
55
+ opt.separator 'Options:'
56
+ opt.on("--skip-migration", "Don't generate migration file for this model") { |v| options[:skip_migration] = v }
57
+ opt.on("--skip-scaffold", "Don't generate scaffold controller and views for this model") { |v| options[:skip_scaffold] = v }
58
+ opt.on("--skip-examples", "Don't generate example workers") { |v| options[:skip_examples] = v }
59
+ end
60
+
61
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + "/../../config/environment"
4
+
5
+ Signal.trap("TERM") { exit }
6
+
7
+ if Job.included_modules.include?(Job::BonusFeatures)
8
+ RAILS_DEFAULT_LOGGER.info("BackgroundFu: Starting daemon (bonus features enabled).")
9
+ else
10
+ RAILS_DEFAULT_LOGGER.info("BackgroundFu: Starting daemon (bonus features disabled).")
11
+ end
12
+
13
+ loop do
14
+ if job = Job.find(:first, :conditions => ["state='pending' and start_at <= ?", Time.now.utc], :order => "priority desc, start_at asc")
15
+ job.get_done!
16
+ else
17
+ RAILS_DEFAULT_LOGGER.info("BackgroundFu: Waiting for jobs...")
18
+ sleep 5
19
+ end
20
+
21
+ Job.destroy_all(["state='finished' and updated_at < ?", 1.week.ago])
22
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'daemons'
4
+ require 'yaml'
5
+ require 'erb'
6
+
7
+ class Hash
8
+ def with_symbols!
9
+ self.keys.each{|key| self[key.to_s.to_sym] = self[key] }; self
10
+ end
11
+ end
12
+
13
+ options = YAML.load(
14
+ ERB.new(
15
+ IO.read(
16
+ File.dirname(__FILE__) + "/../../config/daemons.yml"
17
+ )).result).with_symbols!
18
+ options[:dir_mode] = options[:dir_mode].to_sym
19
+
20
+ Daemons.run File.dirname(__FILE__) + '/background.rb', options
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ Dir[File.dirname(__FILE__) + "/../lib/daemons/*_ctl"].each {|f| `nice ruby #{f} #{ARGV.first}`}
@@ -0,0 +1,5 @@
1
+ dir_mode: script
2
+ dir: ../../log
3
+ multiple: false
4
+ backtrace: true
5
+ monitor: true
@@ -0,0 +1,20 @@
1
+ class ExampleMonitoredWorker
2
+
3
+ # After including worker monitoring you can invoke record_progress() method.
4
+ include BackgroundFu::WorkerMonitoring
5
+
6
+ def long_and_monitored
7
+ my_progress = 0
8
+
9
+ record_progress(my_progress)
10
+
11
+ while(my_progress < 100)
12
+ my_progress += 1
13
+ record_progress(my_progress)
14
+ sleep 1
15
+ end
16
+
17
+ record_progress(100)
18
+ end
19
+
20
+ end
@@ -0,0 +1,8 @@
1
+ # Simple, non-monitored worker.
2
+ class ExampleWorker
3
+
4
+ def add(a, b)
5
+ a + b
6
+ end
7
+
8
+ end
@@ -0,0 +1,30 @@
1
+ class CreateJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :jobs do |t|
4
+ t.string :worker_class
5
+ t.string :worker_method
6
+
7
+ t.text :args
8
+ t.text :result
9
+
10
+ t.integer :priority
11
+
12
+ t.integer :progress
13
+ t.string :state
14
+
15
+ t.integer :lock_version, :default => 0
16
+
17
+ t.timestamp :start_at
18
+ t.timestamp :started_at
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :jobs, :state
23
+ add_index :jobs, :start_at
24
+ add_index :jobs, :priority
25
+ end
26
+
27
+ def self.down
28
+ drop_table :jobs
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ <td class="checkbox">
2
+ <% case job.state
3
+ when "running" %>
4
+ <%= link_to_remote("Stop", :url => admin_job_path(job, :command => "stop"), :method => :put, :confirm => "Are you sure?") %>
5
+ <% when "failed", "stopped" %>
6
+ <%= link_to_remote("Restart", :url => admin_job_path(job, :command => "restart"), :method => :put, :confirm => "Are you sure?") %> <br />
7
+ <%= link_to_remote("Delete", :url => admin_job_path(job), :method => :delete, :confirm => "Are you sure?") %>
8
+ <% when "finished", "pending" %>
9
+ <%= link_to_remote("Delete", :url => admin_job_path(job), :method => :delete, :confirm => "Are you sure?") %><br />
10
+ <%= link_to("Download result", formatted_admin_job_path(job, :txt)) if downloadable?(job) %>
11
+ <% end %>
12
+ <br />
13
+ </td>
14
+ <td class="progress">
15
+ <%= render :partial => "progress_indicator",
16
+ :locals => {
17
+ :progress => job.progress,
18
+ :elapsed => job.elapsed,
19
+ :estimated => job.estimated,
20
+ :state => job.state
21
+ }
22
+ %>
23
+ </td>
24
+ <td class="info">
25
+ <h2><%= job.worker_class %>.new.<%= job.worker_method %>( <strong><%= h truncate(job.args.map(&:inspect).join(", "), 255) %></strong> )</h2>
26
+ <p>
27
+ started at: <strong><%= time_ago_in_words_with_customization(job.started_at) %></strong>,
28
+ created at: <strong><%= time_ago_in_words_with_customization(job.created_at) %></strong>,
29
+ updated at: <strong><%= time_ago_in_words_with_customization(job.updated_at) %></strong>,<br />
30
+ state: <strong><%= job.state %></strong>,
31
+ progress: <strong><%= job.progress || "n/a" %></strong>,
32
+ result: <strong><%=h truncate(job.result.inspect, 255) %></strong>
33
+ </p>
34
+ </td>
35
+ <script type="text/javascript">
36
+ //<![CDATA[
37
+ job_state_<%= dom_id(job) %> = "<%= job.state %>";
38
+ //]]>
39
+ </script>
@@ -0,0 +1,5 @@
1
+ <script type="text/javascript">
2
+ //<![CDATA[
3
+ job_state_<%= dom_id(job_deleted) %> = "deleted";
4
+ //]]>
5
+ </script>
@@ -0,0 +1,5 @@
1
+ <div class="progress-indicator">
2
+ <span class="bar <%= state %>" style="width: <%= progress %>%;"></span>
3
+ <span class="label elapsed"><%= seconds_in_short(elapsed) if elapsed.to_i > 0 %></span>
4
+ <span class="label estimated"><%= seconds_in_short(estimated) if estimated.to_i > 0 %></span>
5
+ </div>
@@ -0,0 +1,23 @@
1
+ .progress-indicator { height: 6em; width: 30em; position: relative; border: 1px solid #536070; background-color: #A8B1BC; }
2
+ .progress-indicator span { position: absolute; height: 100%;}
3
+ .progress-indicator .bar.running { background-color: #1687C2; }
4
+ .progress-indicator .bar.finished { background-color: #00AF00; }
5
+ .progress-indicator .bar.stopping { background-color: orange; }
6
+ .progress-indicator .bar.stopped { background-color: #E90000; }
7
+ .progress-indicator .bar.failed { background-color: #C90000; }
8
+
9
+ .progress-indicator .label { width: 100%; font-size: 1.5em; line-height: 1.5em; font-weight: bold; font-family: "Myriad Pro", sans-serif; color: white; }
10
+ .progress-indicator .elapsed { margin-left: 2%; }
11
+ .progress-indicator .estimated { margin-left: 85%; }
12
+
13
+ table.jobs td { border: none; vertical-align: top; color: #555; border-bottom: 1px solid white; }
14
+ table.jobs tr.even td{ background-color: #f6f6f6; }
15
+ table.jobs tr.odd td { background-color: #efefef; }
16
+ table.jobs td h2 { margin-top: 0; }
17
+ table.jobs strong { color: #000; font-weight: bold; }
18
+
19
+ form.job input { margin: 0 0 1em 0; }
20
+ form.job textarea { width: 50%; }
21
+ form.job input.worker-class { font-size: 2em; font-weight: bold; width: 33%; }
22
+ form.job input.worker-method { font-size: 1.5em; font-weight: bold; width: 33%; }
23
+ form.job input.button { font-size: 1.2em; font-weight: bold; }
@@ -0,0 +1,19 @@
1
+ <div>
2
+ <%= render :partial => "form", :locals => { :job => @job } %>
3
+ </div>
4
+
5
+ <hr style="visibility:hidden; margin: 20px auto" />
6
+
7
+ <table class="jobs">
8
+ <% @jobs.each do |job| %>
9
+ <tr class="job <%= cycle(:odd, :even) %>" id="<%= dom_id(job) %>">
10
+ <%= render :partial => "job", :locals => {:job => job} %>
11
+ </tr>
12
+ <%= periodically_call_remote(
13
+ :url => admin_job_path(job),
14
+ :method => "get",
15
+ :interval => 5,
16
+ :condition => "/^(running|pending|stopping)$/.match(job_state_#{dom_id(job)})")
17
+ %>
18
+ <% end %>
19
+ </table>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3
+ <head>
4
+ <title>Background Jobs - BackgroundFu</title>
5
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.0/build/reset-fonts-grids/reset-fonts-grids.css">
6
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.0/build/base/base-min.css">
7
+ <style type="text/css">
8
+ #custom-doc { width: 90%; min-width: 250px; }
9
+ </style>
10
+ <%= javascript_include_tag :defaults %>
11
+ <%= stylesheet_link_tag 'background_fu' %>
12
+ </head>
13
+ <body>
14
+ <div id="custom-doc" class="yui-t7">
15
+ <div id="hd"><h1>BackgroundFu Jobs</h1></div>
16
+ <div id="bd"><div class="yui-g">
17
+ <%= yield %>
18
+ </div></div>
19
+ <div id="ft" style="text-align: center;"><a href="http://github.com/ncr/background-fu/">BackgroundFu</a> Running Background Jobs Made Dead Simple <a href="http://en.trix.pl">Jacek Becela</a> 2008 </div>
20
+ </div>
21
+ </body>
22
+ </html>
@@ -0,0 +1,60 @@
1
+ class Admin::JobsController < Admin::ApplicationController
2
+
3
+ def index
4
+ @jobs = Job.find(:all, :order => "id desc")
5
+ end
6
+
7
+ def show
8
+ @job = Job.find(params[:id])
9
+
10
+ respond_to do |format|
11
+ format.text do
12
+ if @job.result.respond_to?(:join)
13
+ send_data @job.result.join("\n"), :type => "text/plain", :disposition => "attachment"
14
+ else
15
+ render :nothing => true, :status => 404
16
+ end
17
+ end
18
+ format.js do
19
+ render :update do |page|
20
+ page[@job].replace_html :partial => "job"
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def create
27
+ params[:job][:args] = params[:job][:args].split("\n").map(&:strip)
28
+ @job = Job.new(params[:job])
29
+
30
+ if @job.save
31
+ redirect_to admin_jobs_path
32
+ else
33
+ render :action => "new"
34
+ end
35
+ end
36
+
37
+ def update
38
+ @job = Job.find(params[:id])
39
+
40
+ if params[:command] == "stop"
41
+ @job.stop!
42
+ elsif params[:command] == "restart"
43
+ @job.restart!
44
+ end
45
+
46
+ render :update do |page|
47
+ page[@job].replace_html :partial => "job"
48
+ end
49
+ end
50
+
51
+ def destroy
52
+ @job = Job.find(params[:id])
53
+ @job.destroy
54
+
55
+ render :update do |page|
56
+ page[@job].replace_html :partial => "job_deleted", :object => @job
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,32 @@
1
+ module Admin::JobsHelper
2
+
3
+ def seconds_in_short(seconds)
4
+ seconds = seconds.to_i
5
+ minutes = seconds / 60
6
+ hours = seconds / (60 * 60)
7
+ days = seconds / (60 * 60 * 24)
8
+
9
+ if days > 0
10
+ return "#{days} d"
11
+ elsif hours > 0
12
+ return "#{hours} h"
13
+ elsif minutes > 0
14
+ return "#{minutes} m"
15
+ else
16
+ return "#{seconds} s"
17
+ end
18
+ end
19
+
20
+ def time_ago_in_words_with_customization(time)
21
+ if time
22
+ "#{time_ago_in_words(time)} ago"
23
+ else
24
+ "n/a"
25
+ end
26
+ end
27
+
28
+ def downloadable?(job)
29
+ job.result.respond_to?(:join)
30
+ end
31
+
32
+ end
@@ -0,0 +1,5 @@
1
+ module BackgroundFu
2
+
3
+ VERSION = "1.0.8"
4
+
5
+ end
@@ -0,0 +1,19 @@
1
+ # Include it in your workers to enable progress monitoring and stopping jobs.
2
+ module BackgroundFu::WorkerMonitoring
3
+
4
+ # In most cases you will have some loop which will execute known (m) times.
5
+ # Every time the loop iterates you increment a counter (n).
6
+ # The formula to get progress in percents is: 100 * n / m.
7
+ # If you invoke this method with second argument, then this is calculated for you.
8
+ # You also can omit second argument and progress will be passed directly to db.
9
+ def record_progress(progress_or_iteration, iterations_count = nil)
10
+ if iterations_count.to_i > 0
11
+ @progress = ((progress_or_iteration.to_f / iterations_count) * 100).to_i
12
+ else
13
+ @progress = progress_or_iteration.to_i
14
+ end
15
+
16
+ throw(:stopping, true) if @stopping
17
+ end
18
+
19
+ end
data/lib/job.rb ADDED
@@ -0,0 +1,113 @@
1
+ # Example:
2
+ #
3
+ # job = Job.enqueue!(MyWorker, :my_method, "my_arg_1", "my_arg_2")
4
+ class Job < ActiveRecord::Base
5
+
6
+ cattr_accessor :states
7
+ self.states = %w(pending running finished failed)
8
+
9
+ serialize :args, Array
10
+ serialize :result
11
+
12
+ before_create :setup_state, :setup_priority, :setup_start_at
13
+ validates_presence_of :worker_class, :worker_method
14
+
15
+ attr_readonly :worker_class, :worker_method, :args
16
+
17
+ def self.enqueue!(worker_class, worker_method, *args)
18
+ job = create!(
19
+ :worker_class => worker_class.to_s,
20
+ :worker_method => worker_method.to_s,
21
+ :args => args
22
+ )
23
+
24
+ logger.info("BackgroundFu: Job enqueued. Job(id: #{job.id}, worker: #{worker_class}, method: #{worker_method}, argc: #{args.size}).")
25
+
26
+ job
27
+ end
28
+
29
+ # Invoked by a background daemon.
30
+ def get_done!
31
+ initialize_worker
32
+ invoke_worker
33
+ rescue Exception => e
34
+ rescue_worker(e)
35
+ ensure
36
+ ensure_worker
37
+ end
38
+
39
+ # Restart a failed job.
40
+ def restart!
41
+ if failed?
42
+ update_attributes!(
43
+ :result => nil,
44
+ :progress => nil,
45
+ :started_at => nil,
46
+ :state => "pending"
47
+ )
48
+ logger.info("BackgroundFu: Job restarted. Job(id: #{id}).")
49
+ end
50
+ end
51
+
52
+ def initialize_worker
53
+ update_attributes!(:started_at => Time.now.utc, :state => "running")
54
+ @worker = worker_class.constantize.new
55
+ logger.info("BackgroundFu: Job initialized. Job(id: #{id}).")
56
+ end
57
+
58
+ def invoke_worker
59
+ self.result = @worker.send!(worker_method, *args)
60
+ self.state = "finished"
61
+ logger.info("BackgroundFu: Job finished. Job(id: #{id}).")
62
+ end
63
+
64
+ def rescue_worker(exception)
65
+ self.result = [exception.message, exception.backtrace.join("\n")].join("\n\n")
66
+ self.state = "failed"
67
+ logger.info("BackgroundFu: Job failed. Job(id: #{id}).")
68
+ end
69
+
70
+ def ensure_worker
71
+ self.progress = @worker.instance_variable_get("@progress")
72
+ save!
73
+ rescue StaleObjectError
74
+ # Ignore this exception as its only purpose is
75
+ # not allowing multiple daemons execute the same job.
76
+ logger.info("BackgroundFu: Race condition handled (It's OK). Job(id: #{id}).")
77
+ end
78
+
79
+ def self.generate_state_helpers
80
+ states.each do |state_name|
81
+ define_method("#{state_name}?") do
82
+ state == state_name
83
+ end
84
+
85
+ # Job.running => array of running jobs, etc.
86
+ self.class.send!(:define_method, state_name) do
87
+ find_all_by_state(state_name, :order => "id desc")
88
+ end
89
+ end
90
+ end
91
+ generate_state_helpers
92
+
93
+ def setup_state
94
+ return unless state.blank?
95
+
96
+ self.state = "pending"
97
+ end
98
+
99
+ # Default priority is 0. Jobs will be executed in descending priority order (negative priorities allowed).
100
+ def setup_priority
101
+ return unless priority.blank?
102
+
103
+ self.priority = 0
104
+ end
105
+
106
+ # Job will be executed after this timestamp.
107
+ def setup_start_at
108
+ return unless start_at.blank?
109
+
110
+ self.start_at = Time.now.utc
111
+ end
112
+
113
+ end
@@ -0,0 +1,99 @@
1
+ module Job::BonusFeatures
2
+
3
+ def self.included(base)
4
+ base.states += %w(stopping stopped)
5
+ base.generate_state_helpers
6
+
7
+ base.alias_method_chain :invoke_worker, :threads
8
+ base.alias_method_chain :ensure_worker, :threads
9
+ base.alias_method_chain :restart!, :threads
10
+ end
11
+
12
+ def invoke_worker_with_threads
13
+ monitor_worker
14
+
15
+ res = catch(:stopping) do
16
+ invoke_worker_without_threads; nil
17
+ end
18
+
19
+ self.state = res ? "stopped" : "finished"
20
+ end
21
+
22
+ def ensure_worker_with_threads
23
+ ensure_worker_without_threads
24
+ cleanup_after_threads
25
+ end
26
+
27
+ # The record_progress() method becomes available when your worker class includes
28
+ # Background::WorkerMonitoring.
29
+ #
30
+ # Every time worker invokes record_progress() is a possible stopping place.
31
+ #
32
+ # How it works:
33
+ # 1. invoke job.stop! to set a state (stopping) in a db
34
+ # 2. Monitoring thread picks up the state change from db
35
+ # and sets @stopping to true in the worker.
36
+ # 3. The worker invokes a register_progress() somewhere during execution.
37
+ # 4. The record_progress() method throws :stopping symbol if @stopping == true
38
+ # 5. The job catches the :stopping symbol and reacts upon it.
39
+ # 6. The job is stopped in a merciful way. No one gets harmed.
40
+ def stop!
41
+ if running?
42
+ update_attribute(:state, "stopping")
43
+ logger.info("BackgroundFu: Stopping job. Job(id: #{id}).")
44
+ end
45
+ end
46
+
47
+ # Overwritten because of new "stopped" state.
48
+ def restart_with_threads!
49
+ if stopped? || failed?
50
+ update_attributes!(
51
+ :result => nil,
52
+ :progress => nil,
53
+ :started_at => nil,
54
+ :state => "pending"
55
+ )
56
+ logger.info("BackgroundFu: Restarting job. Job(id: #{id}).")
57
+ end
58
+ end
59
+
60
+ # This is the only place where multi-threading
61
+ # is used in the plugin and is completely optional.
62
+ def monitor_worker
63
+ Thread.new do
64
+ # 1. running? - check if not failed or finished.
65
+ # 2. !Job.find(id).stopping? - check if someone ordered stopping the job.
66
+ while(running? && !Job.find(id).stopping?)
67
+ current_progress = @worker.instance_variable_get("@progress")
68
+
69
+ if current_progress == progress
70
+ sleep 5
71
+ else
72
+ update_attribute(:progress, current_progress)
73
+ sleep 1
74
+ end
75
+ end
76
+
77
+ # If someone ordered stopping a job we infrom the worker that it should stop.
78
+ if(Job.find(id).stopping?)
79
+ @worker.instance_variable_set("@stopping", true)
80
+ end
81
+ end
82
+ logger.info("BackgroundFu: Job monitoring started. Job(id: #{id}).")
83
+ end
84
+
85
+ # Closes database connections left after finished threads.
86
+ def cleanup_after_threads
87
+ ActiveRecord::Base.verify_active_connections!
88
+ end
89
+
90
+ def elapsed
91
+ (updated_at.to_f - started_at.to_f).to_i if !pending?
92
+ end
93
+
94
+ # seconds to go, based on estimated and progress
95
+ def estimated
96
+ ((elapsed * 100) / progress) - elapsed if running? && (1..99).include?(progress.to_i)
97
+ end
98
+
99
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'background_fu'
2
+ require 'background_fu/worker_monitoring'
3
+ require 'job'
4
+ require 'job/bonus_features'
5
+
6
+ # ActiveSupport::Dependencies will be here after edge becomes stable.
7
+ ActiveSupport::Dependencies.load_paths << "#{RAILS_ROOT}/lib/workers"
8
+
9
+ if ActiveRecord::Base.allow_concurrency
10
+ Job.send!(:include, Job::BonusFeatures)
11
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: genki-background_fu
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.8.2
5
+ platform: ruby
6
+ authors:
7
+ - Jacek Becela
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: daemons
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.10
23
+ version:
24
+ description: Background tasks in Ruby On Rails made dead simple.
25
+ email:
26
+ - jacek.becela@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ files:
36
+ - History.txt
37
+ - Manifest.txt
38
+ - README.txt
39
+ - Rakefile
40
+ - background-fu.gemspec
41
+ - generators/background/USAGE
42
+ - generators/background/background_generator.rb
43
+ - generators/background/templates/background.rb
44
+ - generators/background/templates/background_ctl
45
+ - generators/background/templates/daemons
46
+ - generators/background/templates/daemons.yml
47
+ - generators/background/templates/example_monitored_worker.rb
48
+ - generators/background/templates/example_worker.rb
49
+ - generators/background/templates/migration.rb
50
+ - generators/background/templates/scaffold/_job.html.erb
51
+ - generators/background/templates/scaffold/_job_deleted.html.erb
52
+ - generators/background/templates/scaffold/_progress_indicator.html.erb
53
+ - generators/background/templates/scaffold/background_fu.css
54
+ - generators/background/templates/scaffold/index.html.erb
55
+ - generators/background/templates/scaffold/jobs.html.erb
56
+ - generators/background/templates/scaffold/jobs_controller.rb
57
+ - generators/background/templates/scaffold/jobs_helper.rb
58
+ - lib/background_fu.rb
59
+ - lib/background_fu/worker_monitoring.rb
60
+ - lib/job.rb
61
+ - lib/job/bonus_features.rb
62
+ - rails/init.rb
63
+ has_rdoc: true
64
+ homepage: http://github.com/ncr/background-fu
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --main
68
+ - README.txt
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project: background-fu
86
+ rubygems_version: 1.2.0
87
+ signing_key:
88
+ specification_version: 2
89
+ summary: Background tasks in Ruby On Rails made dead simple.
90
+ test_files: []
91
+