./lib/schedulero.rb in schedulero-0.1.1 vs ./lib/schedulero.rb in schedulero-0.2.3

- old
+ new

@@ -1,112 +1,161 @@ require 'pathname' require 'json' require 'logger' require 'colorize' require 'as-duration' +require 'pp' +require_relative './utils' + class Schedulero + include Schedulero::Utils + + attr_reader :tasks, :logger + def initialize state_file: nil, log_file: true + init_log log_file + init_state state_file + @tasks = {} + @running = {} + @count = 0 + end + + def init_state state_file + # state file + state_file ||= "./tmp/schedulero.json" + puts 'State file: %s' % state_file + + @state_file = Pathname.new state_file + @state_file.write '{}' unless @state_file.exist? + end + + def init_log log_file # log file log_file = case log_file when String log_file - when true - './log/schedulero.log' when false nil + else + "./log/schedulero.log'" end - @logger = Logger.new log_file - @logger.datetime_format = "%Y-%m-%d %H:%M:%S" + puts 'Log file : %s' % log_file - # state file - unless state_file - state_file = 'schedulero.json' - state_file = Dir.exists?('./tmp') ? "./tmp/#{state_file}" : state_file + @logger = Logger.new log_file + @logger.formatter = proc do |severity, datetime, progname, msg| + severity = severity == 'INFO' ? '' : "(#{severity}) " + "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}]: #{severity}#{msg}\n" end + end - @state_file = Pathname.new state_file + # add task + def every name, seconds, proc=nil, &block + proc ||= block + @tasks[name] = { interval: seconds , func: proc, name: name } + end - if @state_file.exist? - @state = JSON.load @state_file.read - else - @state = {} + # run task at specific hours + def at name, hours, proc=nil, &block + proc ||= block + @tasks[name] = { at: hours , func: proc, name: name } + end + + def run_forever interval: 3 + Thread.new do + loop do + puts 'looping ...' + run + sleep interval + end end end - def run &block - @logger.info 'running...' + # run all tasks once, safe + def run + state = JSON.load @state_file.read - instance_exec &block + state['_pid'] ||= Process.pid + state['_last_run'] ||= Time.now.to_i + diff = Time.now.to_i - state['_last_run'] - @state_file.write @state.to_json - end + # if another process is controlling state, exit + if state['_pid'] != Process.pid && diff < 10 + puts "Another process [#{state['_pid']}] is controlling state before #{diff} sec, skipping. I am (#{Process.pid})".red + return + end - def every name, seconds, &block - @state[name] ||= 0 + for name, block in @tasks + state[name] ||= 0 + now = Time.now.to_i - now = Time.now.to_i - diff = (@state[name] + seconds.to_i) - now + if block[:at] + # run at specific times + hour_now = Time.now.hour + hours = block[:at].class == Array ? block[:at] : [block[:at]] - if diff < 0 - puts 'running "%s"' % name.green + if hours.include?(hour_now) && (Time.now.to_i - state[name] > 3700) + state[name] = now + safe_run block + end + else + # run in intervals + seconds = block[:interval] + diff = (state[name].to_i + seconds.to_i) - now - @state[name] = now - - begin - @logger.info 'Run: %s' % name - yield - rescue - log_errror name + if diff < 0 + state[name] = now + safe_run block + else + puts 'skipping "%s" for %s' % [name, humanize_seconds(diff)] + end end - else - puts 'skipping "%s" for %s' % [name, humanize_seconds(diff)] end + + state['_last_run'] = Time.now.to_i + state['_pid'] = Process.pid + + @state_file.write state.to_json end - def at name, hours, &block - @state[name] ||= 0 + # run in rescue mode, kill if still running + def safe_run block + name = block[:name] - hour_now = Time.now.hour - hours = [hours] unless hours.class == Array + puts 'Running "%s"' % name.green + @logger.info 'Run: %s' % name - if hours.include?(hour_now) && (Time.now.to_i - @state[name] > 3700) - puts 'running "%s"' % name.green + if block[:running] + log_errror "Task [#{block[:name]}] is still running, killing..." + Thread.kill(block[:running]) + end - @state[name] = Time.now.to_i + thread = Thread.start(block) do |b| + block[:running] = thread begin - @logger.info 'Run: %s' % name - yield + @count += 1 + b[:func].call @count rescue - log_errror name + log_errror b[:name] end - else - puts 'skipping "%s" at %d, running in %s' % [name, hour_now, hours] + + b[:running] = false end end + # show and log error def log_errror name - msg = '%s: %s (%s)' % [name, $!.message, $!.class] + msg = if $! + '%s: %s (%s)' % [name, $!.message, $!.class] + else + name + end + puts msg.red - Dir.mkdir('./log') unless Dir.exist?('./log') - @logger.error(msg) - end - - def humanize_seconds secs - return '-' unless secs - - secs = secs.to_i - - [[60, :sec], [60, :min], [24, :h], [356, :days], [1000, :years]].map{ |count, name| - if secs > 0 - secs, n = secs.divmod(count) - "#{n.to_i} #{name}" - end - }.compact.reverse.slice(0,2).join(' ') end end