Tons of bug fixes after usability testing, still a few more to go - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
---
(DIR) commit 4abcd8f39f245c31f30752f92c4979dbf18879e7
(DIR) parent c617d707a5b387aa960188673371bbef88994ab9
(HTM) Author: HD Moore <hd_moore@rapid7.com>
Date: Thu, 5 Mar 2009 07:36:47 +0000
Tons of bug fixes after usability testing, still a few more to go
Diffstat:
M lib/warvox/jobs.rb | 47 +++++++++++++++----------------
M lib/warvox/jobs/analysis.rb | 10 ++++++----
M lib/warvox/jobs/base.rb | 29 +++++++++++++++++++++++++----
M lib/warvox/jobs/dialer.rb | 38 ++++++++++---------------------
M web/app/controllers/analyze_contro… | 4 ++--
M web/app/controllers/dial_jobs_cont… | 3 +--
M web/app/controllers/dial_results_c… | 29 +++++++++++++++++++++++++++--
M web/app/models/dial_job.rb | 8 ++++----
M web/app/views/dial_jobs/index.html… | 6 +++---
M web/app/views/dial_jobs/new.html.e… | 6 +++---
M web/app/views/dial_results/analyze… | 35 ++++++++-----------------------
M web/app/views/home/index.html.erb | 4 ++--
M web/config/database.yml | 6 +++---
13 files changed, 119 insertions(+), 106 deletions(-)
---
(DIR) diff --git a/lib/warvox/jobs.rb b/lib/warvox/jobs.rb
@@ -3,44 +3,41 @@ class JobQueue
attr_accessor :active_job, :active_thread, :queue, :queue_thread
def initialize
+ @mutex = Mutex.new
@queue = []
@queue_thread = Thread.new{ manage_queue }
+
super
end
- # XXX synchronize
- def deschedule(job_id)
-
- if(@active_job and @active_job.name == job_id)
- @active_thread.kill
- @active_job = @active_thread = nil
- end
-
- res = []
- @queue.each do |j|
- res << j if j.name == job_id
- end
-
- if(res.length > 0)
- res.each {|j| @queue.delete(j) }
+ def scheduled?(klass, job_id)
+ @mutex.synchronize do
+ [@active_job, *(@queue)].each do |c|
+ next if not c
+ return true if (c.class == klass and c.name == job_id)
+ end
end
+ false
end
- def schedule(job)
- @queue.push(job)
+ def schedule(klass, job_id)
+ return false if scheduled?(klass, job_id)
+ @queue.push(klass.new(job_id))
end
def manage_queue
begin
while(true)
- if(@active_job and @active_job.status == 'completed')
- @active_job = nil
- @active_thread = nil
- end
-
- if(not @active_job and @queue.length > 0)
- @active_job = @queue.shift
- @active_thread = Thread.new { @active_job.start }
+ @mutex.synchronize do
+ if(@active_job and @active_job.status == 'completed')
+ @active_job = nil
+ @active_thread = nil
+ end
+
+ if(not @active_job and @queue.length > 0)
+ @active_job = @queue.shift
+ @active_thread = Thread.new { @active_job.start }
+ end
end
Kernel.select(nil, nil, nil, 1)
(DIR) diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
@@ -231,8 +231,10 @@ class Analysis < Base
system("gnuplot #{plotter.path}")
File.unlink(plotter.path)
File.unlink(datfile.path)
+ File.unlink(frefile.path)
plotter.close
datfile.close
+ frefile.path
# Generate a MP3 audio file
system("sox -s -w -r 8000 -t raw -c 1 #{rawfile.path} #{bname}.wav")
@@ -240,13 +242,13 @@ class Analysis < Base
File.unlink("#{bname}.wav")
File.unlink(rawfile.path)
rawfile.close
-
- # XXX: Dump the frequencies
-
+
# Save the changes
r.processed = true
r.processed_at = Time.now
- r.save
+ db_save(r)
+
+ clear_zombies()
end
end
(DIR) diff --git a/lib/warvox/jobs/base.rb b/lib/warvox/jobs/base.rb
@@ -7,10 +7,6 @@ class Base
'base'
end
- def name
- 'noname'
- end
-
def stop
@status = 'active'
end
@@ -18,6 +14,31 @@ class Base
def start
@status = 'completed'
end
+
+ def db_save(obj)
+ max_tries = 10
+ cur_tries = 0
+ begin
+ obj.save
+ rescue ::SQLite3::BusyException => e
+ cur_tries += 1
+ if(cur_tries > max_tries)
+ raise e
+ return
+ end
+ Kernel.select(nil, nil, nil, 0.25)
+ retry
+ end
+ end
+
+ def clear_zombies
+ begin
+ # Clear zombies just in case...
+ while(Process.waitpid(-1, Process::WNOHANG))
+ end
+ rescue ::Exception
+ end
+ end
end
end
end
(DIR) diff --git a/lib/warvox/jobs/dialer.rb b/lib/warvox/jobs/dialer.rb
@@ -70,7 +70,7 @@ class Dialer < Base
model = get_job
model.status = 'active'
model.started_at = Time.now
- model.save
+ db_save(model)
start_dialing()
@@ -86,7 +86,7 @@ class Dialer < Base
model = get_job
model.status = 'completed'
model.completed_at = Time.now
- model.save
+ db_save(model)
end
def start_dialing
@@ -177,32 +177,18 @@ class Dialer < Base
end
# END SPAWN THREADS
tasks.map{|t| t.join if t}
-
- # Save data to the database
- begin
-
- # Iterate through the results
- @calls.each do |r|
- tries = 0
- begin
- r.save
- rescue ::Exception => e
- $stderr.puts "ERROR: #{r.inspect} #{e.class} #{e}"
- tries += 1
- Kernel.select(nil, nil, nil, 0.25 * (rand(8)+1))
- retry if tries < 5
- end
- end
-
- # Update the progress bar
- model = get_job
- model.progress = ((@nums_total - @nums.length) / @nums_total.to_f) * 100
- model.save
- rescue ::SQLite3::BusyException => e
- $stderr.puts "ERROR: Database lock hit trying to save, retrying"
- retry
+ # Iterate through the results
+ @calls.each do |r|
+ db_save(r)
end
+
+ # Update the progress bar
+ model = get_job
+ model.progress = ((@nums_total - @nums.length) / @nums_total.to_f) * 100
+ db_save(model)
+
+ clear_zombies()
end
# ALL DONE
(DIR) diff --git a/web/app/controllers/analyze_controller.rb b/web/app/controllers/analyze_controller.rb
@@ -20,8 +20,8 @@ class AnalyzeController < ApplicationController
:conditions => [ 'completed = ? and processed = ? and busy = ?', true, true, false ]
)
- @g1 = Ezgraphix::Graphic.new(:c_type => 'pie2d', :div_name => 'calls_pie1')
- @g1.render_options(:caption => 'Line Types')
+ @g1 = Ezgraphix::Graphic.new(:c_type => 'col3d', :div_name => 'calls_pie1')
+ @g1.render_options(:caption => 'Detected Lines by Type', :y_name => 'Lines')
@g2 = Ezgraphix::Graphic.new(:c_type => 'pie2d', :div_name => 'calls_pie2')
@g2.render_options(:caption => 'Ring Time')
(DIR) diff --git a/web/app/controllers/dial_jobs_controller.rb b/web/app/controllers/dial_jobs_controller.rb
@@ -79,8 +79,7 @@ class DialJobsController < ApplicationController
flash[:notice] = 'Job was successfully created.'
# Launch it
- dialer = WarVOX::Jobs::Dialer.new(@dial_job.id)
- WarVOX::JobManager.schedule(dialer)
+ WarVOX::JobManager.schedule(::WarVOX::Jobs::Dialer, @dial_job.id)
format.html { redirect_to(@dial_job) }
format.xml { render :xml => @dial_job, :status => :created, :location => @dial_job }
(DIR) diff --git a/web/app/controllers/dial_results_controller.rb b/web/app/controllers/dial_results_controller.rb
@@ -38,6 +38,32 @@ class DialResultsController < ApplicationController
@job_id = params[:id]
@job = DialJob.find(@job_id)
+ @dial_data_total = DialResult.find_all_by_dial_job_id(
+ @job_id,
+ :conditions => [ 'completed = ? and busy = ?', true, false ]
+ ).length
+
+ @dial_data_done_set = DialResult.find_all_by_dial_job_id(
+ @job_id,
+ :conditions => [ 'processed = ?', true]
+ )
+ @dial_data_done = @dial_data_done_set.length
+
+ @g1 = Ezgraphix::Graphic.new(:c_type => 'col3d', :div_name => 'calls_pie1')
+ @g1.render_options(:caption => 'Detected Lines by Type', :y_name => 'Lines')
+
+ @g2 = Ezgraphix::Graphic.new(:c_type => 'pie2d', :div_name => 'calls_pie2')
+ @g2.render_options(:caption => 'Analysis Progress')
+
+ res_types = {}
+ @dial_data_done_set.each do |r|
+ res_types[ r.line_type.capitalize.to_sym ] ||= 0
+ res_types[ r.line_type.capitalize.to_sym ] += 1
+ end
+
+ @g1.data = res_types
+ @g2.data = {:Remaining => @dial_data_total-@dial_data_done, :Complete => @dial_data_done}
+
@dial_data_todo = DialResult.paginate_all_by_dial_job_id(
@job_id,
:page => params[:page],
@@ -52,8 +78,7 @@ class DialResultsController < ApplicationController
end
if(@dial_data_todo.length > 0)
- analyzer = WarVOX::Jobs::Analysis.new(@job_id)
- WarVOX::JobManager.schedule(analyzer)
+ WarVOX::JobManager.schedule(::WarVOX::Jobs::Analysis, @job_id)
end
end
(DIR) diff --git a/web/app/models/dial_job.rb b/web/app/models/dial_job.rb
@@ -6,16 +6,16 @@ class DialJob < ActiveRecord::Base
validates_numericality_of :seconds, :less_than => 301, :greater_than => 0
def validate
- if(range.gsub(/[^0-9X]/, '').length != 10)
- errors.add(:range, "The range must be exactly 10 characters long and made up of 0-9 and X as the mask.")
+ if(range.gsub(/[^0-9X]/, '').empty?)
+ errors.add(:range, "The range must be at least 1 character long and made up of 0-9 and X as the mask.")
end
if(range.scan(/X/).length > 5)
errors.add(:range, "The range must contain no more than 5 mask digits.")
end
- if(cid_mask != "SELF" and cid_mask.gsub(/[^0-9X]/, '').length != 10)
- errors.add(:range, "The Caller ID must be exactly 10 characters long and made up of 0-9 and X as the mask.")
+ if(cid_mask != "SELF" and cid_mask.gsub(/[^0-9X]/, '').empty?)
+ errors.add(:range, "The Caller ID must be at least 1 character long and made up of 0-9 and X as the mask.")
end
if(cid_mask != "SELF" and cid_mask.scan(/X/).length > 5)
(DIR) diff --git a/web/app/views/dial_jobs/index.html.erb b/web/app/views/dial_jobs/index.html.erb
@@ -77,7 +77,7 @@
<% form_for(@new_job) do |f| %>
<%= f.error_messages %>
<p>
- <%= f.label :range, 'The target telephone range (123-456-XXXX)' %><br />
+ <%= f.label :range, 'The target telephone range (1-123-456-XXXX)' %><br />
<%= f.text_field :range %>
</p>
<p>
@@ -89,8 +89,8 @@
<%= f.text_field :lines, :value => 10 %>
</p>
<p>
- <%= f.label :lines, 'The source Caller ID range (555-555-55XX)' %><br />
- <%= f.text_field :cid_mask, :value => '000-000-XXXX' %>
+ <%= f.label :lines, 'The source Caller ID range (1-555-555-55XX)' %><br />
+ <%= f.text_field :cid_mask, :value => '1-123-456-XXXX' %>
</p>
<p>
<%= f.submit "Create" %>
(DIR) diff --git a/web/app/views/dial_jobs/new.html.erb b/web/app/views/dial_jobs/new.html.erb
@@ -3,7 +3,7 @@
<% form_for(@dial_job) do |f| %>
<%= f.error_messages %>
<p>
- <%= f.label :range, 'The target telephone range (123-456-XXXX)' %><br />
+ <%= f.label :range, 'The target telephone range (1-123-456-XXXX)' %><br />
<%= f.text_field :range %>
</p>
<p>
@@ -15,8 +15,8 @@
<%= f.text_field :lines, :value => 10 %>
</p>
<p>
- <%= f.label :lines, 'The source Caller ID range (555-555-55XX or SELF)' %><br />
- <%= f.text_field :cid_mask, :value => '000-000-XXXX' %>
+ <%= f.label :lines, 'The source Caller ID range (1-555-555-55XX or SELF)' %><br />
+ <%= f.text_field :cid_mask, :value => '1-123-456-XXXX' %>
</p>
<p>
<%= f.submit "Create" %>
(DIR) diff --git a/web/app/views/dial_results/analyze.html.rb b/web/app/views/dial_results/analyze.html.rb
@@ -1,40 +1,23 @@
<% if @dial_data_todo.length > 0 %>
-<h1 class='title'>Processing <%= @dial_data_todo.length %> Calls...</h1>
+<h1 class='title'>
+ Analyzing Calls ( completed <%= @dial_data_done %> of <%= @dial_data_total %>
+ - <%= @dial_data_total-@dial_data_done %> left )...
+</h1>
<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
<tr>
- <td align='center'> </td>
- <td align='center'> </td>
-</tr>
-</table>
-
-<table class='table_scaffold' width='100%'>
- <tr>
- <th>Number</th>
- <th>CallerID</th>
- <th>Provider</th>
- <th>Call Time</th>
- <th>Ring Time</th>
- </tr>
-
-<% for dial_result in @dial_data_todo.sort{|a,b| a.number <=> b.number } %>
- <tr>
- <td><%=h dial_result.number %></td>
- <td><%=h dial_result.cid %></td>
- <td><%=h dial_result.provider.name %></td>
- <td><%=h dial_result.seconds %></td>
- <td><%=h dial_result.ringtime %></td>
- </tr>
+<% if @dial_data_done > 0 %>
+ <td align='center'><%= render_ezgraphix @g1 %></td>
+ <td align='center'><%= render_ezgraphix @g2 %></td>
<% end %>
+</tr>
</table>
<script language="javascript">
- setTimeout("location.reload(true);", 3000);
+ setTimeout("location.reload(true);", 5000);
</script>
-<%= will_paginate @dial_data_todo %>
-
<% else %>
<h1 class='title'>No Completed Calls Found</h1>
(DIR) diff --git a/web/app/views/home/index.html.erb b/web/app/views/home/index.html.erb
@@ -16,7 +16,7 @@ In order to make phone calls, WarVOX needs to be configured with one or more ser
<p>Once one or more service providers have been configured, click the <a href="/dial_jobs/">Jobs</a> link. This will present a form that asks for the phone number range to dial, the number of seconds of audio to capture, and the maximum number of outgoing lines to use for this job.
</p>
-<p>The phone number range is specified by entering the full 10-digit phone number, with numbers replaced by X's where an entire range should be dialed. For example, the value 512-555-XXXX will make 10,000 calls, one to each number within the 512-555 exchange. In contrast, the value 512-555-555X will only make 10 calls, covering 5550 to 5559. Only 5 digits of the phone number range can be masked.
+<p>The phone number range is specified by entering the phone number (including country code), with numbers replaced by X's where an entire range should be dialed. For example, the value 1-512-555-XXXX will make 10,000 calls, one to each number within the 512-555 exchange. In contrast, the value 1-512-555-555X will only make 10 calls, covering 5550 to 5559. Only 5 digits of the phone number range can be masked.
</p>
<p>
@@ -27,7 +27,7 @@ The seconds field indicates the number of seconds to spend on each call, includi
The outgoing line count is limited by the number of providers available and the number of lines available at each provider. If you are dialing a range with a limited number of inbound lines, the outgoing line count should be set to a small value, otherwise leave this value at the maximum number of available lines to complete jobs as quickly as possible. Each concurrent outbound call requires approximately 80kbits/s of downstream bandwidth.
</p>
-<p>The Caller ID is specified by entering the full 10-digit phone number, with numbers replaced by X's where parts of the number should be chosen randomly. This field also accepts the special value of "SELF", which will cause all calls to be made with the Caller ID set to the destination number (useful for testing poorly implemented voice mail security).
+<p>The Caller ID is specified by entering the phone number (including country code), with numbers replaced by X's where parts of the number should be chosen randomly. This field also accepts the special value of "SELF", which will cause all calls to be made with the Caller ID set to the destination number (useful for testing poorly implemented voice mail security).
</p>
<p>
(DIR) diff --git a/web/config/database.yml b/web/config/database.yml
@@ -4,7 +4,7 @@ development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
- timeout: 25000
+ timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
@@ -13,10 +13,10 @@ test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
- timeout: 25000
+ timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
- timeout: 25000
+ timeout: 5000