backgroundrb-rails3 1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +17 -0
- data/ChangeLog +50 -0
- data/Gemfile +11 -0
- data/LICENSE +4 -0
- data/MIT-LICENSE +20 -0
- data/README +22 -0
- data/Rakefile +128 -0
- data/TODO.org +5 -0
- data/app/controller/backgroundrb_status_controller.rb +6 -0
- data/backgroundrb-rails3.gemspec +219 -0
- data/config/backgroundrb.yml +11 -0
- data/doc/Rakefile +5 -0
- data/doc/config.yaml +2 -0
- data/doc/content/advanced/advanced.txt +76 -0
- data/doc/content/advanced/advanced.yaml +4 -0
- data/doc/content/bugs/bugs.txt +20 -0
- data/doc/content/bugs/bugs.yaml +5 -0
- data/doc/content/community/community.txt +36 -0
- data/doc/content/community/community.yaml +5 -0
- data/doc/content/content.txt +168 -0
- data/doc/content/content.yaml +5 -0
- data/doc/content/faq/faq.txt +41 -0
- data/doc/content/faq/faq.yaml +5 -0
- data/doc/content/rails/rails.txt +182 -0
- data/doc/content/rails/rails.yaml +5 -0
- data/doc/content/scheduling/scheduling.txt +166 -0
- data/doc/content/scheduling/scheduling.yaml +5 -0
- data/doc/content/workers/workers.txt +178 -0
- data/doc/content/workers/workers.yaml +5 -0
- data/doc/layouts/default/default.erb +56 -0
- data/doc/layouts/default/default.yaml +4 -0
- data/doc/lib/default.rb +7 -0
- data/doc/output/Assets/BG-Ad-Top.png +0 -0
- data/doc/output/Assets/BG-Body.png +0 -0
- data/doc/output/Assets/BG-Feed.png +0 -0
- data/doc/output/Assets/BG-Menu-Hover.png +0 -0
- data/doc/output/Assets/BG-Menu.png +0 -0
- data/doc/output/Assets/BG-Sidebar-Bottom.png +0 -0
- data/doc/output/Assets/Button-Feed.png +0 -0
- data/doc/output/images/bg-ad-top.png +0 -0
- data/doc/output/images/bg-body.png +0 -0
- data/doc/output/images/bg-feed.gif +0 -0
- data/doc/output/images/bg-footer.jpg +0 -0
- data/doc/output/images/bg-header.jpg +0 -0
- data/doc/output/images/bg-menu-hover.png +0 -0
- data/doc/output/images/bg-menu.png +0 -0
- data/doc/output/images/bg-sidebar-bottom.gif +0 -0
- data/doc/output/images/button-feed.png +0 -0
- data/doc/output/images/icon-comment.png +0 -0
- data/doc/output/images/more_icon.gif +0 -0
- data/doc/output/style.css +299 -0
- data/doc/page_defaults.yaml +13 -0
- data/doc/tasks/default.rake +3 -0
- data/doc/templates/default/default.txt +1 -0
- data/doc/templates/default/default.yaml +4 -0
- data/examples/backgroundrb.yml +25 -0
- data/examples/foo_controller.rb +48 -0
- data/examples/god_worker.rb +7 -0
- data/examples/worker_tests/god_worker_test.rb +8 -0
- data/examples/workers/error_worker.rb +17 -0
- data/examples/workers/foo_worker.rb +38 -0
- data/examples/workers/god_worker.rb +7 -0
- data/examples/workers/model_worker.rb +13 -0
- data/examples/workers/renewal_worker.rb +11 -0
- data/examples/workers/rss_worker.rb +26 -0
- data/examples/workers/server_worker.rb +31 -0
- data/examples/workers/world_worker.rb +12 -0
- data/examples/workers/xmpp_worker.rb +7 -0
- data/init.rb +7 -0
- data/install.rb +1 -0
- data/know_issues.org +5 -0
- data/lib/backgroundrb.rb +1 -0
- data/lib/backgroundrb/bdrb_client_helper.rb +8 -0
- data/lib/backgroundrb/bdrb_cluster_connection.rb +156 -0
- data/lib/backgroundrb/bdrb_config.rb +43 -0
- data/lib/backgroundrb/bdrb_conn_error.rb +29 -0
- data/lib/backgroundrb/bdrb_connection.rb +179 -0
- data/lib/backgroundrb/bdrb_job_queue.rb +79 -0
- data/lib/backgroundrb/bdrb_result.rb +19 -0
- data/lib/backgroundrb/bdrb_start_stop.rb +146 -0
- data/lib/backgroundrb/rails_worker_proxy.rb +181 -0
- data/lib/backgroundrb/railtie.rb +48 -0
- data/lib/generators/backgroundrb/bdrb_migration/USAGE +12 -0
- data/lib/generators/backgroundrb/bdrb_migration/bdrb_migration_generator.rb +15 -0
- data/lib/generators/backgroundrb/bdrb_migration/templates/migration.rb +27 -0
- data/lib/generators/backgroundrb/worker/USAGE +16 -0
- data/lib/generators/backgroundrb/worker/templates/unit_test.rb +12 -0
- data/lib/generators/backgroundrb/worker/templates/worker.rb +7 -0
- data/lib/generators/backgroundrb/worker/worker_generator.rb +14 -0
- data/lib/tasks/backgroundrb_tasks.rake +103 -0
- data/release_notes.org +48 -0
- data/release_points.org +46 -0
- data/script/backgroundrb +52 -0
- data/script/bdrb_test_helper.rb +99 -0
- data/script/load_worker_env.rb +31 -0
- data/script/monitrc +25 -0
- data/server/backgroundrb_server.rb +12 -0
- data/server/lib/bdrb_result_storage.rb +62 -0
- data/server/lib/bdrb_server_helper.rb +24 -0
- data/server/lib/bdrb_thread_pool.rb +127 -0
- data/server/lib/cron_trigger.rb +197 -0
- data/server/lib/invalid_dump_error.rb +4 -0
- data/server/lib/log_worker.rb +25 -0
- data/server/lib/master_proxy.rb +140 -0
- data/server/lib/master_worker.rb +187 -0
- data/server/lib/meta_worker.rb +432 -0
- data/server/lib/trigger.rb +34 -0
- data/test/bdrb_client_test_helper.rb +5 -0
- data/test/bdrb_test_helper.rb +35 -0
- data/test/client/backgroundrb.yml +17 -0
- data/test/client/test_bdrb_client_helper.rb +13 -0
- data/test/client/test_bdrb_cluster_connection.rb +162 -0
- data/test/client/test_bdrb_config.rb +20 -0
- data/test/client/test_bdrb_connection.rb +29 -0
- data/test/client/test_bdrb_job_queue.rb +63 -0
- data/test/client/test_worker_proxy.rb +130 -0
- data/test/server/test_cron_trigger.rb +281 -0
- data/test/server/test_master_proxy.rb +54 -0
- data/test/server/test_master_worker.rb +157 -0
- data/test/server/test_meta_worker.rb +281 -0
- data/test/server/test_result_storage.rb +14 -0
- data/test/socket_mocker.rb +34 -0
- data/test/workers/bar_worker.rb +10 -0
- data/test/workers/foo_worker.rb +10 -0
- data/uninstall.rb +1 -0
- metadata +345 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
RAILS_HOME = File.expand_path(File.join(File.dirname(__FILE__),".."))
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require "active_support"
|
7
|
+
require "active_record"
|
8
|
+
|
9
|
+
require "yaml"
|
10
|
+
require "erb"
|
11
|
+
require "logger"
|
12
|
+
require "optparse"
|
13
|
+
|
14
|
+
require RAILS_HOME + "/config/boot"
|
15
|
+
require "backgroundrb"
|
16
|
+
|
17
|
+
BDRB_HOME = ::BackgrounDRb::BACKGROUNDRB_ROOT
|
18
|
+
|
19
|
+
["server","server/lib","lib","lib/backgroundrb"].each { |x| $LOAD_PATH.unshift(BDRB_HOME + "/#{x}")}
|
20
|
+
|
21
|
+
$LOAD_PATH.unshift(File.join(RAILS_HOME,"lib","workers"))
|
22
|
+
|
23
|
+
require "bdrb_config"
|
24
|
+
|
25
|
+
BDRB_CONFIG = BackgrounDRb::Config.read_config("#{RAILS_HOME}/config/backgroundrb.yml")
|
26
|
+
|
27
|
+
if !(::Packet::WorkerRunner::WORKER_OPTIONS[:worker_env] == false)
|
28
|
+
require RAILS_HOME + "/config/environment"
|
29
|
+
end
|
30
|
+
require "backgroundrb_server"
|
31
|
+
|
data/script/monitrc
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
set daemon 60
|
2
|
+
set mailserver localhost
|
3
|
+
set mail-format {
|
4
|
+
from: sample@foobar.com
|
5
|
+
subject: Alert from Backgroundrb
|
6
|
+
}
|
7
|
+
set alert hemant@gmail.com
|
8
|
+
|
9
|
+
set httpd port 3000 and
|
10
|
+
use address localhost # only accept connection from localhost
|
11
|
+
allow localhost # allow localhost to connect to the server and
|
12
|
+
|
13
|
+
check process backgroundrb
|
14
|
+
with pidfile /home/hemant/rails_sandbox/tmp/pids/backgroundrb_11008.pid
|
15
|
+
start program = "/usr/bin/ruby /home/hemant/rails_sandbox/script/backgroundrb start"
|
16
|
+
stop program = "/usr/bin/ruby /home/hemant/rails_sandbox/script/backgroundrb stop"
|
17
|
+
if totalmem > 50.0 MB for 5 cycles then restart
|
18
|
+
if cpu usage > 95% for 3 cycles then restart
|
19
|
+
|
20
|
+
if failed port 11008 type tcp
|
21
|
+
with timeout 30 seconds
|
22
|
+
for 5 cycles
|
23
|
+
then restart
|
24
|
+
group backgroundrb
|
25
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "chronic"
|
2
|
+
require "lib/bdrb_result_storage"
|
3
|
+
require "lib/bdrb_thread_pool"
|
4
|
+
require "lib/bdrb_server_helper"
|
5
|
+
require "lib/master_worker"
|
6
|
+
require "lib/master_proxy"
|
7
|
+
require "lib/cron_trigger"
|
8
|
+
require "lib/invalid_dump_error"
|
9
|
+
require "lib/log_worker"
|
10
|
+
require "lib/trigger"
|
11
|
+
require "lib/meta_worker"
|
12
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module BackgrounDRb
|
2
|
+
class ResultStorage
|
3
|
+
attr_accessor :cache,:worker_name,:worker_key,:storage_type
|
4
|
+
def initialize(worker_name,worker_key,storage_type = nil)
|
5
|
+
@worker_name = worker_name
|
6
|
+
@worker_key = worker_key
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@storage_type = storage_type
|
9
|
+
@cache = (@storage_type == 'memcache') ? memcache_instance : {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Initialize Memcache for result or object caching
|
13
|
+
def memcache_instance
|
14
|
+
require 'memcache'
|
15
|
+
memcache_options = {
|
16
|
+
:c_threshold => 10_000,
|
17
|
+
:compression => true,
|
18
|
+
:debug => false,
|
19
|
+
:namespace => 'backgroundrb_result_hash',
|
20
|
+
:readonly => false,
|
21
|
+
:urlencode => false
|
22
|
+
}
|
23
|
+
t_cache = MemCache.new(memcache_options)
|
24
|
+
t_cache.servers = BDRB_CONFIG[:memcache].split(',')
|
25
|
+
t_cache
|
26
|
+
end
|
27
|
+
|
28
|
+
# generate key based on worker_name and worker_key
|
29
|
+
# for local cache, there is no need of unique key
|
30
|
+
def gen_key key
|
31
|
+
if storage_type == 'memcache'
|
32
|
+
key = [worker_name,worker_key,key].compact.join('_')
|
33
|
+
key
|
34
|
+
else
|
35
|
+
key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# fetch object from cache
|
40
|
+
def [] key
|
41
|
+
@mutex.synchronize { @cache[gen_key(key)] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def []= key,value
|
45
|
+
@mutex.synchronize { @cache[gen_key(key)] = value }
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete key
|
49
|
+
@mutex.synchronize { @cache.delete(gen_key(key)) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def shift key
|
53
|
+
val = nil
|
54
|
+
@mutex.synchronize do
|
55
|
+
val = @cache[key]
|
56
|
+
@cache.delete(key)
|
57
|
+
end
|
58
|
+
return val
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BackgrounDRb
|
2
|
+
module BdrbServerHelper
|
3
|
+
# Load data using Marshal.load, if load fails because of undefined constant
|
4
|
+
# try to load the constant. FIXME: regexp needs to handle all the cases.
|
5
|
+
def load_data data
|
6
|
+
begin
|
7
|
+
return Marshal.load(data)
|
8
|
+
rescue
|
9
|
+
error_msg = $!.message
|
10
|
+
if error_msg =~ /^undefined\ .+\ ([A-Z][^:]+)/
|
11
|
+
file_name = $1.underscore
|
12
|
+
begin
|
13
|
+
require file_name
|
14
|
+
return Marshal.load(data)
|
15
|
+
rescue
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
else
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end # end of load_data method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module BackgrounDRb
|
2
|
+
|
3
|
+
class InterruptedException < RuntimeError ; end
|
4
|
+
|
5
|
+
class WorkData
|
6
|
+
attr_accessor :args,:block,:job_method,:persistent_job_id,:job_key
|
7
|
+
def initialize(args,job_key,job_method,persistent_job_id)
|
8
|
+
@args = args
|
9
|
+
@job_key = job_key
|
10
|
+
@job_method = job_method
|
11
|
+
@persistent_job_id = persistent_job_id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ThreadPool
|
16
|
+
attr_accessor :size,:threads,:work_queue,:logger
|
17
|
+
attr_accessor :result_queue,:master
|
18
|
+
|
19
|
+
def initialize(master,size,logger)
|
20
|
+
@master = master
|
21
|
+
@logger = logger
|
22
|
+
@size = size
|
23
|
+
@threads = []
|
24
|
+
@work_queue = []
|
25
|
+
@mutex = Monitor.new
|
26
|
+
@cv = @mutex.new_cond
|
27
|
+
@size.times { add_thread }
|
28
|
+
end
|
29
|
+
|
30
|
+
# can be used to make a call in threaded manner
|
31
|
+
# passed block runs in a thread from thread pool
|
32
|
+
# for example in a worker method you can do:
|
33
|
+
# def user_tags url
|
34
|
+
# thread_pool.defer(:fetch_url,url)
|
35
|
+
# end
|
36
|
+
# def fetch_url(url)
|
37
|
+
# begin
|
38
|
+
# data = Net::HTTP.get(url,'/')
|
39
|
+
# File.open("#{RAILS_ROOT}/log/pages.txt","w") do |fl|
|
40
|
+
# fl.puts(data)
|
41
|
+
# end
|
42
|
+
# rescue
|
43
|
+
# logger.info "Error downloading page"
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
# you can invoke above method from rails as:
|
47
|
+
# MiddleMan.worker(:rss_worker).async_user_tags(:arg => "en.wikipedia.org")
|
48
|
+
# assuming method is defined in rss_worker
|
49
|
+
|
50
|
+
def defer(method_name,args = nil)
|
51
|
+
@mutex.synchronize do
|
52
|
+
job_key = Thread.current[:job_key]
|
53
|
+
persistent_job_id = Thread.current[:persistent_job_id]
|
54
|
+
@cv.wait_while { @work_queue.size >= size }
|
55
|
+
@work_queue.push(WorkData.new(args,job_key,method_name,persistent_job_id))
|
56
|
+
@cv.broadcast
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Start worker threads
|
61
|
+
def add_thread
|
62
|
+
@threads << Thread.new do
|
63
|
+
Thread.current[:job_key] = nil
|
64
|
+
Thread.current[:persistent_job_id] = nil
|
65
|
+
while true
|
66
|
+
begin
|
67
|
+
task = nil
|
68
|
+
@mutex.synchronize do
|
69
|
+
@cv.wait_while { @work_queue.size == 0 }
|
70
|
+
task = @work_queue.pop
|
71
|
+
@cv.broadcast
|
72
|
+
end
|
73
|
+
if task
|
74
|
+
Thread.current[:job_key] = task.job_key
|
75
|
+
Thread.current[:persistent_job_id] = task.persistent_job_id
|
76
|
+
block_result = run_task(task)
|
77
|
+
end
|
78
|
+
rescue BackgrounDRb::InterruptedException
|
79
|
+
STDERR.puts("BackgrounDRb thread interrupted: #{Thread.current.inspect}")
|
80
|
+
STDERR.flush
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# run tasks popped out of queue
|
87
|
+
def run_task task
|
88
|
+
block_arity = master.method(task.job_method).arity
|
89
|
+
begin
|
90
|
+
check_db_connection
|
91
|
+
t_data = task.args
|
92
|
+
result = nil
|
93
|
+
if block_arity != 0
|
94
|
+
result = master.send(task.job_method,task.args)
|
95
|
+
else
|
96
|
+
result = master.send(task.job_method)
|
97
|
+
end
|
98
|
+
return result
|
99
|
+
rescue BackgrounDRb::InterruptedException => e
|
100
|
+
# Don't log, just re-raise
|
101
|
+
raise e
|
102
|
+
rescue Object => bdrb_error
|
103
|
+
log_exception(bdrb_error)
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def log_exception exception_object
|
109
|
+
STDERR.puts exception_object.to_s
|
110
|
+
STDERR.puts exception_object.backtrace.join("\n")
|
111
|
+
STDERR.flush
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Periodic check for lost database connections and closed connections
|
116
|
+
def check_db_connection
|
117
|
+
begin
|
118
|
+
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
119
|
+
rescue Object => bdrb_error
|
120
|
+
log_exception(bdrb_error)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
end #end of class ThreadPool
|
126
|
+
end # end of module BackgrounDRb
|
127
|
+
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module BackgrounDRb
|
2
|
+
class CronTrigger
|
3
|
+
WDAYS = { 0 => "Sunday",1 => "Monday",2 => "Tuesday",3 => "Wednesday", 4 => "Thursday", 5 => "Friday", 6 => "Saturday" }
|
4
|
+
LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
5
|
+
CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
6
|
+
|
7
|
+
attr_reader :sec, :min, :hour, :day, :month, :wday, :year, :cron_expr
|
8
|
+
|
9
|
+
# initialize the Cron Trigger
|
10
|
+
def initialize(expr)
|
11
|
+
self.cron_expr = expr
|
12
|
+
end
|
13
|
+
|
14
|
+
# create the cron expression and populate instance variables.
|
15
|
+
def cron_expr=(expr)
|
16
|
+
@cron_expr = expr
|
17
|
+
self.sec, self.min, self.hour, self.day, self.month, self.wday, self.year = @cron_expr.split(' ')
|
18
|
+
end
|
19
|
+
|
20
|
+
def fire_after_time(p_time)
|
21
|
+
@t_sec,@t_min,@t_hour,@t_day,@t_month,@t_year,@t_wday,@t_yday,@t_idst,@t_zone = p_time.to_a
|
22
|
+
@count = 0
|
23
|
+
loop do
|
24
|
+
@count += 1
|
25
|
+
|
26
|
+
if @year && !@year.include?(@t_year)
|
27
|
+
return nil if @t_year > @year.max
|
28
|
+
@t_year = @year.detect { |y| y > @t_year }
|
29
|
+
end
|
30
|
+
|
31
|
+
# if range of months doesn't include current month, find next month from the range
|
32
|
+
unless @month.include?(@t_month)
|
33
|
+
next_month = @month.detect { |m| m > @t_month } || @month.min
|
34
|
+
@t_day,@t_hour,@t_min,@t_sec = @day.min,@hour.min,@min.min,@sec.min
|
35
|
+
if next_month < @t_month
|
36
|
+
@t_month = next_month
|
37
|
+
@t_year += 1
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
@t_month = next_month
|
41
|
+
end
|
42
|
+
|
43
|
+
if !day_restricted? && wday_restricted?
|
44
|
+
unless @wday.include?(@t_wday)
|
45
|
+
next_wday = @wday.detect { |w| w > @t_wday} || @wday.min
|
46
|
+
@t_hour,@t_min,@t_sec = @hour.min,@min.min,@sec.min
|
47
|
+
t_time = Chronic.parse("next #{WDAYS[next_wday]}",:now => current_time)
|
48
|
+
@t_day,@t_month,@t_year = t_time.to_a[3..5]
|
49
|
+
@t_wday = next_wday
|
50
|
+
retry
|
51
|
+
end
|
52
|
+
elsif !wday_restricted? && day_restricted?
|
53
|
+
day_range = (1.. month_days(@t_year,@t_month))
|
54
|
+
# day array, that includes days which are present in current month
|
55
|
+
day_array = @day.select { |d| day_range === d }
|
56
|
+
unless day_array.include?(@t_day)
|
57
|
+
next_day = day_array.detect { |d| d > @t_day } || day_array.min
|
58
|
+
@t_hour,@t_min,@t_sec = @hour.min,@min.min,@sec.min
|
59
|
+
if !next_day || next_day < @t_day
|
60
|
+
t_time = Chronic.parse("next month",:now => current_time)
|
61
|
+
@t_day = next_day.nil? ? @day.min : next_day
|
62
|
+
@t_month,@t_year = t_time.month,t_time.year
|
63
|
+
retry
|
64
|
+
end
|
65
|
+
@t_day = next_day
|
66
|
+
end
|
67
|
+
else
|
68
|
+
# if both day and wday are restricted cron should give preference to one thats closer to current time
|
69
|
+
day_range = (1 .. month_days(@t_year,@t_month))
|
70
|
+
day_array = @day.select { |d| day_range === d }
|
71
|
+
if !day_array.include?(@t_day) && !@wday.include?(@t_wday)
|
72
|
+
next_day = day_array.detect { |d| d > @t_day } || day_array.min
|
73
|
+
next_wday = @wday.detect { |w| w > @t_wday } || @wday.min
|
74
|
+
@t_hour,@t_min,@t_sec = @hour.min,@min.min,@sec.min
|
75
|
+
|
76
|
+
# if next_day is nil or less than @t_day it means that it should run in next month
|
77
|
+
if !next_day || next_day < @t_day
|
78
|
+
next_time_mday = Chronic.parse("next month",:now => current_time)
|
79
|
+
else
|
80
|
+
@t_day = next_day
|
81
|
+
next_time_mday = current_time
|
82
|
+
end
|
83
|
+
next_time_wday = Chronic.parse("next #{WDAYS[next_wday]}",:now => current_time)
|
84
|
+
if next_time_mday < next_time_wday
|
85
|
+
@t_day,@t_month,@t_year = next_time_mday.to_a[3..5]
|
86
|
+
else
|
87
|
+
@t_day,@t_month,@t_year = next_time_wday.to_a[3..5]
|
88
|
+
end
|
89
|
+
retry
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
unless @hour.include?(@t_hour)
|
94
|
+
next_hour = @hour.detect { |h| h > @t_hour } || @hour.min
|
95
|
+
@t_min,@t_sec = @min.min,@sec.min
|
96
|
+
if next_hour < @t_hour
|
97
|
+
@t_hour = next_hour
|
98
|
+
next_day = Chronic.parse("next day",:now => current_time)
|
99
|
+
@t_day,@t_month,@t_year,@t_wday = next_day.to_a[3..6]
|
100
|
+
retry
|
101
|
+
end
|
102
|
+
@t_hour = next_hour
|
103
|
+
end
|
104
|
+
|
105
|
+
unless @min.include?(@t_min)
|
106
|
+
next_min = @min.detect { |m| m > @t_min } || @min.min
|
107
|
+
@t_sec = @sec.min
|
108
|
+
if next_min < @t_min
|
109
|
+
@t_min = next_min
|
110
|
+
next_hour = Chronic.parse("next hour",:now => current_time)
|
111
|
+
@t_hour,@t_day,@t_month,@t_year,@t_wday = next_hour.to_a[2..6]
|
112
|
+
retry
|
113
|
+
end
|
114
|
+
@t_min = next_min
|
115
|
+
end
|
116
|
+
|
117
|
+
unless @sec.include?(@t_sec)
|
118
|
+
next_sec = @sec.detect { |s| s > @t_sec } || @sec.min
|
119
|
+
if next_sec < @t_sec
|
120
|
+
@t_sec = next_sec
|
121
|
+
next_min = Chronic.parse("next minute",:now => current_time)
|
122
|
+
@t_min,@t_hour,@t_day,@t_month,@t_year,@t_wday = next_min.to_a[1..6]
|
123
|
+
retry
|
124
|
+
end
|
125
|
+
@t_sec = next_sec
|
126
|
+
end
|
127
|
+
break
|
128
|
+
end # end of loop do
|
129
|
+
current_time
|
130
|
+
end
|
131
|
+
|
132
|
+
def current_time
|
133
|
+
Time.local(@t_sec,@t_min,@t_hour,@t_day,@t_month,@t_year,@t_wday,nil,@t_idst,@t_zone)
|
134
|
+
end
|
135
|
+
|
136
|
+
def day_restricted?
|
137
|
+
return !@day.eql?(1..31)
|
138
|
+
end
|
139
|
+
|
140
|
+
def wday_restricted?
|
141
|
+
return !@wday.eql?(0..6)
|
142
|
+
end
|
143
|
+
|
144
|
+
# TODO: mimic attr_reader to define all of these
|
145
|
+
def sec=(sec); @sec = parse_part(sec, 0 .. 59); end
|
146
|
+
|
147
|
+
def min=(min); @min = parse_part(min, 0 .. 59); end
|
148
|
+
|
149
|
+
def hour=(hour); @hour = parse_part(hour, 0 .. 23); end
|
150
|
+
|
151
|
+
def day=(day)
|
152
|
+
@day = parse_part(day, 1 .. 31)
|
153
|
+
end
|
154
|
+
|
155
|
+
def month=(month)
|
156
|
+
@month = parse_part(month, 1 .. 12)
|
157
|
+
end
|
158
|
+
|
159
|
+
def year=(year)
|
160
|
+
@year = parse_part(year)
|
161
|
+
end
|
162
|
+
|
163
|
+
def wday=(wday)
|
164
|
+
@wday = parse_part(wday, 0 .. 6)
|
165
|
+
end
|
166
|
+
private
|
167
|
+
def month_days(y, m)
|
168
|
+
if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
|
169
|
+
LeapYearMonthDays[m-1]
|
170
|
+
else
|
171
|
+
CommonYearMonthDays[m-1]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# 0-5,8,10; 0-5; *; */5
|
176
|
+
def parse_part(part, range=nil)
|
177
|
+
return range if part.nil? or part == '*' or part =~ /^[*0]\/1$/
|
178
|
+
|
179
|
+
r = Array.new
|
180
|
+
part.split(',').each do |p|
|
181
|
+
if p =~ /-/ # 0-5
|
182
|
+
r << Range.new(*(p.scan(/\d+/).map { |x| x.to_i })).map { |x| x.to_i }
|
183
|
+
elsif p =~ /(\*|\d+)\/(\d+)/ && range # */5, 2/10
|
184
|
+
min = $1 == '*' ? 0 : $1.to_i
|
185
|
+
inc = $2.to_i
|
186
|
+
(min .. range.end).each_with_index do |x, i|
|
187
|
+
r << (range.begin == 1 ? x + 1 : x) if i % inc == 0
|
188
|
+
end
|
189
|
+
else
|
190
|
+
r << p.to_i
|
191
|
+
end
|
192
|
+
end
|
193
|
+
r.flatten
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|