simple_scheduler 0.2.1 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/simple_scheduler.rb +1 -0
- data/lib/simple_scheduler/at.rb +99 -0
- data/lib/simple_scheduler/future_job.rb +11 -15
- data/lib/simple_scheduler/task.rb +47 -73
- data/lib/simple_scheduler/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fcaf4fe9556dbb85aec2ed64578416c97121157
|
4
|
+
data.tar.gz: 6cd57daf1dd3a2d8fe31c4eed3ba18904d2f4121
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a6c2563947e0450d48f6d3729b3fe9746917056747894f8ad2d887c29b5a81bb2eb34a7d02785355c1b65a4bedf1fd97e6d303da1845eb9396e0bffb65707b2
|
7
|
+
data.tar.gz: 475b59cac94a988e02cb4eccd6f907b64221bb64c67184beefdc0d5089d104600b5598ceee675cf89ccea197f29380f2ec55e93b1e9b0dbc7eb80456a93b269b
|
data/lib/simple_scheduler.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
module SimpleScheduler
|
2
|
+
# A Time class for parsing the :at option on a task into the first time it should run.
|
3
|
+
# Time.now
|
4
|
+
# # => 2016-12-09 08:24:11 -0600
|
5
|
+
# SimpleScheduler::At.new("*:30")
|
6
|
+
# # => 2016-12-09 08:30:00 -0600
|
7
|
+
# SimpleScheduler::At.new("1:00")
|
8
|
+
# # => 2016-12-10 01:00:00 -0600
|
9
|
+
# SimpleScheduler::At.new("Sun 0:00")
|
10
|
+
# # => 2016-12-11 01:00:00 -0600
|
11
|
+
class At < Time
|
12
|
+
AT_PATTERN = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat)?\s?(?:\*{1,2}|(\d{1,2})):(\d{1,2})/
|
13
|
+
DAYS = %w(Sun Mon Tue Wed Thu Fri Sat).freeze
|
14
|
+
|
15
|
+
# Accepts a time string to determine when a task should be run for the first time.
|
16
|
+
# Valid formats:
|
17
|
+
# "18:00"
|
18
|
+
# "3:30"
|
19
|
+
# "**:00"
|
20
|
+
# "*:30"
|
21
|
+
# "Sun 2:00"
|
22
|
+
# "[Sun|Mon|Tue|Wed|Thu|Fri|Sat] 00:00"
|
23
|
+
# @param at [String] The formatted string for a task's run time
|
24
|
+
# @param time_zone [ActiveSupport::TimeZone] The time zone to parse the at time in
|
25
|
+
def initialize(at, time_zone = Time.zone)
|
26
|
+
@at = at
|
27
|
+
@time_zone = time_zone
|
28
|
+
super(parsed_time.year, parsed_time.month, parsed_time.day,
|
29
|
+
parsed_time.hour, parsed_time.min, parsed_time.sec, parsed_time.utc_offset)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Always returns the specified hour.
|
33
|
+
# @return [Integer]
|
34
|
+
def hour
|
35
|
+
hour? ? at_hour : super
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns whether or not the hour was specified in the :at string.
|
39
|
+
# @return [Boolean]
|
40
|
+
def hour?
|
41
|
+
at_match[2].present?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def at_match
|
47
|
+
@at_match ||= AT_PATTERN.match(@at) || []
|
48
|
+
end
|
49
|
+
|
50
|
+
def at_hour
|
51
|
+
@at_hour ||= (at_match[2] || now.hour).to_i
|
52
|
+
end
|
53
|
+
|
54
|
+
def at_min
|
55
|
+
@at_min ||= (at_match[3] || now.min).to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def at_wday
|
59
|
+
@wday ||= DAYS.index(at_match[1])
|
60
|
+
end
|
61
|
+
|
62
|
+
def at_wday?
|
63
|
+
at_match[1].present?
|
64
|
+
end
|
65
|
+
|
66
|
+
def next_hour?
|
67
|
+
!hour? && at_hour == now.hour && at_min < now.min
|
68
|
+
end
|
69
|
+
|
70
|
+
def now
|
71
|
+
@now ||= @time_zone.now.beginning_of_minute
|
72
|
+
end
|
73
|
+
|
74
|
+
def parsed_day
|
75
|
+
parsed_day = now.beginning_of_day
|
76
|
+
|
77
|
+
# If no day of the week is given, return today
|
78
|
+
return parsed_day unless at_wday?
|
79
|
+
|
80
|
+
# Shift to the correct day of the week if given
|
81
|
+
add_days = at_wday - parsed_day.wday
|
82
|
+
add_days += 7 if parsed_day.wday > at_wday
|
83
|
+
parsed_day + add_days.days
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the very first time a job should be run for the scheduled task.
|
87
|
+
# @return [Time]
|
88
|
+
def parsed_time
|
89
|
+
return @parsed_time if @parsed_time
|
90
|
+
|
91
|
+
@parsed_time = parsed_day
|
92
|
+
change_hour = at_hour
|
93
|
+
change_hour += 1 if next_hour?
|
94
|
+
@parsed_time = @parsed_time.change(hour: change_hour, min: at_min)
|
95
|
+
@parsed_time += at_wday? ? 1.week : 1.day if now > @parsed_time
|
96
|
+
@parsed_time
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -23,12 +23,7 @@ module SimpleScheduler
|
|
23
23
|
@task = Task.new(task_params)
|
24
24
|
@scheduled_time = Time.at(scheduled_time).in_time_zone(@task.time_zone)
|
25
25
|
raise Expired if expired?
|
26
|
-
|
27
|
-
if @task.job_class.included_modules.include?(Sidekiq::Worker)
|
28
|
-
queue_sidekiq_worker
|
29
|
-
else
|
30
|
-
queue_active_job
|
31
|
-
end
|
26
|
+
queue_task
|
32
27
|
end
|
33
28
|
|
34
29
|
private
|
@@ -62,21 +57,22 @@ module SimpleScheduler
|
|
62
57
|
end
|
63
58
|
end
|
64
59
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
60
|
+
# The name of the method used to queue the task's job or worker.
|
61
|
+
# @return [Symbol]
|
62
|
+
def perform_method
|
63
|
+
if @task.job_class.included_modules.include?(Sidekiq::Worker)
|
64
|
+
:perform_async
|
69
65
|
else
|
70
|
-
|
66
|
+
:perform_later
|
71
67
|
end
|
72
68
|
end
|
73
69
|
|
74
|
-
# Queue the
|
75
|
-
def
|
70
|
+
# Queue the task with the scheduled time if the job allows.
|
71
|
+
def queue_task
|
76
72
|
if @task.job_class.instance_method(:perform).arity > 0
|
77
|
-
@task.job_class.
|
73
|
+
@task.job_class.send(perform_method, @scheduled_time.to_i)
|
78
74
|
else
|
79
|
-
@task.job_class.
|
75
|
+
@task.job_class.send(perform_method)
|
80
76
|
end
|
81
77
|
end
|
82
78
|
end
|
@@ -2,30 +2,13 @@ module SimpleScheduler
|
|
2
2
|
# Class for parsing each task in the scheduler config YAML file and returning
|
3
3
|
# the values needed to schedule the task in the future.
|
4
4
|
#
|
5
|
-
# @!attribute [r] at
|
6
|
-
# @return [String] The starting time for the interval
|
7
|
-
# @!attribute [r] expires_after
|
8
|
-
# @return [String] The time between the scheduled and actual run time that should cause the job not to run
|
9
|
-
# @!attribute [r] frequency
|
10
|
-
# @return [ActiveSupport::Duration] How often the job will be run
|
11
5
|
# @!attribute [r] job_class
|
12
|
-
# @return [Class] The class of the job or worker
|
13
|
-
# @!attribute [r] job_class_name
|
14
|
-
# @return [String] The class name of the job or worker
|
15
|
-
# @!attribute [r] name
|
16
|
-
# @return [String] The name of the task as defined in the YAML config
|
6
|
+
# @return [Class] The class of the job or worker.
|
17
7
|
# @!attribute [r] params
|
18
8
|
# @return [Hash] The params used to create the task
|
19
|
-
# @!attribute [r] queue_ahead
|
20
|
-
# @return [String] The name of the task as defined in the YAML config
|
21
|
-
# @!attribute [r] time_zone
|
22
|
-
# @return [ActiveSupport::TimeZone] The time zone to use when parsing the `at` option
|
23
9
|
class Task
|
24
|
-
attr_reader :
|
25
|
-
attr_reader :name, :params, :queue_ahead, :time_zone
|
10
|
+
attr_reader :job_class, :params
|
26
11
|
|
27
|
-
AT_PATTERN = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat)?\s?(?:\*{1,2}|(\d{1,2})):(\d{1,2})/
|
28
|
-
DAYS = %w(Sun Mon Tue Wed Thu Fri Sat).freeze
|
29
12
|
DEFAULT_QUEUE_AHEAD_MINUTES = 360
|
30
13
|
|
31
14
|
# Initializes a task by parsing the params so the task can be queued in the future.
|
@@ -39,15 +22,19 @@ module SimpleScheduler
|
|
39
22
|
# @option params [String] :tz The time zone to use when parsing the `at` option
|
40
23
|
def initialize(params)
|
41
24
|
validate_params!(params)
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@
|
49
|
-
|
50
|
-
|
25
|
+
@params = params
|
26
|
+
end
|
27
|
+
|
28
|
+
# The task's first run time as a Time-like object.
|
29
|
+
# @return [SimpleScheduler::At]
|
30
|
+
def at
|
31
|
+
@at ||= At.new(@params[:at], time_zone)
|
32
|
+
end
|
33
|
+
|
34
|
+
# The time between the scheduled and actual run time that should cause the job not to run.
|
35
|
+
# @return [String]
|
36
|
+
def expires_after
|
37
|
+
@params[:expires_after]
|
51
38
|
end
|
52
39
|
|
53
40
|
# Returns an array of existing jobs matching the job class of the task.
|
@@ -66,15 +53,10 @@ module SimpleScheduler
|
|
66
53
|
@existing_run_times ||= existing_jobs.map(&:at)
|
67
54
|
end
|
68
55
|
|
69
|
-
#
|
70
|
-
# @return [
|
71
|
-
def
|
72
|
-
|
73
|
-
change_hour = first_run_hour
|
74
|
-
change_hour += 1 if at_match[2].nil? && first_run_hour == now.hour && first_run_min < now.min
|
75
|
-
first_run_time = first_run_time.change(hour: change_hour, min: first_run_min)
|
76
|
-
first_run_time += at_match[1] ? 1.week : 1.day if now > first_run_time
|
77
|
-
first_run_time
|
56
|
+
# How often the job will be run.
|
57
|
+
# @return [ActiveSupport::Duration]
|
58
|
+
def frequency
|
59
|
+
@frequency ||= parse_frequency(@params[:every])
|
78
60
|
end
|
79
61
|
|
80
62
|
# Returns an array Time objects for future run times based on
|
@@ -82,60 +64,51 @@ module SimpleScheduler
|
|
82
64
|
# @return [Array<Time>]
|
83
65
|
def future_run_times
|
84
66
|
future_run_times = existing_run_times.dup
|
85
|
-
last_run_time = future_run_times.last ||
|
67
|
+
last_run_time = future_run_times.last || at - frequency
|
86
68
|
last_run_time = last_run_time.in_time_zone(time_zone)
|
87
69
|
|
88
|
-
|
70
|
+
# Ensure there are at least two future jobs scheduled and that the queue ahead time is filled
|
71
|
+
while future_run_times.length < 2 || ((last_run_time - Time.now) / 1.minute) < queue_ahead
|
89
72
|
last_run_time = frequency.from_now(last_run_time)
|
90
|
-
last_run_time = last_run_time.change(hour:
|
73
|
+
last_run_time = last_run_time.change(hour: at.hour, min: at.min) if at.hour?
|
91
74
|
future_run_times << last_run_time
|
92
75
|
end
|
93
76
|
|
94
77
|
future_run_times
|
95
78
|
end
|
96
79
|
|
97
|
-
#
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
@scheduled_set ||= Sidekiq::ScheduledSet.new
|
80
|
+
# The class name of the job or worker.
|
81
|
+
# @return [String]
|
82
|
+
def job_class_name
|
83
|
+
@params[:class]
|
102
84
|
end
|
103
85
|
|
104
|
-
|
105
|
-
|
106
|
-
def
|
107
|
-
@
|
86
|
+
# The name of the task as defined in the YAML config.
|
87
|
+
# @return [String]
|
88
|
+
def name
|
89
|
+
@params[:name]
|
108
90
|
end
|
109
91
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
@
|
114
|
-
|
115
|
-
# If no day of the week is given, return today
|
116
|
-
return @first_run_day unless first_run_wday
|
117
|
-
|
118
|
-
# Shift to the correct day of the week if given
|
119
|
-
add_days = first_run_wday - first_run_day.wday
|
120
|
-
add_days += 7 if first_run_day.wday > first_run_wday
|
121
|
-
@first_run_day += add_days.days
|
122
|
-
end
|
123
|
-
|
124
|
-
def first_run_hour
|
125
|
-
@first_run_hour ||= (at_match[2] || now.hour).to_i
|
92
|
+
# The number of minutes that jobs should be queued in the future.
|
93
|
+
# @return [Integer]
|
94
|
+
def queue_ahead
|
95
|
+
@queue_ahead ||= @params[:queue_ahead] || DEFAULT_QUEUE_AHEAD_MINUTES
|
126
96
|
end
|
127
97
|
|
128
|
-
|
129
|
-
|
98
|
+
# The time zone to use when parsing the `at` option.
|
99
|
+
# @return [ActiveSupport::TimeZone]
|
100
|
+
def time_zone
|
101
|
+
@time_zone ||= params[:tz] ? ActiveSupport::TimeZone.new(params[:tz]) : Time.zone
|
130
102
|
end
|
131
103
|
|
132
|
-
|
133
|
-
|
104
|
+
# Loads the scheduled jobs from Sidekiq once to avoid loading from
|
105
|
+
# Redis for each task when looking up existing scheduled jobs.
|
106
|
+
# @return [Sidekiq::ScheduledSet]
|
107
|
+
def self.scheduled_set
|
108
|
+
@scheduled_set ||= Sidekiq::ScheduledSet.new
|
134
109
|
end
|
135
110
|
|
136
|
-
|
137
|
-
@now ||= @time_zone.now.beginning_of_minute
|
138
|
-
end
|
111
|
+
private
|
139
112
|
|
140
113
|
def parse_frequency(every_string)
|
141
114
|
split_duration = every_string.split(".")
|
@@ -145,9 +118,10 @@ module SimpleScheduler
|
|
145
118
|
end
|
146
119
|
|
147
120
|
def validate_params!(params)
|
148
|
-
params[:name] ||= params[:class]
|
149
121
|
raise ArgumentError, "Missing param `class` specifying the class of the job to run." unless params.key?(:class)
|
150
122
|
raise ArgumentError, "Missing param `every` specifying how often the job should run." unless params.key?(:every)
|
123
|
+
@job_class = params[:class].constantize
|
124
|
+
params[:name] ||= params[:class]
|
151
125
|
end
|
152
126
|
end
|
153
127
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Pattison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '4.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: appraisal
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec-rails
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,7 @@ files:
|
|
108
122
|
- README.md
|
109
123
|
- Rakefile
|
110
124
|
- lib/simple_scheduler.rb
|
125
|
+
- lib/simple_scheduler/at.rb
|
111
126
|
- lib/simple_scheduler/future_job.rb
|
112
127
|
- lib/simple_scheduler/railtie.rb
|
113
128
|
- lib/simple_scheduler/scheduler_job.rb
|