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