sshaw/itunes_store_transporter_web

View on GitHub
models/transporter_job.rb

Summary

Maintainability
B
5 hrs
Test Coverage
require "fileutils"
require "delayed_job"
require "itunes/store/transporter"
require "itunes/store/transporter/errors"
require "itunes/store/transporter/web/search"

class TransporterJob < ActiveRecord::Base
  include ITunes::Store::Transporter::Web

  STATES = [:queued, :running, :success, :failure]
  STATES.each do |s|
    define_method("#{s}!") { update_attribute(:state, s) }
    define_method("#{s}?") { state == s }
  end

  PRIORITY = Hash.new(0).merge!(:high => -1, :normal => 0, :low => 1)
  NON_DUP_ATTRS = [:exceptions, :job_id, :output_log_file, :result, :state].freeze

  # TODO: params in controller
  #attr_protected :state, :target, :job_id

  serialize :result
  serialize :options, Hash
  serialize :exceptions

  validates :account_id, :presence => true

  belongs_to :account

  before_save :typecast_options, :set_target

  after_create  :enqueue_delayed_job
  after_destroy :dequeue_delayed_job, :remove_log

  scope :completed, lambda { where(:state => [:success, :failure]) }

  def self.search(params)
    Search::Order.new(
      Search::Where.new(self).build(params)
    ).build(params)
  end

  def command
    self.class.to_s.split(/::/)[-1].sub(/Job/, "")
  end

  def output?
    log.nil? || !File.exists?(log) ? false : File.size?(log) > 0
  end

  def output(offset = 0)
    data = ""
    if log && File.exists?(log)
      File.open(log, "r") do |f|
    f.seek(offset.to_i)
    data = f.read
      end
    end
    data
  end

  # Why not just use command?
  def type
    self[:type].sub(/Job$/, "") if self[:type]
  end

  def state
    self[:state].to_sym if !self[:state].blank?
  end

  def completed?
    success? || failure?
  end

  # Mixin...
  def haml_object_ref
    "job"
  end

  # @job.execute
  # @job.delay.execute
  # def execute
  # end

  def perform
    save! if new_record?
    options[:log] = log
    running!
    update_attribute(:result, run)
  ensure
    options.delete(:log)
  end

  def queue_name
    "jobs".freeze
  end

  # job is Delayed::Backend::ActiveRecord::Job
  def enqueue(job)
    queued!
  end

  def success(job)
    success!
  end

  def error(job, exception)
    update_attributes(:state => :failure, :exceptions => exception)
  end

  def failure
    failure!
  end

  def target
    _target
  end

  def priority
    self[:priority].respond_to?(:to_sym) ? self[:priority].to_sym : :normal
  end

  def as_json(options = nil)
    return super if options

    json = super(:except => [:job_id, :target, :output_log_file])
    if json["options"]
      json["options"].delete(:log)
      json["options"][:password] = "*" * 8 if json["options"][:password]
    end

    json.merge!("type" => type.try(:downcase), "exceptions" => exceptions ? exceptions.to_s : nil)
  end

  def to_json(options = nil)
    options ? super : as_json.to_json
  end

  def to_s
    return "" unless type

    s = "#{type} Job"
    s << ": #{target}" if target.present?
    s
  end

  def initialize_copy(other)
    copy = super
    NON_DUP_ATTRS.each { |name| copy[name] = nil }
    copy
  end

  protected

  def set_target
    self[:target] = target
  end

  def numeric_priority
    # Lower number == higher priority
    priority == :next ?
      [ Delayed::Job.minimum(:priority).to_i, 0 ].min - 1 :
      PRIORITY[priority]
  end

  def enqueue_delayed_job
    transaction do
      # Uh, why not just has_one..?
      job = Delayed::Job.enqueue(self, :priority => numeric_priority)
      update_column(:job_id, job.id)
    end
  end

  def dequeue_delayed_job
    # what if running... raise error?
    Delayed::Job.delete(job_id) if queued?
  end

  def _target
    # An ID to denote the job's target. E.g., package name, apple id, ...
  end

  # TODO: this should probably be moved to a the form class
  def typecast_options
    # For subclasses
  end

  def to_bool(val)
    case val
      when String
    val == "true" || val == "1" ? true : false
      when Fixnum
    val == 1 ? true : false
      else
    val
    end
  end

  def log
    if id?
      @log ||= File.join(config.output_log_directory || ".", "#{id}.log")
    end
  end

  def remove_log
    FileUtils.rm_f(log) if log && File.file?(log)
  end

  def itms
    @itms ||= ITunes::Store::Transporter.new(:path => config.path,
                         :print_stdout => true,
                         :print_stderr => true)
  end

  def config
    @confg ||= AppConfig.first_or_initialize
  end
end