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
  HTML 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