recurrent 0.1.0 → 0.2.0

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.
@@ -35,7 +35,7 @@ Use the every method to create tasks.
35
35
  end
36
36
 
37
37
  ####Frequency
38
- The first argument is the frequency, it can be an integer in seconds, including ActiveSupport style 2.hours, 3.weeks etc, or an [IceCube::Rule](http://seejohncode.com/ice_cube/)
38
+ The first argument is the frequency, it can be an integer in seconds, including ActiveSupport style 2.hours, 3.weeks etc, or an [IceCube::Schedule](http://seejohncode.com/ice_cube/)
39
39
  ####Name
40
40
  The second argument is the name of the task
41
41
  ####Options
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- $: << File.expand_path("#{File.dirname(__FILE__)}/../lib")
3
2
  require 'rubygems'
4
3
  require 'trollop'
5
4
 
@@ -16,5 +15,5 @@ begin
16
15
  rescue LoadError
17
16
  require 'recurrent'
18
17
  end
19
-
18
+ $0 = "recurrent:worker:started-#{Time.now.to_s(:logging)}"
20
19
  Recurrent::Worker.new(opts).start
@@ -19,7 +19,7 @@ module Recurrent
19
19
  ")
20
20
  end
21
21
  end
22
- block_accessor :logger, :save_task_schedule, :load_task_schedule, :save_task_return_value, :process_locking, :handle_slow_task
22
+ block_accessor :logger, :save_task_schedule, :load_task_schedule, :save_task_return_value, :load_task_return_value, :process_locking, :handle_slow_task, :setup
23
23
 
24
24
  end
25
25
  end
@@ -1,5 +1,12 @@
1
1
  module IceCube
2
2
  class Rule
3
+
4
+ def ==(another_rule)
5
+ ['@interval', '@validation_types', '@validations'].all? do |variable|
6
+ self.instance_variable_get(variable) == another_rule.instance_variable_get(variable)
7
+ end && self.class.name == another_rule.class.name
8
+ end
9
+
3
10
  def frequency_in_seconds
4
11
  rule_type = self.class
5
12
  if rule_type == IceCube::YearlyRule
@@ -19,4 +26,12 @@ module IceCube
19
26
  end
20
27
  end
21
28
  end
22
- end
29
+
30
+ class Schedule
31
+ attr_writer :start_date
32
+
33
+ def has_same_rules?(other_schedule)
34
+ self.rrules == other_schedule.rrules
35
+ end
36
+ end
37
+ end
@@ -41,50 +41,26 @@ module Recurrent
41
41
  end
42
42
 
43
43
  def create_schedule(name, frequency, start_time=nil)
44
- logger.info "| Creating schedule"
45
- if frequency.is_a? IceCube::Rule
46
- logger.info "| Frequency is an IceCube Rule: #{frequency.to_s}"
47
- rule = frequency
48
- frequency_in_seconds = rule.frequency_in_seconds
44
+ saved_schedule = Configuration.load_task_schedule.call(name) if Configuration.load_task_schedule
45
+ new_schedule = frequency.is_a?(IceCube::Schedule) ? frequency : create_schedule_from_frequency(frequency, start_time)
46
+ if saved_schedule
47
+ use_saved_schedule_if_rules_match(saved_schedule, new_schedule)
49
48
  else
50
- logger.info "| Frequency is an integer: #{frequency}"
51
- rule = create_rule_from_frequency(frequency)
52
- logger.info "| IceCube Rule created: #{rule.to_s}"
53
- frequency_in_seconds = frequency
49
+ new_schedule
54
50
  end
55
- start_time ||= derive_start_time(name, frequency_in_seconds)
51
+ end
52
+
53
+ def create_schedule_from_frequency(frequency, start_time=nil)
54
+ logger.info "| Frequency is an integer: #{frequency}"
55
+ rule = create_rule_from_frequency(frequency)
56
+ logger.info "| IceCube Rule created: #{rule.to_s}"
57
+ frequency_in_seconds = frequency
58
+ start_time ||= derive_start_time_from_frequency(frequency_in_seconds)
56
59
  schedule = IceCube::Schedule.new(start_time)
57
60
  schedule.add_recurrence_rule rule
58
- logger.info "| schedule created"
59
61
  schedule
60
62
  end
61
63
 
62
- def derive_start_time(name, frequency)
63
- logger.info "| No start time provided, deriving one."
64
- if Configuration.load_task_schedule
65
- logger.info "| Attempting to derive from saved schedule"
66
- derive_start_time_from_saved_schedule(name, frequency)
67
- else
68
- derive_start_time_from_frequency(frequency)
69
- end
70
- end
71
-
72
- def derive_start_time_from_saved_schedule(name, frequency)
73
- saved_schedule = Configuration.load_task_schedule.call(name)
74
- if saved_schedule
75
- logger.info "| Saved schedule found"
76
- if saved_schedule.rrules.first.frequency_in_seconds == frequency
77
- logger.info "| Saved schedule frequency matches, setting start time to saved schedules next occurrence: #{saved_schedule.next_occurrence.to_s(:seconds)}"
78
- saved_schedule.next_occurrence
79
- else
80
- logger.info "| Schedule frequency does not match saved schedule frequency"
81
- derive_start_time_from_frequency(frequency)
82
- end
83
- else
84
- derive_start_time_from_frequency(frequency)
85
- end
86
- end
87
-
88
64
  def derive_start_time_from_frequency(frequency)
89
65
  logger.info "| Deriving start time from frequency"
90
66
  current_time = Time.now
@@ -135,6 +111,16 @@ module Recurrent
135
111
  end
136
112
  end
137
113
 
114
+ def use_saved_schedule_if_rules_match(saved_schedule, new_schedule)
115
+ if new_schedule.has_same_rules? saved_schedule
116
+ logger.info "| Schedule matches a saved schedule, using saved schedule."
117
+ saved_schedule.start_date = saved_schedule.next_occurrence
118
+ saved_schedule
119
+ else
120
+ new_schedule
121
+ end
122
+ end
123
+
138
124
  def self.define_frequencies(*frequencies)
139
125
  frequencies.each do |frequency|
140
126
  method_name = frequency == :day ? :daily? : :"#{frequency}ly?"
@@ -15,8 +15,18 @@ module Recurrent
15
15
  return handle_still_running(execution_time) if running?
16
16
  @thread = Thread.new do
17
17
  Thread.current["execution_time"] = execution_time
18
- return_value = action.call
19
- save_results(return_value) if save?
18
+ begin
19
+ if Configuration.load_task_return_value && action.arity == 1
20
+ previous_value = Configuration.load_task_return_value.call(name)
21
+ return_value = action.call(previous_value)
22
+ else
23
+ return_value = action.call
24
+ end
25
+ save_results(return_value) if save?
26
+ rescue => e
27
+ logger.warn("#{name} - #{e.message}")
28
+ logger.warn(e.backtrace)
29
+ end
20
30
  end
21
31
  end
22
32
 
@@ -28,8 +38,8 @@ module Recurrent
28
38
  end
29
39
 
30
40
  def next_occurrence
31
- return @next_occurrence if @next_occurrence && @next_occurrence.future?
32
- @next_occurrence = schedule.next_occurrence
41
+ occurrence = schedule.next_occurrence
42
+ schedule.start_date = occurrence
33
43
  end
34
44
 
35
45
  def save?
@@ -1,3 +1,3 @@
1
1
  module Recurrent
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -4,6 +4,7 @@ module Recurrent
4
4
  attr_accessor :scheduler, :logger
5
5
 
6
6
  def initialize(options={})
7
+ Configuration.setup.call if Configuration.setup
7
8
  file = options[:file]
8
9
  @scheduler = Scheduler.new(file)
9
10
  if options[:every]
@@ -61,7 +62,7 @@ module Recurrent
61
62
  lock_established = nil
62
63
  until lock_established
63
64
  break if $exit
64
- lock_established = Configuration.process_locking.call do
65
+ lock_established = Configuration.process_locking.call(*scheduler.tasks.map(&:name)) do
65
66
  execute
66
67
  end
67
68
  break if $exit
@@ -55,17 +55,16 @@ module Recurrent
55
55
  end
56
56
  end
57
57
 
58
- describe "create_schedule" do
59
- context "when frequency is an IceCube Rule" do
60
- subject do
58
+ describe "#create_schedule" do
59
+ context "when frequency is an IceCube::Schedule" do
60
+ before :each do
61
61
  rule = IceCube::Rule.daily(1)
62
- @scheduler.create_schedule(:test, rule)
63
- end
64
- it "should be a schedule" do
65
- subject.class.should == IceCube::Schedule
62
+ @schedule = IceCube::Schedule.new(Time.now)
63
+ @schedule.add_recurrence_rule rule
66
64
  end
67
- it "should have the correct rule" do
68
- subject.rrules.first.is_a? IceCube::DailyRule
65
+
66
+ it "returns the schedule" do
67
+ @scheduler.create_schedule(:test, @schedule).should == @schedule
69
68
  end
70
69
  end
71
70
 
@@ -83,7 +82,7 @@ module Recurrent
83
82
 
84
83
  context "when start time is not provided" do
85
84
  it "should derive its own start time" do
86
- @scheduler.should_receive(:derive_start_time)
85
+ @scheduler.should_receive(:derive_start_time_from_frequency).with(1.day)
87
86
  @scheduler.create_schedule(:test, 1.day)
88
87
  end
89
88
  end
@@ -96,7 +95,7 @@ module Recurrent
96
95
  end
97
96
  end
98
97
 
99
- describe "derive_start_time_from_frequency" do
98
+ describe "#derive_start_time_from_frequency" do
100
99
  context "when the current time is 11:35:12 am on July 26th, 2011" do
101
100
  before(:all) do
102
101
  Timecop.freeze(Time.local(2011, 7, 26, 11, 35, 12))
@@ -150,30 +149,26 @@ module Recurrent
150
149
  end
151
150
  end
152
151
 
153
- describe "derive_start_time_from_saved_schedule" do
152
+ describe "A schedule has a saved schedule" do
154
153
  before(:all) do
155
154
  @scheduler = Scheduler.new
156
155
  Configuration.load_task_schedule do |name|
157
- current_time = Time.new
158
- current_time.change(:sec => 0, :usec => 0)
159
- @scheduler.create_schedule(:test, 10.seconds, current_time) if name == :test
156
+ if name == :test
157
+ current_time = Time.new
158
+ current_time.change(:sec => 0, :usec => 0)
159
+ schedule = IceCube::Schedule.new(current_time)
160
+ schedule.add_recurrence_rule IceCube::SecondlyRule.new(10)
161
+ schedule
162
+ end
160
163
  end
161
164
  end
162
165
 
163
166
  describe "a schedule being created with a saved schedule with the same name and frequency" do
164
- it "derives its start time from the saved schedule" do
165
- @scheduler.should_not_receive(:derive_start_time_from_frequency)
166
- @scheduler.create_schedule(:test, 10.seconds)
167
- end
168
-
169
- describe "the created schedule's start time" do
170
- it "should be the next occurrence of the saved schedule" do
171
- saved_schedule = Configuration.load_task_schedule.call(:test)
172
- created_schedule = @scheduler.create_schedule(:test, 10.seconds)
173
- created_schedule.start_date.to_s(:seconds).should == saved_schedule.next_occurrence.to_s(:seconds)
174
- end
167
+ it "should return the saved schedule with its start time updated to be its next_occurrence" do
168
+ saved_schedule = Configuration.load_task_schedule.call(:test)
169
+ created_schedule = @scheduler.create_schedule(:test, 10.seconds)
170
+ created_schedule.start_date.to_s(:seconds).should == saved_schedule.next_occurrence.to_s(:seconds)
175
171
  end
176
-
177
172
  end
178
173
 
179
174
  describe "a schedule being created with a saved schedule with the same name and different frequency" do
@@ -10,7 +10,7 @@ module Recurrent
10
10
  before :each do
11
11
  @executing_task_time = 5.minutes.ago
12
12
  @current_time = Time.now
13
- @task = Task.new :name => 'execute test', :logger => Logger.new('some identifier')
13
+ @task = Task.new :name => 'execute test', :logger => Logger.new('some identifier'), :action => proc { 'blah' }
14
14
  end
15
15
 
16
16
  context "The task is still running a previous execution" do
@@ -40,30 +40,64 @@ module Recurrent
40
40
  @task.thread['execution_time'].should == @current_time
41
41
  end
42
42
 
43
- it "calls the action" do
44
- @task.action.should_receive(:call)
45
- @task.execute(@current_time)
43
+ context "load_task_return_value is configured" do
44
+ before :each do
45
+ Configuration.load_task_return_value do
46
+ "testing"
47
+ end
48
+ end
49
+
50
+ context "the action doesn't take an argument" do
51
+ it "calls the action with a nil argument" do
52
+ @task.action.should_receive(:call)
53
+ @task.execute(@current_time)
54
+ end
55
+ end
56
+
57
+ context "the action takes an argument" do
58
+ before :each do
59
+ @task.action = proc { |previous_value| "derp" }
60
+ end
61
+
62
+ it "loads the task return value and calls the action with it as an argument" do
63
+ @task.action.should_receive(:call).with("testing")
64
+ @task.execute(@current_time)
65
+ end
66
+
67
+ after :each do
68
+ @task.action = proc { "derp" }
69
+ end
70
+ end
71
+ after :each do
72
+ Configuration.load_task_return_value = nil
73
+ end
74
+ end
75
+
76
+ context "load_task_return_value is not configured" do
77
+
46
78
  end
79
+
80
+
81
+
47
82
  end
48
83
 
49
84
  describe "#next_occurrence" do
50
85
  context "a task that occurs ever 10 seconds and has just occurred" do
51
- subject do
52
- current_time = Time.new
53
- current_time.change(:sec => 0, :usec => 0)
54
- Timecop.freeze(current_time)
55
- Task.new(:name => :test, :schedule => Scheduler.new.create_schedule(:test, 10.seconds, current_time))
86
+ before :each do
87
+ @current_time = Time.new
88
+ @current_time.change(:sec => 0, :usec => 0)
89
+ Timecop.freeze(@current_time)
90
+ @task = Task.new(:name => :test, :schedule => Scheduler.new.create_schedule(:test, 10.seconds, @current_time))
56
91
  end
57
92
 
58
93
  it "should occur 10 seconds from now" do
59
- subject.next_occurrence.should == 10.seconds.from_now
94
+ @task.next_occurrence.should == 10.seconds.from_now
60
95
  end
61
96
 
62
- it "should cache its next occurrence while it's still valid" do
63
- subject.schedule.should_receive(:next_occurrence).and_return(10.seconds.from_now)
64
- subject.next_occurrence
65
- subject.schedule.should_not_receive(:next_occurrence)
66
- subject.next_occurrence
97
+ it "update the start time of the task to the time of this occurrence because IceCube::Schedule#next_occurrence gets progressively slower the farther back the start time is" do
98
+ @task.schedule.start_date.should == @current_time
99
+ @task.next_occurrence.should == 10.seconds.from_now
100
+ @task.schedule.start_date.should == 10.seconds.from_now
67
101
  end
68
102
 
69
103
  after(:each) do
@@ -41,5 +41,23 @@ module Recurrent
41
41
  Timecop.return
42
42
  end
43
43
  end
44
+
45
+ describe "setup" do
46
+ before :each do
47
+ Configuration.setup do
48
+ @setup = true
49
+ end
50
+ end
51
+
52
+ it "runs any configured setup when a worker is created" do
53
+ @setup.should == nil
54
+ Worker.new
55
+ @setup.should == true
56
+ end
57
+
58
+ after :each do
59
+ Configuration.setup = nil
60
+ end
61
+ end
44
62
  end
45
63
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recurrent
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam Kittelson
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-09 00:00:00 Z
18
+ date: 2011-08-13 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: ice_cube