sidekiq-cron 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.travis.yml +18 -0
- data/Changes.md +8 -0
- data/Gemfile +30 -0
- data/LICENSE.txt +20 -0
- data/README.md +152 -0
- data/Rakefile +61 -0
- data/VERSION +1 -0
- data/config.ru +14 -0
- data/examples/web-cron-ui.png +0 -0
- data/lib/sidekiq-cron.rb +4 -0
- data/lib/sidekiq/cron.rb +29 -0
- data/lib/sidekiq/cron/job.rb +397 -0
- data/lib/sidekiq/cron/locales/en.yml +7 -0
- data/lib/sidekiq/cron/poller.rb +65 -0
- data/lib/sidekiq/cron/views/cron.slim +47 -0
- data/lib/sidekiq/cron/web_extension.rb +75 -0
- data/lib/sidekiq/launcher.rb +46 -0
- data/sidekiq-cron.gemspec +104 -0
- data/test/test_helper.rb +67 -0
- data/test/unit/job_test.rb +529 -0
- data/test/unit/poller_test.rb +157 -0
- data/test/unit/web_extesion_test.rb +92 -0
- metadata +313 -0
data/.document
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.3
|
4
|
+
- jruby-19mode
|
5
|
+
- rbx-19mode
|
6
|
+
- 2.0.0
|
7
|
+
branches:
|
8
|
+
only:
|
9
|
+
- master
|
10
|
+
notifications:
|
11
|
+
email:
|
12
|
+
recipients:
|
13
|
+
- ondrej@bartas.cz
|
14
|
+
matrix:
|
15
|
+
allow_failures:
|
16
|
+
- rvm: jruby-19mode
|
17
|
+
- rvm: rbx-19mode
|
18
|
+
- rvm: 2.0.0
|
data/Changes.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem "sidekiq", ">= 2.13.1"
|
4
|
+
gem 'parse-cron', '>= 0.1.2'
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem "bundler"
|
8
|
+
gem "simplecov"
|
9
|
+
|
10
|
+
gem 'shoulda-context'
|
11
|
+
gem "turn"
|
12
|
+
|
13
|
+
gem 'rack'
|
14
|
+
gem 'rack-test'
|
15
|
+
|
16
|
+
gem "jeweler", "~> 1.8.3"
|
17
|
+
|
18
|
+
gem "sdoc" # sdoc -N .
|
19
|
+
|
20
|
+
gem "slim"
|
21
|
+
gem "sinatra"
|
22
|
+
|
23
|
+
gem 'mocha'
|
24
|
+
gem 'coveralls'
|
25
|
+
|
26
|
+
gem "shotgun"
|
27
|
+
|
28
|
+
# gem 'guard'
|
29
|
+
# gem 'guard-minitest'
|
30
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Ondrej Bartas
|
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 PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL 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,152 @@
|
|
1
|
+
Sidekiq-Cron
|
2
|
+
============
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/ondrejbartas/sidekiq-cron.png?branch=master)](https://travis-ci.org/ondrejbartas/sidekiq-cron) [![Coverage Status](https://coveralls.io/repos/ondrejbartas/sidekiq-cron/badge.png?branch=master)](https://coveralls.io/r/ondrejbartas/sidekiq-cron?branch=master)
|
5
|
+
|
6
|
+
Add-on for [Sidekiq](http://sidekiq.org)
|
7
|
+
|
8
|
+
Allows you to schedule recurring jobs for sidekiq workers using cron notation _* * * * *_.
|
9
|
+
|
10
|
+
Requirements
|
11
|
+
-----------------
|
12
|
+
|
13
|
+
- Redis 2.4 or greater is required.
|
14
|
+
- Sidekiq 2.13.1 or grater is required.
|
15
|
+
|
16
|
+
|
17
|
+
Installation
|
18
|
+
------------
|
19
|
+
|
20
|
+
$ gem install sidekiq-cron
|
21
|
+
|
22
|
+
or add to your Gemfile
|
23
|
+
|
24
|
+
gem "sidekiq-cron", "~> 0.1.0"
|
25
|
+
|
26
|
+
|
27
|
+
Getting Started
|
28
|
+
-----------------
|
29
|
+
|
30
|
+
|
31
|
+
If you are not using Rails you need to add `require 'sidekiq-cron'` somewhere after `require 'sidekiq'`.
|
32
|
+
|
33
|
+
_Job properties_:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
{
|
37
|
+
'name' => 'name_of_job', #must be uniq!
|
38
|
+
'cron' => '1 * * * *',
|
39
|
+
'klass' => 'MyClass',
|
40
|
+
#OPTIONAL
|
41
|
+
'queue' => 'name of queue',
|
42
|
+
'args' => '[Array or Hash] of arguments hich will be passed to perform method'
|
43
|
+
}
|
44
|
+
```
|
45
|
+
|
46
|
+
#### Adding Cron job:
|
47
|
+
```ruby
|
48
|
+
|
49
|
+
class HardWorker
|
50
|
+
include Sidekiq::Worker
|
51
|
+
def perform(name, count)
|
52
|
+
# do something
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Sidekiq::Cron::Job.create( name: 'Hard worker - every 5min', cron: '*/5 * * * *', klass: 'HardWorker')
|
57
|
+
# => true
|
58
|
+
```
|
59
|
+
|
60
|
+
`create` method will return only true/false if job was saved or not.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
job = Sidekiq::Cron::Job.new( name: 'Hard worker - every 5min', cron: '*/5 * * * *', klass: 'HardWorker')
|
64
|
+
|
65
|
+
if job.valid?
|
66
|
+
job.save
|
67
|
+
else
|
68
|
+
puts job.errors
|
69
|
+
end
|
70
|
+
|
71
|
+
#or simple
|
72
|
+
|
73
|
+
unless job.save
|
74
|
+
puts job.errors #will return array of errors
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
#### Finding jobs
|
79
|
+
```ruby
|
80
|
+
#return array of all jobs
|
81
|
+
Sidekiq::Cron::Job.all
|
82
|
+
|
83
|
+
#return one job by its uniq name - case sensitive
|
84
|
+
Sidekiq::Cron::Job.find "Job Name"
|
85
|
+
|
86
|
+
#return one job by its uniq name - you can use hash with 'name' key
|
87
|
+
Sidekiq::Cron::Job.find name: "Job Name"
|
88
|
+
|
89
|
+
#if job can't be found nil is returned
|
90
|
+
```
|
91
|
+
|
92
|
+
#### Destroy jobs:
|
93
|
+
```ruby
|
94
|
+
#destroys all jobs
|
95
|
+
Sidekiq::Cron::Job.destroy_all!
|
96
|
+
|
97
|
+
#destroy job by its name
|
98
|
+
Sidekiq::Cron::Job.destroy "Job Name"
|
99
|
+
|
100
|
+
#destroy founded job
|
101
|
+
Sidekiq::Cron::Job.find('Job name').destroy
|
102
|
+
```
|
103
|
+
|
104
|
+
#### Work with job:
|
105
|
+
```ruby
|
106
|
+
job = Sidekiq::Cron::Job.find('Job name')
|
107
|
+
|
108
|
+
#disable cron scheduling
|
109
|
+
job.disable!
|
110
|
+
|
111
|
+
#enable cron scheduling
|
112
|
+
job.enable!
|
113
|
+
|
114
|
+
#get status of job:
|
115
|
+
job.status
|
116
|
+
# => enabled/disabled
|
117
|
+
|
118
|
+
#enqueue job right now!
|
119
|
+
job.enque!
|
120
|
+
```
|
121
|
+
|
122
|
+
How to start scheduling?
|
123
|
+
Just start sidekiq workers by:
|
124
|
+
|
125
|
+
sidekiq
|
126
|
+
|
127
|
+
### Web Ui for Cron Jobs
|
128
|
+
|
129
|
+
If you are using sidekiq web ui and you would like to add cron josb to web too,
|
130
|
+
add `require 'sidekiq-cron'` after `require 'sidekiq/web'`.
|
131
|
+
By this you will get:
|
132
|
+
![Web UI](https://github.com/ondrejbartas/sidekiq-cron/raw/master/examples/web-cron-ui.png)
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
## Contributing to sidekiq-cron
|
137
|
+
|
138
|
+
|
139
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
140
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
141
|
+
* Fork the project.
|
142
|
+
* Start a feature/bugfix branch.
|
143
|
+
* Commit and push until you are happy with your contribution.
|
144
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
145
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
146
|
+
|
147
|
+
|
148
|
+
## Copyright
|
149
|
+
|
150
|
+
Copyright (c) 2013 Ondrej Bartas. See LICENSE.txt for
|
151
|
+
further details.
|
152
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "sidekiq-cron"
|
18
|
+
gem.homepage = "http://github.com/ondrejbartas/sidekiq-cron"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Sidekiq Cron helps to add repeated scheduled jobs}
|
21
|
+
gem.description = %Q{Enables to set jobs to be run in specified time (using CRON notation)}
|
22
|
+
gem.email = "ondrej@bartas.cz"
|
23
|
+
gem.authors = ["Ondrej Bartas"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
#TESTING
|
29
|
+
|
30
|
+
task :doc do
|
31
|
+
system 'sdoc -N .'
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'rake/testtask'
|
35
|
+
task :default => :test
|
36
|
+
|
37
|
+
Rake::TestTask.new(:test) do |t|
|
38
|
+
t.test_files = FileList['test/functional/**/*_test.rb', 'test/unit/**/*_test.rb','test/integration/**/*_test.rb']
|
39
|
+
t.warning = false
|
40
|
+
t.verbose = false
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :test do
|
44
|
+
Rake::TestTask.new(:unit) do |t|
|
45
|
+
t.test_files = FileList['test/unit/**/*_test.rb']
|
46
|
+
t.warning = false
|
47
|
+
t.verbose = false
|
48
|
+
end
|
49
|
+
|
50
|
+
Rake::TestTask.new(:functional) do |t|
|
51
|
+
t.test_files = FileList['test/functional/**/*_test.rb']
|
52
|
+
t.warning = false
|
53
|
+
t.verbose = false
|
54
|
+
end
|
55
|
+
|
56
|
+
Rake::TestTask.new(:integration) do |t|
|
57
|
+
t.test_files = FileList['test/integration/**/*_test.rb']
|
58
|
+
t.warning = false
|
59
|
+
t.verbose = false
|
60
|
+
end
|
61
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/config.ru
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
|
3
|
+
Sidekiq.configure_client do |config|
|
4
|
+
config.redis = { :size => 1 }
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'sidekiq/web'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
10
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
11
|
+
|
12
|
+
require 'sidekiq-cron'
|
13
|
+
|
14
|
+
run Sidekiq::Web
|
Binary file
|
data/lib/sidekiq-cron.rb
ADDED
data/lib/sidekiq/cron.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
begin
|
2
|
+
require "sidekiq/web"
|
3
|
+
rescue LoadError
|
4
|
+
# client-only usage
|
5
|
+
end
|
6
|
+
|
7
|
+
require "sidekiq/cron/job"
|
8
|
+
require "sidekiq/cron/web_extension"
|
9
|
+
|
10
|
+
#require poller only if celluloid is defined
|
11
|
+
if defined?(Celluloid)
|
12
|
+
require "sidekiq/cron/poller"
|
13
|
+
end
|
14
|
+
|
15
|
+
module Sidekiq
|
16
|
+
module Cron
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if defined?(Sidekiq::Web)
|
21
|
+
Sidekiq::Web.register Sidekiq::Cron::WebExtension
|
22
|
+
|
23
|
+
if Sidekiq::Web.tabs.is_a?(Array)
|
24
|
+
# For sidekiq < 2.5
|
25
|
+
Sidekiq::Web.tabs << "cron"
|
26
|
+
else
|
27
|
+
Sidekiq::Web.tabs["Cron"] = "cron"
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,397 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'sidekiq/util'
|
3
|
+
require 'sidekiq/actor'
|
4
|
+
require 'parse-cron'
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
module Cron
|
8
|
+
|
9
|
+
class Job
|
10
|
+
include Util
|
11
|
+
extend Util
|
12
|
+
|
13
|
+
#how long we would like to store informations about previous enqueues
|
14
|
+
REMEMBER_THRESHOLD = 24 * 60 * 60
|
15
|
+
|
16
|
+
#crucial part of whole enquing job
|
17
|
+
def should_enque? time
|
18
|
+
out = false
|
19
|
+
Sidekiq.redis do |conn|
|
20
|
+
out = (
|
21
|
+
status == "enabled" &&
|
22
|
+
@last_run_time < last_time(time) &&
|
23
|
+
conn.zadd(job_enqueued_key, time.to_f.to_s, formated_last_time(time) )
|
24
|
+
)
|
25
|
+
end
|
26
|
+
out
|
27
|
+
end
|
28
|
+
|
29
|
+
# remove previous informations about run times
|
30
|
+
# this will clear redis and make sure that redis will
|
31
|
+
# not overflow with memory
|
32
|
+
def remove_previous_enques time
|
33
|
+
Sidekiq.redis do |conn|
|
34
|
+
conn.zremrangebyscore(job_enqueued_key, 0, "(#{(time.to_f - REMEMBER_THRESHOLD).to_s}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#test if job should be enqued If yes add it to queue
|
39
|
+
def test_and_enque_for_time! time
|
40
|
+
#should this job be enqued?
|
41
|
+
if should_enque?(time)
|
42
|
+
enque!
|
43
|
+
|
44
|
+
remove_previous_enques(time)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#enque cron job to queue
|
49
|
+
def enque! time = Time.now
|
50
|
+
@last_run_time = time
|
51
|
+
|
52
|
+
Sidekiq::Client.push(@message.is_a?(String) ? Sidekiq.load_json(@message) : @message)
|
53
|
+
|
54
|
+
save
|
55
|
+
logger.debug { "enqueued #{@name}: #{@message}" }
|
56
|
+
end
|
57
|
+
|
58
|
+
# load cron jobs from Hash
|
59
|
+
# input structure should look like:
|
60
|
+
# {
|
61
|
+
# 'name_of_job' => {
|
62
|
+
# 'class' => 'MyClass',
|
63
|
+
# 'cron' => '1 * * * *',
|
64
|
+
# 'args' => '(OPTIONAL) [Array or Hash]'
|
65
|
+
# },
|
66
|
+
# 'My super iber cool job' => {
|
67
|
+
# 'class' => 'SecondClass',
|
68
|
+
# 'cron' => '*/5 * * * *'
|
69
|
+
# }
|
70
|
+
# }
|
71
|
+
#
|
72
|
+
def self.load_from_hash hash
|
73
|
+
array = hash.inject([]) do |out,(key, job)|
|
74
|
+
job['name'] = key
|
75
|
+
out << job
|
76
|
+
end
|
77
|
+
load_from_array array
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# load cron jobs from Array
|
82
|
+
# input structure should look like:
|
83
|
+
# [
|
84
|
+
# {
|
85
|
+
# 'name' => 'name_of_job',
|
86
|
+
# 'class' => 'MyClass',
|
87
|
+
# 'cron' => '1 * * * *',
|
88
|
+
# 'args' => '(OPTIONAL) [Array or Hash]'
|
89
|
+
# },
|
90
|
+
# {
|
91
|
+
# 'name' => 'Cool Job for Second Class',
|
92
|
+
# 'class' => 'SecondClass',
|
93
|
+
# 'cron' => '*/5 * * * *'
|
94
|
+
# }
|
95
|
+
# ]
|
96
|
+
#
|
97
|
+
def self.load_from_array array
|
98
|
+
errors = {}
|
99
|
+
array.each do |job_data|
|
100
|
+
job = new(job_data)
|
101
|
+
errors[job.name] = job.errors unless job.save
|
102
|
+
end
|
103
|
+
errors
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# get all cron jobs
|
108
|
+
def self.all
|
109
|
+
out = []
|
110
|
+
Sidekiq.redis do |conn|
|
111
|
+
out = conn.smembers(jobs_key).collect do |key|
|
112
|
+
if conn.exists key
|
113
|
+
Job.new conn.hgetall(key)
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
out.select{|j| !j.nil? }
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.count
|
123
|
+
out = 0
|
124
|
+
Sidekiq.redis do |conn|
|
125
|
+
out = conn.scard(jobs_key)
|
126
|
+
end
|
127
|
+
out
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.find name
|
131
|
+
#if name is hash try to get name from it
|
132
|
+
name = name[:name] || name['name'] if name.is_a?(Hash)
|
133
|
+
|
134
|
+
output = nil
|
135
|
+
Sidekiq.redis do |conn|
|
136
|
+
if exists? name
|
137
|
+
output = Job.new conn.hgetall( redis_key(name) )
|
138
|
+
end
|
139
|
+
end
|
140
|
+
output
|
141
|
+
end
|
142
|
+
|
143
|
+
# create new instance of cron job
|
144
|
+
def self.create hash
|
145
|
+
new(hash).save
|
146
|
+
end
|
147
|
+
|
148
|
+
#destroy job by name
|
149
|
+
def self.destroy name
|
150
|
+
#if name is hash try to get name from it
|
151
|
+
name = name[:name] || name['name'] if name.is_a?(Hash)
|
152
|
+
|
153
|
+
if job = find(name)
|
154
|
+
job.destroy
|
155
|
+
else
|
156
|
+
false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
attr_accessor :name, :cron, :klass, :args, :message
|
161
|
+
attr_reader :last_run_time
|
162
|
+
|
163
|
+
def initialize input_args = {}
|
164
|
+
args = input_args.stringify_keys
|
165
|
+
|
166
|
+
@name = args["name"]
|
167
|
+
@cron = args["cron"]
|
168
|
+
|
169
|
+
#get class from klass or class
|
170
|
+
@klass = args["klass"] || args["class"]
|
171
|
+
|
172
|
+
#set status of job
|
173
|
+
@status = args['status'] || status_from_redis
|
174
|
+
|
175
|
+
#set last run time
|
176
|
+
@last_run_time = Time.parse(args['last_run_time'].to_s) rescue Time.now
|
177
|
+
|
178
|
+
#get right arguments for job
|
179
|
+
@args = args["args"].nil? ? [] : (args["args"].is_a?(Array) ? args["args"] : [ args["args"] ])
|
180
|
+
|
181
|
+
if args["message"]
|
182
|
+
@message = args["message"]
|
183
|
+
elsif @klass
|
184
|
+
message_data = {
|
185
|
+
"class" => @klass.to_s,
|
186
|
+
"args" => @args,
|
187
|
+
}
|
188
|
+
|
189
|
+
#get right data for message
|
190
|
+
#only if message wasn't specified before
|
191
|
+
message_data = case @klass
|
192
|
+
when Class
|
193
|
+
@klass.get_sidekiq_options.merge(message_data)
|
194
|
+
when String
|
195
|
+
begin
|
196
|
+
@klass.constantize.get_sidekiq_options.merge(message_data)
|
197
|
+
rescue
|
198
|
+
#Unknown class
|
199
|
+
message_data.merge("queue"=>"default")
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
#override queue if setted in config
|
205
|
+
#only if message is hash - can be string (dumped JSON)
|
206
|
+
message_data['queue'] = args['queue'] if args['queue']
|
207
|
+
|
208
|
+
#dump message as json
|
209
|
+
@message = message_data
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
def status
|
215
|
+
@status
|
216
|
+
end
|
217
|
+
|
218
|
+
def disable!
|
219
|
+
@status = "disabled"
|
220
|
+
save
|
221
|
+
end
|
222
|
+
|
223
|
+
def enable!
|
224
|
+
@status = "enabled"
|
225
|
+
save
|
226
|
+
end
|
227
|
+
|
228
|
+
def status_from_redis
|
229
|
+
if exists?
|
230
|
+
out = "enabled"
|
231
|
+
Sidekiq.redis do |conn|
|
232
|
+
out = conn.hget redis_key, "status"
|
233
|
+
end
|
234
|
+
out
|
235
|
+
else
|
236
|
+
"enabled"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
#export job data to hash
|
241
|
+
def to_hash
|
242
|
+
{
|
243
|
+
name: @name,
|
244
|
+
klass: @klass,
|
245
|
+
cron: @cron,
|
246
|
+
args: @args.is_a?(String) ? @args : Sidekiq.dump_json(@args || []),
|
247
|
+
message: @message.is_a?(String) ? @message : Sidekiq.dump_json(@message || {}),
|
248
|
+
status: @status,
|
249
|
+
last_run_time: @last_run_time,
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
def errors
|
254
|
+
@errors ||= []
|
255
|
+
end
|
256
|
+
|
257
|
+
def valid?
|
258
|
+
#clear previos errors
|
259
|
+
@errors = []
|
260
|
+
|
261
|
+
errors << "'name' must be set" if @name.nil? || @name.size == 0
|
262
|
+
if @cron.nil? || @cron.size == 0
|
263
|
+
errors << "'cron' must be set"
|
264
|
+
else
|
265
|
+
begin
|
266
|
+
cron = CronParser.new(@cron)
|
267
|
+
cron.next(Time.now)
|
268
|
+
rescue Exception => e
|
269
|
+
errors << "'cron' -> #{@cron}: #{e.message}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
errors << "'klass' (or class) must be set" if @klass.nil? || @klass.size == 0
|
274
|
+
|
275
|
+
!errors.any?
|
276
|
+
end
|
277
|
+
|
278
|
+
# add job to cron jobs
|
279
|
+
# input:
|
280
|
+
# name: (string) - name of job
|
281
|
+
# cron: (string: '* * * * *' - cron specification when to run job
|
282
|
+
# class: (string|class) - which class to perform
|
283
|
+
# optional input:
|
284
|
+
# queue: (string) - which queue to use for enquing (will override class queue)
|
285
|
+
# args: (array|hash|nil) - arguments for permorm method
|
286
|
+
|
287
|
+
def save
|
288
|
+
#if job is invalid return false
|
289
|
+
return false unless valid?
|
290
|
+
|
291
|
+
Sidekiq.redis do |conn|
|
292
|
+
|
293
|
+
#add to set of all jobs
|
294
|
+
conn.sadd self.class.jobs_key, redis_key
|
295
|
+
|
296
|
+
#add informations for this job!
|
297
|
+
conn.hmset redis_key, *hash_to_redis(to_hash)
|
298
|
+
|
299
|
+
#add information about last time! - don't enque right after scheduler poller starts!
|
300
|
+
time = Time.now
|
301
|
+
conn.zadd(job_enqueued_key, time.to_f.to_s, formated_last_time(time).to_s)
|
302
|
+
end
|
303
|
+
logger.info { "Cron Jobs - add job with name: #{@name}" }
|
304
|
+
end
|
305
|
+
|
306
|
+
# remove job from cron jobs by name
|
307
|
+
# input:
|
308
|
+
# first arg: name (string) - name of job (must be same - case sensitive)
|
309
|
+
def destroy
|
310
|
+
Sidekiq.redis do |conn|
|
311
|
+
#delete from set
|
312
|
+
conn.srem self.class.jobs_key, redis_key
|
313
|
+
|
314
|
+
#delete runned timestamps
|
315
|
+
conn.del job_enqueued_key
|
316
|
+
|
317
|
+
#delete main job
|
318
|
+
conn.del redis_key
|
319
|
+
end
|
320
|
+
logger.info { "Cron Jobs - deleted job with name: #{@name}" }
|
321
|
+
end
|
322
|
+
|
323
|
+
# remove all job from cron
|
324
|
+
def self.destroy_all!
|
325
|
+
all.each do |job|
|
326
|
+
job.destroy
|
327
|
+
end
|
328
|
+
logger.info { "Cron Jobs - deleted all jobs" }
|
329
|
+
end
|
330
|
+
|
331
|
+
# Parse cron specification '* * * * *' and returns
|
332
|
+
# time when last run should be performed
|
333
|
+
def last_time now = Time.now
|
334
|
+
# add 1 minute to Time now - Cron parser return last time after minute ends,
|
335
|
+
# so by adding 60 second we will get last time after the right time happens
|
336
|
+
# without any delay!
|
337
|
+
CronParser.new(@cron).last(now + 60)
|
338
|
+
end
|
339
|
+
|
340
|
+
def formated_last_time now = Time.now
|
341
|
+
last_time(now).getutc
|
342
|
+
end
|
343
|
+
|
344
|
+
def self.exists? name
|
345
|
+
out = false
|
346
|
+
Sidekiq.redis do |conn|
|
347
|
+
out = conn.exists redis_key name
|
348
|
+
end
|
349
|
+
out
|
350
|
+
end
|
351
|
+
|
352
|
+
def exists?
|
353
|
+
self.class.exists? @name
|
354
|
+
end
|
355
|
+
|
356
|
+
def sort_name
|
357
|
+
"#{status == "enabled" ? 0 : 1}_#{name}".downcase
|
358
|
+
end
|
359
|
+
|
360
|
+
private
|
361
|
+
|
362
|
+
# Redis key for set of all cron jobs
|
363
|
+
def self.jobs_key
|
364
|
+
"cron_jobs"
|
365
|
+
end
|
366
|
+
|
367
|
+
# Redis key for storing one cron job
|
368
|
+
def self.redis_key name
|
369
|
+
"cron_job:#{name}"
|
370
|
+
end
|
371
|
+
|
372
|
+
# Redis key for storing one cron job
|
373
|
+
def redis_key
|
374
|
+
self.class.redis_key @name
|
375
|
+
end
|
376
|
+
|
377
|
+
# Redis key for storing one cron job run times
|
378
|
+
# (when poller added job to queue)
|
379
|
+
def self.job_enqueued_key name
|
380
|
+
"cron_job:#{name}:enqueued"
|
381
|
+
end
|
382
|
+
|
383
|
+
# Redis key for storing one cron job run times
|
384
|
+
# (when poller added job to queue)
|
385
|
+
def job_enqueued_key
|
386
|
+
self.class.job_enqueued_key @name
|
387
|
+
end
|
388
|
+
|
389
|
+
# Give Hash
|
390
|
+
# returns array for using it for redis.hmset
|
391
|
+
def hash_to_redis hash
|
392
|
+
hash.inject([]){ |arr,kv| arr + [kv[0], kv[1]] }
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|