jobit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ log
19
+ .idea
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1
2
+
3
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jobit.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Victor Yunevich
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ nothing yet
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,19 @@
1
+ module Jobit
2
+ class JobsController < ActionController::Base
3
+
4
+ def index
5
+ job = Jobit::Job.find_by_name(params[:id])
6
+
7
+ if job.nil?
8
+ json_response = {:status => 'not_found'}
9
+ else
10
+ json_response = job.session
11
+ if job.status == 'complete'
12
+ job.destroy if job.status == "complete"
13
+ end
14
+ end
15
+
16
+ render :json => json_response
17
+ end
18
+ end
19
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Jobit::Engine.routes.draw do
2
+ get '/(:id)', :to => 'jobs#index'
3
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/lib/jobit'
2
+
data/jobit.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/jobit/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Victor Yunevich"]
6
+ gem.email = ["v.t.g.m.b.x@gmail.com"]
7
+ gem.description = %q{Process background jobs in queue.}
8
+ gem.summary = %q{Background jobs processing}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "jobit"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Jobit::VERSION
17
+
18
+ gem.add_dependency "daemons"
19
+ gem.add_development_dependency "rspec"
20
+ end
data/lib/jobit.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "jobit/version"
2
+ require File.dirname(__FILE__) + '/jobit/storage'
3
+ require File.dirname(__FILE__) + '/jobit/jobby'
4
+ require File.dirname(__FILE__) + '/jobit/job'
5
+ require File.dirname(__FILE__) + '/jobit/worker'
6
+ require File.dirname(__FILE__) + '/jobit/engine' if defined?(Rails)
7
+ require File.dirname(__FILE__) + '/jobit/railtie' if defined?(Rails)
8
+
@@ -0,0 +1,88 @@
1
+ require 'rubygems'
2
+ require 'daemons'
3
+ require 'optparse'
4
+
5
+ module Jobit
6
+ class Command
7
+ attr_accessor :worker_count
8
+
9
+ def initialize(args)
10
+ @files_to_reopen = []
11
+ @options = {
12
+ :quiet => true,
13
+ :pid_dir => "#{Rails.root}/tmp/pids"
14
+ }
15
+
16
+ @worker_count = 1
17
+ @monitor = false
18
+
19
+ opts = OptionParser.new do |opts|
20
+ opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
21
+
22
+ opts.on('-h', '--help', 'Show this message') do
23
+ puts opts
24
+ exit 1
25
+ end
26
+ opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
27
+ @worker_count = worker_count.to_i rescue 1
28
+ end
29
+ end
30
+ @args = opts.parse!(args)
31
+ end
32
+
33
+ def daemonize
34
+ #Jobit::Worker.backend.before_fork
35
+
36
+ ObjectSpace.each_object(File) do |file|
37
+ @files_to_reopen << file unless file.closed?
38
+ end
39
+
40
+ dir = @options[:pid_dir]
41
+ Dir.mkdir(dir) unless File.exists?(dir)
42
+
43
+ if @worker_count > 1 && @options[:identifier]
44
+ raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
45
+ elsif @worker_count == 1 && @options[:identifier]
46
+ process_name = "jobit.#{@options[:identifier]}"
47
+ run_process(process_name, dir)
48
+ else
49
+ worker_count.times do |worker_index|
50
+ process_name = worker_count == 1 ? "jobit" : "jobit.#{worker_index}"
51
+ run_process(process_name, dir)
52
+ end
53
+ end
54
+ end
55
+
56
+ def run_process(process_name, dir)
57
+ Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args|
58
+ $0 = File.join(@options[:prefix], process_name) if @options[:prefix]
59
+ run process_name
60
+ end
61
+ end
62
+
63
+ def run(worker_name = nil)
64
+ Dir.chdir(Rails.root)
65
+
66
+ # Re-open file handles
67
+ @files_to_reopen.each do |file|
68
+ begin
69
+ file.reopen file.path, "a+"
70
+ file.sync = true
71
+ rescue ::Exception
72
+ end
73
+ end
74
+
75
+ #Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
76
+ #Delayed::Worker.backend.after_fork
77
+
78
+ worker = Jobit::Worker.new(@options)
79
+ #worker.name_prefix = "#{worker_name} "
80
+ worker.start
81
+ rescue => e
82
+ Rails.logger.fatal e
83
+ STDERR.puts e.message
84
+ exit 1
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ module Jobit
2
+ class Engine < Rails::Engine
3
+ isolate_namespace Jobit
4
+ end
5
+ end
data/lib/jobit/job.rb ADDED
@@ -0,0 +1,102 @@
1
+ require 'logger'
2
+ require 'benchmark'
3
+
4
+ module Jobit
5
+
6
+ class Job
7
+
8
+ def self.jobs_path
9
+ if defined?(Rails)
10
+ "#{Rails.root.to_s}/tmp/jobit"
11
+ else
12
+ File.dirname(__FILE__) + "/../../tmp/jobit"
13
+ end
14
+ end
15
+
16
+ def self.worker_name
17
+ "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
18
+ end
19
+
20
+ def self.destroy_all
21
+ Storage.destroy_all
22
+ end
23
+
24
+ def self.destroy_failed
25
+ failed_jobs = Storage.where({:status => 'failed'})
26
+ for job in failed_jobs
27
+ job.destroy
28
+ end
29
+ true
30
+ end
31
+
32
+ def self.logger
33
+ if defined?(Rails)
34
+ Logger.new("#{Rails.root.to_s}/log/jobit.log", shift_age = 7, shift_size = 1048576)
35
+ else
36
+ Logger.new(File.dirname(__FILE__) + "/../../log/jobit.log")
37
+ end
38
+ end
39
+
40
+ # Jobit::Job.all
41
+ # returns array of all jobs
42
+ def self.all
43
+ Storage.all
44
+ end
45
+
46
+ # Jobit::Job.find(11111.111)
47
+ # returns job or nil
48
+ def self.find(id)
49
+ Storage.find(id)
50
+ end
51
+
52
+ # Jobit::Job.find_by_name('name')
53
+ # returns job or nil
54
+ def self.find_by_name(name)
55
+ Storage.find_by_name(name)
56
+ end
57
+
58
+ # Jobit::Job.where({:name => 'name'})
59
+ # returns array of found jobs
60
+ def self.where(search)
61
+ Storage.where(search)
62
+ end
63
+
64
+ # Jobit::Job.add(name, object, *args) {{options}}
65
+ # options:
66
+ # :priority => the job priority (Integer)
67
+ # :run_after => run job after some time from now ex: :run_after => 4.hours
68
+ # :schedule => run job at some time. ex: :schedule_at => "16:00"
69
+ # :repeat => how many times to repeat the job (Integer)
70
+ # :repeat_delay => delay in seconds before next repeat
71
+ def self.add(name, object, *args, &block)
72
+ unless JobitItems.method_defined?(object)
73
+ raise ArgumentError, "Can't add job #{object}. It's not defined in jobs."
74
+ end
75
+
76
+ options = {}
77
+ options = yield if block_given?
78
+
79
+ options[:name] = name
80
+ options[:object] = object
81
+ options[:args] = Marshal.dump(args)
82
+
83
+ Jobby.new(nil, options)
84
+ end
85
+
86
+ def self.work_off(num = 100)
87
+ complete, failed, reissued = 0, 0, 0
88
+ num.times do
89
+ jobs = self.where({:status => 'new'})
90
+ break unless jobs.size > 0
91
+ res = jobs.first.run_job
92
+ complete += res[0]
93
+ failed += res[1]
94
+ reissued += res[2]
95
+ break if $exit
96
+ end
97
+ [complete, failed, reissued]
98
+ end
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,152 @@
1
+ require File.dirname(__FILE__) + '/jobs/statuses'
2
+ require File.dirname(__FILE__) + '/jobs/commands'
3
+
4
+ module Jobit
5
+ class Jobby < Struct.new(
6
+ :id, :name, :object, :args, :message, :progress, :error, :priority, :run_at,
7
+ :schedule, :repeat, :repeat_delay, :created_at, :started_at, :stopped_at,
8
+ :locked_by, :locked_at, :tries, :status, :keep, :failed_at
9
+ )
10
+
11
+ MAX_ATTEMPTS = 25
12
+ MAX_RUN_TIME = 4 # hours
13
+
14
+ include Jobit::Jobs::Statuses
15
+ include Jobit::Jobs::Commands
16
+
17
+ def initialize(job = nil, options = {})
18
+ if job.nil?
19
+ create_new
20
+ if options
21
+ for key, val in options
22
+ self[key] = val
23
+ end
24
+ end
25
+ self.id = "#{priority}.#{id}"
26
+ Jobit::Storage.create(id, self)
27
+ else
28
+ job.each_with_index do |val, index|
29
+ self[index] = val
30
+ end
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ return unless id
36
+ Jobit::Storage.destroy(id)
37
+ clear
38
+ end
39
+
40
+ def process_job(args)
41
+ begin
42
+ logger.info "* [JOB] aquiring lock on #{name}"
43
+ runtime = Benchmark.realtime do
44
+ set_status 'running'
45
+ set_start_time
46
+ self.send(object,*args)
47
+ set_status 'complete'
48
+ set_stop_time
49
+ end
50
+ logger.info "* [JOB] #{name} completed after %.4f" % runtime
51
+ rescue Exception => e
52
+ log_exception(e)
53
+ msg = "#{e.message}\n\n#{e.backtrace.join("\n")}"
54
+ set_error(msg)
55
+ end
56
+ end
57
+
58
+ def run_job(worker_name = 'worker')
59
+ return nil if locked?
60
+ lock!(worker_name)
61
+ self.class.send(:include, JobitItems)
62
+ job_args = Marshal.restore(args)
63
+ num = repeat.to_i == 0 ? 1 : repeat.to_i
64
+ delay = repeat_delay.to_i
65
+ num.times do
66
+ process_job(job_args)
67
+ break if failed? # stop loop if job failed
68
+ increase_tries
69
+ sleep delay if delay > 0
70
+ end
71
+ #p self.class.send(:method_defined?, :perform)
72
+ #p self.class.send(:undef_method, :perform)
73
+ cleanup
74
+ end
75
+
76
+
77
+ def log_exception(error)
78
+ logger.error "* [JOB:#{name}] failed with #{error.class.name}: #{error.message} - #{tries} failed attempts"
79
+ logger.error(error)
80
+ end
81
+
82
+ private
83
+
84
+ def logger
85
+ @logger ||= Jobit::Job.logger
86
+ end
87
+
88
+ def create_new
89
+ self.id = Time.now.to_f
90
+ self.name = name
91
+ self.message = ''
92
+ self.progress = 0
93
+ self.priority = 0
94
+ self.run_at = nil
95
+ self.repeat = 0
96
+ self.repeat_delay = 0
97
+ self.created_at = id
98
+ self.tries = 0
99
+ self.status = 'new'
100
+ self.keep = false
101
+ self
102
+ end
103
+
104
+ def reload
105
+ reloaded_job = Storage.find(id)
106
+ reloaded_job.each_with_index do |val, index|
107
+ self[index] = val
108
+ end
109
+ end
110
+
111
+ def update
112
+ Jobit::Storage.update(id, self)
113
+ end
114
+
115
+ def cleanup
116
+ complete, failed, reissued = 0, 0, 0
117
+ if failed?
118
+ if tries < MAX_ATTEMPTS
119
+ self.tries += 1
120
+ self.run_at = Time.now + (tries ** 4) + 5
121
+ self.status = 'new'
122
+ unlock!
123
+ update
124
+ reissued = 1
125
+ else
126
+ logger.info "* [JOB:#{name}] Removing... Too many tries."
127
+ destroy unless keep?
128
+ failed = 1
129
+ end
130
+ elsif complete?
131
+ if keep?
132
+ set_progress(100)
133
+ set_stop_time
134
+ unlock!
135
+ else
136
+ destroy
137
+ end
138
+ complete = 1
139
+ end
140
+ [complete, failed, reissued]
141
+ end
142
+
143
+ def clear
144
+ options_size = self.size-1
145
+
146
+ for i in (0..options_size)
147
+ self[i]= nil
148
+ end
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,64 @@
1
+ module Jobit
2
+ module Jobs
3
+ module Commands
4
+ def set_start_time
5
+ self.started_at = Time.now.to_f
6
+ update
7
+ end
8
+
9
+ def set_stop_time
10
+ self.stopped_at = Time.now.to_f
11
+ update
12
+ end
13
+
14
+ def set_status(new_status)
15
+ self.status = new_status
16
+ update
17
+ end
18
+
19
+ def set_error(text)
20
+ self.error = text
21
+ self.status = 'failed'
22
+ self.stopped_at = Time.now.to_f
23
+ self.failed_at = stopped_at
24
+ update
25
+ end
26
+
27
+ def set_tries(num)
28
+ self.tries = num
29
+ update
30
+ end
31
+
32
+ def increase_tries
33
+ self.tries += 1
34
+ update
35
+ end
36
+
37
+ def set_progress(num)
38
+ self.progress = num
39
+ update
40
+ end
41
+
42
+ def add_message(text, force = false)
43
+ if self.keep? || force
44
+ self.message += text
45
+ update
46
+ end
47
+ end
48
+
49
+ def unlock!
50
+ self.locked_at= nil
51
+ self.locked_by= nil
52
+ update
53
+ end
54
+
55
+ def lock!(worker_name)
56
+ self.run_at= Time.now.to_f
57
+ self.locked_at= Time.now.to_f
58
+ self.locked_by= worker_name
59
+ update
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ module Jobit
2
+ module Jobs
3
+ module Statuses
4
+ def running?
5
+ status == 'running'
6
+ end
7
+
8
+ def failed?
9
+ status == 'failed'
10
+ end
11
+
12
+ def complete?
13
+ status == 'complete'
14
+ end
15
+
16
+ def stopped?
17
+ status == 'stopped'
18
+ end
19
+
20
+ def new?
21
+ status == 'new'
22
+ end
23
+
24
+ def keep?
25
+ keep
26
+ end
27
+
28
+ def locked?
29
+ locked_at != nil
30
+ end
31
+
32
+ def current_job
33
+ self
34
+ end
35
+
36
+ def session
37
+ {
38
+ :name => name,
39
+ :message => message,
40
+ :progress => progress,
41
+ :error => error,
42
+ :run_at => run_at,
43
+ :created_at => created_at,
44
+ :started_at => started_at,
45
+ :stopped_at =>stopped_at,
46
+ :tries => tries,
47
+ :status => status,
48
+ :failed_at => failed_at
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ require 'jobit'
2
+ require 'rails'
3
+ module Jobit
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :jobit
6
+
7
+ rake_tasks do
8
+ load "tasks/jobit.rake"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+ module Jobit
3
+ class Storage
4
+
5
+ def self.all_files
6
+ Dir.mkdir(Jobit::Job.jobs_path) unless Dir.exist?(Jobit::Job.jobs_path)
7
+ Dir.glob("#{Jobit::Job.jobs_path}/*").find_all { |x| File.file? x }
8
+ end
9
+
10
+ def self.destroy_all
11
+ for file_name in self.all_files
12
+ File.delete(file_name)
13
+ end
14
+ end
15
+
16
+ def self.all
17
+ result = []
18
+
19
+ for file_name in self.all_files
20
+ obj = get_data_from_file(file_name)
21
+ result << Jobby.new(obj)
22
+ end
23
+
24
+ result
25
+ end
26
+
27
+ def self.find(id)
28
+ file_name = self.make_file_name(id)
29
+ return nil unless File.exist?(file_name)
30
+
31
+ obj = get_data_from_file(file_name)
32
+ Jobby.new(obj)
33
+ end
34
+
35
+
36
+ def self.find_by_name(name)
37
+
38
+ result = nil
39
+
40
+ for file_name in self.all_files
41
+ obj = get_data_from_file(file_name)
42
+ next if obj.nil?
43
+ if obj.name == name
44
+ result = Jobby.new(obj)
45
+ break
46
+ end
47
+ end
48
+
49
+ result
50
+ end
51
+
52
+ def self.where(search)
53
+ search_hash = {:name => search}
54
+ search_hash = search if search.is_a?(Hash)
55
+
56
+ result = []
57
+
58
+ for file_name in self.all_files
59
+ obj = get_data_from_file(file_name)
60
+ for key, val in search_hash
61
+ next unless obj.respond_to?(key)
62
+ result << Jobby.new(obj) if obj[key] == val
63
+ end
64
+ end
65
+
66
+ result
67
+ end
68
+
69
+ def self.destroy(file)
70
+ file_name = self.make_file_name(file)
71
+ return false unless File.exist?(file_name)
72
+ File.delete(file_name)
73
+ end
74
+
75
+ def self.create(file, content)
76
+ Dir.mkdir(Jobit::Job.jobs_path) unless Dir.exist?(Jobit::Job.jobs_path)
77
+ file_name = self.make_file_name(file)
78
+ data = Marshal.dump(content.to_hash)
79
+ File.open(file_name, 'w+b') { |f| f.write(data) }
80
+ true
81
+ end
82
+
83
+
84
+ def self.update(file, content)
85
+ file_name = self.make_file_name(file)
86
+ return false unless File.exist?(file_name)
87
+ File.open(file_name, 'w+b') { |f| f.write(Marshal.dump(content.to_hash)) }
88
+ true
89
+ end
90
+
91
+ private
92
+
93
+ def self.get_data_from_file(file_name)
94
+ begin
95
+ file = File.open(file_name)
96
+ obj = Marshal.load(file).to_struct
97
+ rescue
98
+ return nil
99
+ ensure
100
+ file.close
101
+ end
102
+ obj
103
+ end
104
+
105
+ def self.make_file_name(file)
106
+ File.join(Jobit::Job.jobs_path, file.to_s)
107
+ end
108
+
109
+
110
+ end
111
+ end
112
+
113
+ class Hash
114
+ def to_struct
115
+ Struct.new(*keys).new(*values)
116
+ end
117
+ end
118
+
119
+ class Struct
120
+ def to_hash
121
+ Hash[*members.zip(values).flatten]
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ module Jobit
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,49 @@
1
+ module Jobit
2
+ class Worker
3
+ SLEEP = 5
4
+
5
+ def logger
6
+ @logger ||= Jobit::Job.logger
7
+ end
8
+
9
+ def initialize(options={})
10
+ @quiet = options[:quiet]
11
+ #Delayed::Job.min_priority = options[:min_priority] if options.has_key?(:min_priority)
12
+ #Delayed::Job.max_priority = options[:max_priority] if options.has_key?(:max_priority)
13
+ end
14
+
15
+ def start
16
+ #say "*** Starting job worker #{Jobit::Job.worker_name}"
17
+ say "*** Starting job worker #{Jobit::Job.worker_name}"
18
+
19
+ trap('TERM') { say 'Exiting...'; $exit = true }
20
+ trap('INT') { say 'Exiting...'; $exit = true }
21
+
22
+ loop do
23
+ result = nil
24
+
25
+ realtime = Benchmark.realtime do
26
+ result = Jobit::Job.work_off
27
+ end
28
+
29
+ count = result.sum
30
+
31
+ break if $exit
32
+
33
+ if count.zero?
34
+ sleep(SLEEP)
35
+ else
36
+ say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result[1]]
37
+ end
38
+
39
+ break if $exit
40
+ end
41
+ end
42
+
43
+ def say(text)
44
+ puts text unless @quiet
45
+ logger.info text if logger
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'tasks')
@@ -0,0 +1,20 @@
1
+ # Re-definitions are appended to existing tasks
2
+ task :environment
3
+
4
+ namespace :jobit do
5
+ desc "Clear the Jobit job queue."
6
+ task :clear => :environment do
7
+ Jobit::Job.destroy_all
8
+ end
9
+
10
+ desc "Clear the Jobit filed jobs."
11
+ task :clear_failed => :environment do
12
+ Jobit::Job.destroy_failed
13
+ end
14
+
15
+ desc "Start a Jobit worker."
16
+ task :work => :environment do
17
+ p Rails.root
18
+ Jobit::Worker.new().start
19
+ end
20
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe Jobit::Storage do
5
+
6
+ def struct
7
+ struct = Struct.new(:id, :name)
8
+ struct.new(@file_name, @job_name)
9
+ end
10
+
11
+ before(:each) do
12
+ @file_name = Time.now.to_f
13
+ @job_name = "job#1"
14
+ @total_files = Jobit::Storage.all_files.size
15
+ Jobit::Storage.create(@file_name, struct).should eq(true)
16
+ end
17
+
18
+ after(:each) do
19
+ Jobit::Storage.destroy(@file_name)
20
+ end
21
+
22
+ after(:all) do
23
+ begin
24
+ Dir.delete(Jobit.jobs_path)
25
+ rescue
26
+ nil
27
+ end
28
+ end
29
+
30
+ it "creates file with struct id equal to filename" do
31
+ job = Jobit::Storage.find(@file_name)
32
+ job.should be_a(Struct)
33
+ job.id.should eq(@file_name)
34
+ end
35
+
36
+ it ".find returns file value" do
37
+ job = Jobit::Storage.find(@file_name)
38
+ job.should be_a(Struct)
39
+ job.name.should eq(@job_name)
40
+ end
41
+
42
+ it ".all retrieves list of jobs" do
43
+ jobs = Jobit::Storage.all
44
+ jobs.should be_a(Array)
45
+ jobs.size.should eq(@total_files+1)
46
+ end
47
+
48
+
49
+ it ".find_by_name returns file value" do
50
+ job = Jobit::Storage.find_by_name(@job_name)
51
+ job.should be_a(Struct)
52
+ job.name.should eq(@job_name)
53
+ end
54
+
55
+ it ".find_by_name with wrong name returns nil" do
56
+ job = Jobit::Storage.find_by_name('wrong_name')
57
+ job.should eq(nil)
58
+ end
59
+
60
+ it ".where(name) returns file value" do
61
+ jobs = Jobit::Storage.where(@job_name)
62
+ jobs.should be_a(Array)
63
+ jobs.first.name.should eq(@job_name)
64
+ end
65
+
66
+ it ".where(hash) returns file value" do
67
+ jobs = Jobit::Storage.where({:name => @job_name})
68
+ jobs.should be_a(Array)
69
+ jobs.first.name.should eq(@job_name)
70
+ end
71
+
72
+ it ".where(wrong hash key) returns empty array" do
73
+ jobs = Jobit::Storage.where({:name1 => @job_name})
74
+ jobs.should be_a(Array)
75
+ jobs.size.should eq(0)
76
+ end
77
+
78
+ it ".where(wrong hash value) returns empty array" do
79
+ jobs = Jobit::Storage.where({:name => 'wrong_name'})
80
+ jobs.should be_a(Array)
81
+ jobs.size.should eq(0)
82
+ end
83
+
84
+ it "find by name and update" do
85
+ job = Jobit::Storage.find_by_name(@job_name)
86
+ job.name = "new name"
87
+ Jobit::Storage.update(job.id, job)
88
+ job = Jobit::Storage.find(job.id)
89
+ job.should be_a(Struct)
90
+ job.name.should eq('new name')
91
+ end
92
+
93
+ end
@@ -0,0 +1,236 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ module JobitItems
5
+
6
+ def good_job(opt1, opt2)
7
+ set_progress(10)
8
+ unless opt1 == 'val1'
9
+ raise(NoMethodError, 'wrong arguments 0')
10
+ end
11
+ unless opt2 == 'val2'
12
+ raise(NoMethodError, 'wrong arguments 1')
13
+ end
14
+ set_progress(100)
15
+ end
16
+
17
+ def bad_job(text)
18
+ add_message "start"
19
+ set_progress(10)
20
+ add_message "end"
21
+ raise(NoMethodError, "Some dumb error #{text}")
22
+ end
23
+ end
24
+
25
+ describe Jobit do
26
+ describe "Job" do
27
+
28
+ before(:each) do
29
+ @job_name = 'job-1'
30
+ @total_jobs = Jobit::Job.all.size
31
+ @new_job = Jobit::Job.add(@job_name, :good_job, 'val1', 'val2') {
32
+ {:priority => 1}
33
+ }
34
+ end
35
+
36
+ after(:each) do
37
+ @new_job.destroy
38
+ end
39
+
40
+ after(:all) do
41
+ begin
42
+ Dir.delete(Jobit::JOBS_PATH)
43
+ rescue
44
+ nil
45
+ end
46
+ end
47
+
48
+ it "creates new job" do
49
+ @new_job.name.should eq(@job_name)
50
+ end
51
+
52
+ it 'returns nil if not found' do
53
+ job = Jobit::Job.find('123')
54
+ job.should eq(nil)
55
+ end
56
+
57
+ it 'returns job if found by id' do
58
+ job = Jobit::Job.find(@new_job.id)
59
+ job.name.should eq(@job_name)
60
+ end
61
+
62
+ it 'returns job if found_by_name' do
63
+ job = Jobit::Job.find_by_name(@job_name)
64
+ job.name.should eq(@job_name)
65
+ end
66
+
67
+ it 'returns array of all jobs' do
68
+ jobs = Jobit::Job.all
69
+ jobs.should be_a(Array)
70
+ jobs.size.should eq(@total_jobs+1)
71
+ jobs.first.name.should be_a(String)
72
+ end
73
+
74
+ it 'returns array of found jobs' do
75
+ jobs = Jobit::Job.where(:name => @job_name)
76
+ jobs.should be_a(Array)
77
+ jobs.size.should eq(1)
78
+ jobs.first.name.should eq(@job_name)
79
+ end
80
+
81
+ it 'returns empty array if wrong key' do
82
+ jobs = Jobit::Job.where(:name1 => @job_name)
83
+ jobs.should be_a(Array)
84
+ jobs.size.should eq(0)
85
+ end
86
+
87
+ it 'returns empty array if wrong value' do
88
+ jobs = Jobit::Job.where(:name => 'wrong name')
89
+ jobs.should be_a(Array)
90
+ jobs.size.should eq(0)
91
+ end
92
+
93
+ it 'destroys job' do
94
+ @new_job.destroy
95
+ job = Jobit::Job.find_by_name(@job_name)
96
+ job.should eq(nil)
97
+ end
98
+
99
+ it 'update status' do
100
+ @new_job.status.should eq('new')
101
+ @new_job.set_status 'running'
102
+ @new_job.status.should eq('running')
103
+ job = Jobit::Job.find_by_name(@job_name)
104
+ job.status.should eq('running')
105
+ end
106
+
107
+ it 'update error' do
108
+ @new_job.error.should eq(nil)
109
+ @new_job.set_error 'my error'
110
+ @new_job.error.should eq('my error')
111
+ job = Jobit::Job.find_by_name(@job_name)
112
+ job.error.should eq('my error')
113
+ end
114
+
115
+ it 'update error' do
116
+ @new_job.tries.should eq(0)
117
+ @new_job.set_tries 1
118
+ @new_job.tries.should eq(1)
119
+ job = Jobit::Job.find_by_name(@job_name)
120
+ job.tries.should eq(1)
121
+ end
122
+
123
+ it 'update progress' do
124
+ @new_job.progress.should eq(0)
125
+ @new_job.set_progress 10
126
+ @new_job.progress.should eq(10)
127
+ job = Jobit::Job.find_by_name(@job_name)
128
+ job.progress.should eq(10)
129
+ end
130
+
131
+ it 'adds message' do
132
+ @new_job.message.should eq('')
133
+ @new_job.add_message('hello',true)
134
+ @new_job.message.should eq('hello')
135
+ job = Jobit::Job.find_by_name(@job_name)
136
+ job.message.should eq('hello')
137
+ @new_job.add_message(' there',true)
138
+ @new_job.message.should eq('hello there')
139
+ job = Jobit::Job.find_by_name(@job_name)
140
+ job.message.should eq('hello there')
141
+ end
142
+
143
+ it 'running good job' do
144
+ @new_job.run_job
145
+ job = Jobit::Job.find_by_name(@job_name)
146
+ job.should eq(nil)
147
+ end
148
+
149
+ it 'running good job 5 times' do
150
+ job = Jobit::Job.add('good_job', :good_job, 'val1', 'val2') { {
151
+ :priority => 0,
152
+ :repeat => 5
153
+ } }
154
+ job.keep?.should eq(false)
155
+ job.run_job
156
+ job = Jobit::Job.find_by_name('good_job')
157
+ job.should eq(nil)
158
+ end
159
+
160
+ it 'running good job 5 times and keep file' do
161
+ job = Jobit::Job.add('good_job_keep', :good_job, 'val1', 'val2') { {
162
+ :priority => 0,
163
+ :repeat => 5,
164
+ :keep => true
165
+ } }
166
+ job.run_job
167
+ job = Jobit::Job.find_by_name('good_job_keep')
168
+ job.tries.should eq(5)
169
+ job.run_at.should_not eq(nil)
170
+ job.destroy
171
+ end
172
+
173
+ it 'running bad job' do
174
+ job = Jobit::Job.add('bad_job', :bad_job, 'val1') {
175
+ {
176
+ :priority => 0,
177
+ :keep => true
178
+ }
179
+ }
180
+
181
+ job.run_job
182
+ job = Jobit::Job.find_by_name('bad_job')
183
+ job.tries.should eq(1)
184
+ job.message.should eq('startend')
185
+ job.status.should eq('new')
186
+ job.progress.should eq(10)
187
+ job.destroy
188
+ end
189
+
190
+ it 'running bad job 5 times should run it just once' do
191
+ job = Jobit::Job.add('bad_job_5', :bad_job, 'val1') {
192
+ {
193
+ :priority => 0,
194
+ :repeat => 5
195
+ }
196
+ }
197
+
198
+ job.run_job
199
+ job = Jobit::Job.find_by_name('bad_job_5')
200
+ job.tries.should eq(1)
201
+ job.status.should eq('new')
202
+ job.progress.should eq(10)
203
+ job.destroy
204
+ end
205
+
206
+ it 'work_off test' do
207
+ @new_job.destroy
208
+ for i in (0..5)
209
+ Jobit::Job.add("work_off_#{i}", :good_job, 'val1', 'val2') { {
210
+ :priority => rand(6),
211
+ :keep => true
212
+ } }
213
+ end
214
+ Jobit::Job.add('work_off_bad', :bad_job, 'val1') { {
215
+ :priority => 2,
216
+ :keep => true
217
+ } }
218
+
219
+ Jobit::Job.work_off
220
+ jobs = []
221
+ for i in (0..5)
222
+ job = Jobit::Job.find_by_name("work_off_#{i}")
223
+ job.status.should eq('complete'), " #{job}"
224
+ job.destroy
225
+ job.id.should eq(nil)
226
+ end
227
+
228
+ job_failed = Jobit::Job.find_by_name('work_off_bad')
229
+ job_failed.tries.should eq(25)
230
+ job_failed.status.should eq('failed')
231
+ job_failed.destroy
232
+
233
+ end
234
+
235
+ end
236
+ end
@@ -0,0 +1 @@
1
+ require 'jobit'
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jobit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Victor Yunevich
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-03 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: daemons
16
+ requirement: &2162428060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2162428060
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2162427640 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2162427640
36
+ description: Process background jobs in queue.
37
+ email:
38
+ - v.t.g.m.b.x@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - .rspec
45
+ - CHANGELOG.md
46
+ - Gemfile
47
+ - LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - app/controllers/jobit/jobs_controller.rb
51
+ - config/routes.rb
52
+ - init.rb
53
+ - jobit.gemspec
54
+ - lib/jobit.rb
55
+ - lib/jobit/command.rb
56
+ - lib/jobit/engine.rb
57
+ - lib/jobit/job.rb
58
+ - lib/jobit/jobby.rb
59
+ - lib/jobit/jobs/commands.rb
60
+ - lib/jobit/jobs/statuses.rb
61
+ - lib/jobit/railtie.rb
62
+ - lib/jobit/storage.rb
63
+ - lib/jobit/version.rb
64
+ - lib/jobit/worker.rb
65
+ - lib/tasks/jobit.rake
66
+ - lib/tasks/tasks.rb
67
+ - spec/jobit/storage_spec.rb
68
+ - spec/jobit_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage: ''
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.6
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Background jobs processing
94
+ test_files:
95
+ - spec/jobit/storage_spec.rb
96
+ - spec/jobit_spec.rb
97
+ - spec/spec_helper.rb