CronR 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c9bd8a1c64b404ecffe432983d48a7d168e6c539
4
+ data.tar.gz: 8b153052e7e12c4cf79382731ec0fe4944694f45
5
+ SHA512:
6
+ metadata.gz: bd1eb218a75187eef9d1a1f11e1049a541fd9f40ab136cd1ad4cc55998ea40d75f83256705af845d3ddd780ed8ab8b769352e925934e439553958eb734b02128
7
+ data.tar.gz: 121c5104873409d3cc58551ecc84926271176a5b26cf2d796161a04efa377befe5ead289279bb88d102e944348cf25881e0560475a2e512fa5e793d1dd6b70a1
@@ -0,0 +1,11 @@
1
+ # The files in this directory are part of CronR, a ruby library.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the MIT License. A
4
+ # copy of the license should be enclosed with this project in the file
5
+ # LICENSE.txt.
6
+
7
+ module CronR
8
+ require_relative 'CronR/utils'
9
+ require_relative 'CronR/Cron'
10
+ require_relative 'CronR/CronJob'
11
+ end
@@ -0,0 +1,198 @@
1
+ # The files in this directory are part of CronR, a ruby library.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the MIT License. A
4
+ # copy of the license should be enclosed with this project in the file
5
+ # LICENSE.txt.
6
+
7
+ require_relative 'utils'
8
+
9
+ module CronR
10
+
11
+ # This is an array that is modified to represent a cron table along
12
+ # with the necessary machinery to run it.
13
+ #
14
+ # cron = Cron.new
15
+ # # Or
16
+ # cron = Cron.new(job1,job2,...)
17
+ #
18
+ # To start cron:
19
+ #
20
+ # cron.start
21
+ #
22
+ # Jobs are instances of CronJob (although these are just glorified
23
+ # hashes).
24
+ # In fact, Cron expects job instances to respond to:
25
+ #
26
+ # runnable?(time) => if we can run now
27
+ # once? => this job is a one-off
28
+ #
29
+ # @see CronJob
30
+ #
31
+ # Cron uses ruby's Time object to establish what the curren time is.
32
+ #
33
+ # cron.time => current time
34
+ #
35
+ # You can however override cron's idea of the current time.
36
+ # eg using ActiveSupport time zones:
37
+ #
38
+ # cron.time {
39
+ # Time.use_zone("Sydney") {
40
+ # Time.zone.now
41
+ # }
42
+ # }
43
+ #
44
+ # cron.time => <the time in new timezone>
45
+ #
46
+ # To alter cron after it's started (remember, it's just an array):
47
+ #
48
+ # cron.suspend {|arr|
49
+ # ... do something with cron ...
50
+ # ... arr == this cron instance ...
51
+ # arr << cron_job # for example
52
+ # }
53
+ #
54
+ # #suspend will wait for the thread dispatching jobs to be safely
55
+ # sleeping.
56
+ #
57
+ # To gracefully stop cron:
58
+ #
59
+ # cron.stop {
60
+ # ... do something now we've stopped ...
61
+ # }
62
+ #
63
+ #
64
+ # RUNNABLE ITEMS
65
+ #
66
+ # If a runnable item carries a proc, we don't run it - the thread
67
+ # used in Cron is just for doing cron.
68
+ # So runnable items are basically enqueued to @schedule.
69
+ # @schedule is just a queue.
70
+ # You can create an thread and have it dequeue this queue.
71
+ # You can also replace queue with something. It should respond
72
+ # to #enq and #deq and be thread-safe. One example might be
73
+ # to have #enq insert a record into a table.
74
+
75
+ class Cron < Array
76
+
77
+ attr_reader :thread,:mutex,:stopped,:suspended
78
+ attr_accessor :debug,:queue
79
+
80
+ # *items should consist of 0 or more items of form:
81
+ # [job_id(string),CronJob.new(...),thing]
82
+
83
+ def initialize *jobs
84
+ super()
85
+ @stopped = true
86
+ @suspended = false
87
+ @queue = Queue.new
88
+ jobs.each{|job|
89
+ self.push(job)
90
+ }
91
+ @mutex = Mutex.new
92
+ end
93
+
94
+ # Get current time.
95
+ #
96
+ # If passed a block, the block will be used to get the time.
97
+
98
+ def time &block
99
+ if block_given? then
100
+ @time = block
101
+ else
102
+ @time ||= lambda{Time.now}
103
+ @time.call
104
+ end
105
+ end
106
+
107
+ # Check each item in this array, if runnable push it on a queue.
108
+ #
109
+ # We assume that this item is or simlar to CronJob. We don't call
110
+ # cron_job#job. That is the work for another thread esp. if #job
111
+ # is a Proc.
112
+
113
+ def run time=nil
114
+
115
+ puts "[cron] run called #{Time.now}" if @debug
116
+ time = self.time if time.nil?
117
+ self.each{|cron_job|
118
+ ok,details = cron_job.runnable?(time)
119
+ if ok then
120
+ @queue.enq(cron_job)
121
+ if cron_job.once? then
122
+ self.delete(cron_job)
123
+ end
124
+ end
125
+ }
126
+
127
+ end
128
+
129
+ # Start cron.
130
+ #
131
+ # Will wake up every minute and perform #run.
132
+
133
+ def start debug=false,method=:every_minute,*args
134
+ @stopped = false
135
+ @suspended = false
136
+ @dead = Queue.new
137
+ @thread = CronR::Utils.send(method,debug,*args) {
138
+ time = self.time
139
+ @mutex.synchronize {
140
+ if @stopped then
141
+ # It's important we put something on this queue ONLY AFTER
142
+ # we've acquired the mutex...
143
+ @dead.enq(true)
144
+ true
145
+ elsif @suspended then
146
+ else
147
+ self.run(time)
148
+ end
149
+ }
150
+ }
151
+ end
152
+
153
+ # Suspend the thread.
154
+ #
155
+ # If block is given, it will only be called once the thread is
156
+ # sleeping again.
157
+ # You can use this to safely modify this array.
158
+
159
+ def suspend &block
160
+ if block_given? then
161
+ @mutex.synchronize {
162
+ begin
163
+ @suspended = true
164
+ block.call(self)
165
+ ensure
166
+ @suspended = false
167
+ end
168
+ }
169
+ end
170
+ end
171
+
172
+ # Gracefully stop the thread.
173
+ #
174
+ # If block is given, it will be called once the thread has
175
+ # stopped.
176
+
177
+ def stop &block
178
+ if block_given? then
179
+ @stopped = true
180
+ @suspended = false
181
+ # Wait till something is put on the dead queue...
182
+ # This stops us from acquiring the mutex until after @thread
183
+ # has processed @stopped set to true.
184
+ sig = @dead.deq
185
+ # The cron thread should be dead now, or wrapping up (with the
186
+ # acquired mutex)...
187
+ @mutex.synchronize {
188
+ while @thread.alive?
189
+ sleep 0.2
190
+ end
191
+ block.call(self)
192
+ }
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+ end
@@ -0,0 +1,140 @@
1
+ # The files in this directory are part of CronR, a ruby library.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the MIT License. A
4
+ # copy of the license should be enclosed with this project in the file
5
+ # LICENSE.txt.
6
+
7
+ module CronR
8
+
9
+ # An instance (cp) of this class (a subclass of hash) represents the
10
+ # standard cron parameters along with an id and something
11
+ # representing the job (eg a lambda).
12
+ #
13
+ # cp = CronJob.new('id1')
14
+ # => '* * * * *' cron job id1 with no job
15
+ # cp = CronJob.new('id2') {... a proc...}
16
+ # => '* * * * *' cron job id1 with a proc
17
+ # => #job => will 'call' the proc
18
+ # cp = CronJob.new('id2')
19
+ # cp.job = {...} => a non-proc job
20
+ #
21
+ # CRON PARAMETERS:
22
+ #
23
+ # cp[s] where s can be :minute,:hour,:day,:dow,:month .
24
+ #
25
+ # cp[s] = true
26
+ # ie '*' in a crontab
27
+ # cp[s] = i (i.kind_of?(Fixnum))
28
+ # => for minutes we have 0-5, hours 0-23, etc
29
+ # cp[s] = [i,...] (array)
30
+ # => cp[:minute] =
31
+ # cp[s] = (0..58).step(2)
32
+ # => equivalent to */2 in cron
33
+ #
34
+ # For each component: [:minute,:hour,:day,:dow,:month] we can
35
+ # then interpret the settings as:
36
+ #
37
+ # [true,true,true,true,true]
38
+ # => run every minute of every hour of every month of every day of
39
+ # the week
40
+ # [5,true,true,true,true]
41
+ # => run 5 minutes past the hour
42
+ # [(0..55).step(5),true,true,true,true]
43
+ # => run every 5 minutes
44
+ # [[10,30,50],true,true,true,true]
45
+ # => run on 10th, 30th and 50th minute of the hour
46
+
47
+ class CronJob < Hash
48
+
49
+ def initialize id,minute=true,hour=true,day=true,month=true,dow=true,&block
50
+ super() {nil}
51
+ self.set(minute,hour,day,month,dow)
52
+ self[:id] = id
53
+ if block_given? then
54
+ self.job &block
55
+ end
56
+ end
57
+
58
+ def set minute=true,hour=true,day=true,month=true,dow=true
59
+ self[:minute] = minute
60
+ self[:hour] = hour
61
+ self[:day] = day
62
+ self[:month] = month
63
+ self[:dow] = dow # 0=sunday,...,6=Saturday
64
+ end
65
+
66
+ # Get job or set job via block.
67
+ #
68
+ # cj.job
69
+ # cj.job {|cj|...}
70
+
71
+ def job &block
72
+ if block_given? then
73
+ #self[:job] = block.call(self)
74
+ self[:job] = block
75
+ else
76
+ self[:job]
77
+ end
78
+ end
79
+
80
+ def job= thing
81
+ self[:job] = thing
82
+ end
83
+
84
+ # Run the job.
85
+ #
86
+ # This is a convenience method to handle calling proc based
87
+ # :job's.
88
+
89
+ def run
90
+ case self[:job]
91
+ when Proc
92
+ self[:job].call
93
+ else
94
+ self[:job]
95
+ end
96
+ end
97
+
98
+ # Return true if job is runnable at the given time.
99
+ #
100
+ # Note we expect an instance of Time.
101
+ # ActiveSupport can be used to give us time zones in Time.
102
+ #
103
+ # Example
104
+ # ok,details = runnable?(Time.now)
105
+
106
+ def runnable? time
107
+ result = [:minute,:hour,:day,:dow,:month].map{|ct|
108
+ if self[ct] == true then
109
+ true
110
+ else
111
+ case ct
112
+ when :month,:day,:hour
113
+ val = time.send(ct)
114
+ when :dow
115
+ val = time.wday
116
+ when :minute
117
+ val = time.min
118
+ end
119
+
120
+ case self[ct]
121
+ when Numeric # Should be Fixnum
122
+ self[ct] == val
123
+ else # Assume array-like thing...
124
+ self[ct].include?(val)
125
+ end
126
+ end
127
+ }
128
+ # Everything should be true to make us eligible for running:
129
+ [result.inject(true){|s,v| s && v},result]
130
+ end
131
+
132
+ # Return true if the job is intended to only be run as a one-off.
133
+
134
+ def once?
135
+ self[:once] || false
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,124 @@
1
+ # The files in this directory are part of CronR, a ruby ibrary.
2
+ # Copyright (C) 2014 Daniel Bush
3
+ # This program is distributed under the terms of the MIT License. A
4
+ # copy of the license should be enclosed with this project in the file
5
+ # LICENSE.txt.
6
+
7
+ module CronR
8
+ module Utils
9
+
10
+ # Wake up every minute and call &block.
11
+ #
12
+ # Returns the thread that does the waking up.
13
+ # If &block returns true, then stop.
14
+
15
+ def self.every_minute debug=false,&block
16
+ mil = 1000000
17
+ secs = 60.0
18
+ Thread.new {
19
+ now = Time.now
20
+ wait = secs-now.sec-now.usec.to_f/mil
21
+ p "[every_minute] sleeping #{wait}" if debug
22
+ sleep wait
23
+ loop {
24
+ result = block.call
25
+ if result==true then
26
+ break
27
+ end
28
+ now = Time.now
29
+ wait = secs-now.sec-now.usec.to_f/mil
30
+ p "[every_minute] sleeping #{wait}" if debug
31
+ sleep(wait)
32
+ }
33
+ }
34
+ end
35
+
36
+ # Wake up every 'secs' number of seconds and call block.
37
+ #
38
+ # We measure 'secs' after the function call. So it is not aligned
39
+ # to anything.
40
+
41
+ def self.every secs,debug=false,&block
42
+ Thread.new {
43
+ loop {
44
+ result = block.call
45
+ if result==true then
46
+ break
47
+ end
48
+ p "[every] sleeping #{secs}" if debug
49
+ sleep secs
50
+ }
51
+ }
52
+ end
53
+
54
+ # Make a function that returns time to wait till the next secs-th
55
+ # second of each minute.
56
+ #
57
+ # MOTIVATION
58
+ #
59
+ # every_minute wakes up every minute on the minute.
60
+ # Can we wake up every several seconds, on the second?
61
+ # Can we break a minute up into intervals starting from the 0th
62
+ # second in the minute?
63
+ #
64
+ # l = make_waitsecs_time(3)
65
+ # wait = l.call
66
+ # sleep(wait)
67
+ # # So, right *now*:
68
+ # # Time.now.sec ~ s where (0..57).step(3).to_a.include?(s)
69
+ #
70
+
71
+ def self.make_waitsecs_time secs
72
+ if secs < 1 || secs > 60 then
73
+ raise "secs must be 1-60"
74
+ end
75
+ mil = 1000000
76
+ lambda{|time=nil|
77
+ time = Time.now if time.nil?
78
+ sec = time.sec
79
+ usec = time.usec
80
+ frac = usec.to_f/mil
81
+ remainder = secs - ((sec+1) % secs)
82
+ remainder -= frac
83
+ if remainder < 0
84
+ # TODO if frac is almost zero, return zero here?
85
+ remainder + secs
86
+ else
87
+ remainder
88
+ end
89
+ }
90
+ end
91
+
92
+ # Make a function that returns time to wait till the next secs-th
93
+ # second of the unix epoch.
94
+ #
95
+ # MOTIVATION
96
+ #
97
+ # make_waitsecs_time breaks up the minute starting from the 0th second
98
+ # and the maximum cycle is therefore 60.
99
+ # Here we can apply '% secs' for arbitrary value 'secs' up to the current
100
+ # epoch, although such large values would be of little use.
101
+
102
+ def self.make_waitsecs_epoch secs
103
+ if secs < 1 then
104
+ raise "secs must be > 1"
105
+ end
106
+ mil = 1000000
107
+ lambda{|time=nil|
108
+ time = Time.now if time.nil?
109
+ sec = time.to_i # unix timestamp
110
+ usec = time.tv_usec # fraction unix component
111
+ frac = usec.to_f/mil
112
+ remainder = secs - ((sec+1) % secs)
113
+ remainder -= frac
114
+ if remainder < 0
115
+ # TODO if frac is almost zero, return zero here?
116
+ remainder + secs
117
+ else
118
+ remainder
119
+ end
120
+ }
121
+ end
122
+
123
+ end
124
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: CronR
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Bush
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Simple, thread-based, light-weight cron implementation.
14
+ email: dlb.id.au@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/CronR.rb
20
+ - lib/CronR/utils.rb
21
+ - lib/CronR/CronJob.rb
22
+ - lib/CronR/Cron.rb
23
+ homepage: http://github.com/danielbush/RubyCron
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.1.10
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: An implementation of cron in ruby.
47
+ test_files: []