job.rb - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
 (HTM) git clone git://jay.scot/warvox
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
       job.rb (5144B)
       ---
            1 # == Schema Information
            2 #
            3 # Table name: jobs
            4 #
            5 #  id           :integer          not null, primary key
            6 #  created_at   :datetime
            7 #  updated_at   :datetime
            8 #  project_id   :integer          not null
            9 #  locked_by    :string(255)
           10 #  locked_at    :datetime
           11 #  started_at   :datetime
           12 #  completed_at :datetime
           13 #  created_by   :string(255)
           14 #  task         :string(255)      not null
           15 #  args         :binary
           16 #  status       :string(255)
           17 #  error        :text
           18 #  progress     :integer          default(0)
           19 #
           20 
           21 class Job < ApplicationRecord
           22 
           23   reportable :hourly, aggregation: :count, grouping: :hour, date_column: :created_at, cacheable: false
           24   reportable :daily, aggregation: :count, grouping: :day, date_column: :created_at, cacheable: false
           25   reportable :weeky, aggregation: :count, grouping: :week, date_column: :created_at, cacheable: false
           26   reportable :monthly, aggregation: :count, grouping: :month, date_column: :created_at, cacheable: false
           27 
           28   class JobValidator < ActiveModel::Validator
           29     def validate(record)
           30       case record.task
           31       when 'dialer'
           32 
           33         cracked_range = WarVOX::Phone.crack_mask(record.range) rescue []
           34         unless cracked_range.length > 0
           35           record.errors[:range] << "No valid ranges were specified"
           36         end
           37 
           38         cracked_mask = WarVOX::Phone.crack_mask(record.cid_mask) rescue []
           39         unless cracked_mask.length > 0
           40           record.errors[:cid_mask] << "No valid Caller ID mask was specified"
           41         end
           42 
           43         unless record.seconds.to_i > 0 and record.seconds.to_i < 300
           44           record.errors[:seconds] << "Seconds should be between 1 and 300"
           45         end
           46 
           47         unless record.lines.to_i > 0 and record.lines.to_i < 10000
           48           record.errors[:lines] << "Lines should be between 1 and 10,000"
           49         end
           50 
           51         $stderr.puts "Errors: #{record.errors.map{|x| x.inspect}}"
           52 
           53       when 'analysis'
           54         unless ['calls', 'job', 'project', 'global'].include?(record.scope)
           55           record.errors[:scope] << "Scope must be calls, job, project, or global"
           56         end
           57         if record.scope == "job" and Job.where(id: record.target_id.to_i, task: ['import', 'dialer']).count == 0
           58           record.errors[:job_id] << "The job_id is not valid"
           59         end
           60         if record.scope == "project" and Project.where(id: record.target_id.to_i).count == 0
           61           record.errors[:project_id] << "The project_id is not valid"
           62         end
           63         if record.scope == "calls" and (record.target_ids.nil? or record.target_ids.length == 0)
           64           record.errors[:target_ids] << "The target_ids list is empty"
           65         end
           66       when 'import'
           67       else
           68         record.errors[:base] << "Invalid task specified"
           69       end
           70     end
           71   end
           72 
           73   # XXX: Purging a single job will be slow, but deleting the project is fast
           74   has_many :calls, dependent: :destroy
           75 
           76   belongs_to :project
           77 
           78   validates_presence_of :project_id
           79 
           80   # Allow the base Job class to be used for Dial Jobs
           81   attr_accessor :range
           82   attr_accessor :range_file
           83   attr_accessor :lines
           84   attr_accessor :seconds
           85   attr_accessor :cid_mask
           86 
           87   attr_accessor :scope
           88   attr_accessor :force
           89   attr_accessor :target_id
           90   attr_accessor :target_ids
           91 
           92   validates_with JobValidator
           93 
           94   def stop
           95     self.class.where(id: self.id).update_all(status: 'cancelled')
           96   end
           97 
           98   def update_progress(pct)
           99     if pct >= 100
          100       self.class.where(id: self.id).update_all(progress: pct, completed_at: Time.now, status: 'completed')
          101     else
          102       self.class.where(id: self.id).update_all(progress: pct)
          103     end
          104   end
          105 
          106   def details
          107     Marshal.load(self.args) rescue {}
          108   end
          109 
          110   def schedule
          111     case task
          112     when 'dialer'
          113       self.status = 'submitted'
          114       self.args   = Marshal.dump({
          115         range: self.range,
          116         lines: self.lines.to_i,
          117         seconds: self.seconds.to_i,
          118         cid_mask: self.cid_mask
          119       })
          120 
          121       return self.save
          122 
          123     when 'analysis'
          124       self.status = 'submitted'
          125       d = {
          126                                 scope: self.scope,          # job / project/ global
          127                                 force: !!(self.force),      # true / false
          128                                 target_id: self.target_id.to_i, # job_id or project_id or nil
          129                                 target_ids: (self.target_ids || []).map{|x| x.to_i }
          130                         }
          131       $stderr.puts d.inspect
          132 
          133       self.args = Marshal.dump({
          134         scope: self.scope,          # job / project/ global
          135         force: !!(self.force),      # true / false
          136         target_id: self.target_id.to_i, # job_id or project_id or nil
          137         target_ids: (self.target_ids || []).map{|x| x.to_i }
          138       })
          139       return self.save
          140     else
          141       raise ::RuntimeError, "Unsupported Job type"
          142     end
          143   end
          144 
          145   def rate
          146     tend = (self.completed_at || Time.now)
          147     tlen = tend.to_f - self.started_at.to_f
          148 
          149     case self.task
          150     when 'dialer'
          151       Call.where('job_id = ?', self.id).count() / tlen
          152     when 'analysis'
          153       Call.where('job_id = ? AND analysis_completed_at > ? AND analysis_completed_at < ?', self.details[:target_id], self.created_at, tend).count() / tlen
          154     when 'import'
          155       Call.where('job_id = ?', self.id).count() / tlen
          156     else
          157       0
          158     end
          159   end
          160 
          161 end