class Schedulero

Attributes

logger[R]
tasks[R]

Public Class Methods

new(state_file: nil, log_file: true, silent: false) click to toggle source
# File lib/schedulero.rb, line 15
def initialize state_file: nil, log_file: true, silent: false
  @silent  = silent
  @tasks   = {}
  @running = {}
  @count   = 0

  init_log   log_file
  init_state state_file

end

Public Instance Methods

at(name, hours, proc=nil, &block) click to toggle source

run task at specific hours

# File lib/schedulero.rb, line 67
def at name, hours, proc=nil, &block
  proc ||= block
  @tasks[name] = { at: hours , func: proc, name: name }
end
every(name, seconds, proc=nil, &block) click to toggle source

add task

# File lib/schedulero.rb, line 61
def every name, seconds, proc=nil, &block
  proc ||= block
  @tasks[name] = { interval: seconds , func: proc, name: name }
end
init_log(log_file) click to toggle source
# File lib/schedulero.rb, line 35
def init_log log_file
  # log file
  @log_file = case log_file
    when String
      log_file
    when false
      nil
    else
      "./log/schedulero.log"
  end

  show 'Log file  : %s' % @log_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
init_state(state_file) click to toggle source
# File lib/schedulero.rb, line 26
def init_state state_file
  # state file
  state_file ||= "./tmp/schedulero.json"
  show 'State file: %s' % state_file

  @state_file = Pathname.new state_file
  @state_file.write '{}' unless @state_file.exist?
end
log_errror(name) click to toggle source

show and log error

# File lib/schedulero.rb, line 156
def log_errror name
  msg  = if $!
    '%s: %s (%s)' % [name, $!.message, $!.class]
  else
    name
  end

  show msg.red

  @logger.error(msg)
end
run() click to toggle source

run all tasks once, safe

# File lib/schedulero.rb, line 83
def run
  state = JSON.load @state_file.read

  state['_pid']      ||= Process.pid
  state['_last_run'] ||= Time.now.to_i
  diff = Time.now.to_i - state['_last_run']

  # if another process is controlling state, exit
  if state['_pid'] != Process.pid && diff < 10
    show "Another process [#{state['_pid']}] is controlling state before #{diff} sec, skipping. I am (#{Process.pid})".red
    return
  end

  for name, block in @tasks
    state[name] ||= 0
    now           = Time.now.to_i

    if block[:at]
      # run at specific times
      hour_now = Time.now.hour
      hours    = block[:at].class == Array ? block[:at] : [block[:at]]

      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

      if diff < 0
        state[name] = now
        safe_run block
      else
        show 'skipping "%s" for %s' % [name, humanize_seconds(diff)]
      end
    end
  end

  state['_last_run'] = Time.now.to_i
  state['_pid']      = Process.pid

  @state_file.write state.to_json
end
run_forever(interval: 3) click to toggle source
# File lib/schedulero.rb, line 72
def run_forever interval: 3
  Thread.new do
    loop do
      show 'looping ...'
      run
      sleep interval
    end
  end
end
safe_run(block) click to toggle source

run in rescue mode, kill if still running

# File lib/schedulero.rb, line 130
def safe_run block
  name = block[:name]

  show 'Running "%s"' % name.green
  @logger.info 'Run: %s' % name

  if block[:running]
    log_errror "Task [#{block[:name]}] is still running, killing..."
    Thread.kill(block[:running])
  end

  thread = Thread.start(block) do |b|
    block[:running] = thread

    begin
      @count += 1
      b[:func].call @count
    rescue
      log_errror b[:name]
    end

    b[:running] = false
  end
end
show(text) click to toggle source
# File lib/schedulero.rb, line 55
def show text
  return if @silent
  puts 'Schedulero: %s' % text
end