Tabs to spaces - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit 6fc7895fc496df7a5acdc2f22d6e60fb70f1ca65
 (DIR) parent 922c8b67c39f72646441de35353bb87401adea71
 (HTM) Author: HD Moore <x@hdm.io>
       Date:   Wed,  2 Mar 2016 16:04:08 -0600
       
       Tabs to spaces
       
       Diffstat:
         M app/controllers/application_contro… |     100 ++++++++++++++++----------------
         M app/controllers/calls_controller.rb |      46 ++++++++++++++++----------------
         M app/controllers/home_controller.rb  |      38 ++++++++++++++++----------------
         M app/controllers/jobs_controller.rb  |     684 ++++++++++++++++----------------
         M app/controllers/projects_controlle… |     266 ++++++++++++++++----------------
         M app/controllers/providers_controll… |      28 ++++++++++++++--------------
         M app/controllers/user_sessions_cont… |      36 ++++++++++++++++----------------
         M app/helpers/analyze_helper.rb       |      12 ++++++------
         M app/helpers/application_helper.rb   |     344 +++++++++++++++---------------
         M app/models/call_medium.rb           |       4 ++--
         M app/models/job.rb                   |     284 ++++++++++++++++----------------
         M app/models/line.rb                  |      24 ++++++++++++------------
         M app/models/line_attribute.rb        |       4 ++--
         M app/models/project.rb               |      18 +++++++++---------
         M app/models/provider.rb              |      10 +++++-----
         M app/models/settings.rb              |       2 +-
         M app/models/signature.rb             |       2 +-
         M app/models/signature_fp.rb          |       2 +-
         M app/models/user.rb                  |      14 +++++++-------
         M app/models/user_session.rb          |       2 +-
         M bin/adduser                         |      72 ++++++++++++++++----------------
         M bin/analyze_result.rb               |      14 +++++++-------
         M bin/audio_raw_to_fprint.rb          |      16 ++++++++--------
         M bin/audio_raw_to_wav.rb             |      16 ++++++++--------
         M bin/audio_trim.rb                   |      20 ++++++++++----------
         M bin/cache_clear.rb                  |       2 +-
         M bin/export_audio.rb                 |      98 ++++++++++++++++----------------
         M bin/export_list.rb                  |      46 ++++++++++++++++----------------
         M bin/iaxrecord.rb                    |      62 ++++++++++++++++----------------
         M bin/identify_matches.rb             |      68 ++++++++++++++++----------------
         M bin/import_audio.rb                 |     110 ++++++++++++++++----------------
         M bin/resetpw                         |      40 ++++++++++++++++----------------
         M bin/verify_install.rb               |      34 ++++++++++++++++----------------
         M bin/warvox.rb                       |      64 ++++++++++++++++----------------
         M bin/worker.rb                       |      44 ++++++++++++++++----------------
         M bin/worker_manager.rb               |     220 ++++++++++++++++----------------
         M config/classifiers/01.default.rb    |      28 ++++++++++++++--------------
         M config/classifiers/99.default.rb    |       8 ++++----
         M db/migrate/20121228171549_initial_… |     360 ++++++++++++++++----------------
         M lib/warvox.rb                       |      10 +++++-----
         M lib/warvox/audio/raw.rb             |     646 ++++++++++++++++----------------
         M lib/warvox/config.rb                |     310 ++++++++++++++++----------------
         M lib/warvox/jobs.rb                  |     118 ++++++++++++++++----------------
         M lib/warvox/jobs/analysis.rb         |     828 +++++++++++++++---------------
         M lib/warvox/jobs/base.rb             |      52 ++++++++++++++++----------------
         M lib/warvox/jobs/dialer.rb           |     438 ++++++++++++++++----------------
         M lib/warvox/phone.rb                 |      96 ++++++++++++++++----------------
         M lib/warvox/proto/iax2/client.rb     |      10 +++++-----
         M spec/factories/call_media.rb        |       8 ++++----
         M spec/factories/calls.rb             |      12 ++++++------
         M spec/factories/jobs.rb              |      22 +++++++++++-----------
         M spec/factories/lines.rb             |       8 ++++----
         M spec/factories/projects.rb          |       8 ++++----
         M spec/factories/providers.rb         |      18 +++++++++---------
         M spec/factories/settings.rb          |       6 +++---
         M spec/factories/signature_fps.rb     |       6 +++---
         M spec/factories/signatures.rb        |      16 ++++++++--------
         M spec/factories/users.rb             |      14 +++++++-------
         M spec/features/projects_spec.rb      |      38 ++++++++++++++++----------------
         M spec/features/visitor/logins_spec.… |     100 ++++++++++++++++----------------
         M spec/models/call_medium_spec.rb     |      10 +++++-----
         M spec/models/call_spec.rb            |      14 +++++++-------
         M spec/models/job_spec.rb             |      12 ++++++------
         M spec/models/line_spec.rb            |      10 +++++-----
         M spec/models/project_spec.rb         |      20 ++++++++++----------
         M spec/models/provider_spec.rb        |      28 ++++++++++++++--------------
         M spec/models/settings_spec.rb        |       6 +++---
         M spec/models/signature_spec.rb       |      12 ++++++------
         M spec/models/user_spec.rb            |      10 +++++-----
         M spec/rails_helper.rb                |      44 ++++++++++++++++----------------
         M spec/support/auth_logic_helpers.rb  |      28 ++++++++++++++--------------
       
       71 files changed, 3100 insertions(+), 3100 deletions(-)
       ---
 (DIR) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
       @@ -1,68 +1,68 @@
        class ApplicationController < ActionController::Base
       -        protect_from_forgery
       -        helper :all
       +  protect_from_forgery
       +  helper :all
        
       -        helper_method :current_user_session, :current_user
       -        before_filter :require_user, :load_project
       -        add_breadcrumb :projects, :root_path
       +  helper_method :current_user_session, :current_user
       +  before_filter :require_user, :load_project
       +  add_breadcrumb :projects, :root_path
        
       -        include ActionView::Helpers::NumberHelper
       +  include ActionView::Helpers::NumberHelper
        
        private
        
       -        def current_user_session
       -                return @current_user_session if defined?(@current_user_session)
       -                @current_user_session = UserSession.find
       -        end
       +  def current_user_session
       +    return @current_user_session if defined?(@current_user_session)
       +    @current_user_session = UserSession.find
       +  end
        
       -        def current_user
       -                return @current_user if defined?(@current_user)
       -                @current_user = current_user_session && current_user_session.record
       -        end
       +  def current_user
       +    return @current_user if defined?(@current_user)
       +    @current_user = current_user_session && current_user_session.record
       +  end
        
       -        def require_user
       -                unless current_user
       -                        store_location
       -                        flash.now[:notice] = "You must be logged in to access this page"
       -                        redirect_to '/login'
       -                        return false
       -                end
       -        end
       +  def require_user
       +    unless current_user
       +      store_location
       +      flash.now[:notice] = "You must be logged in to access this page"
       +      redirect_to '/login'
       +      return false
       +    end
       +  end
        
       -        def require_no_user
       -                if current_user
       -                        store_location
       -                        flash[:notice] = "You must be logged out to access this page"
       -                        redirect_to user_path(current_user)
       -                        return false
       -                end
       -        end
       +  def require_no_user
       +    if current_user
       +      store_location
       +      flash[:notice] = "You must be logged out to access this page"
       +      redirect_to user_path(current_user)
       +      return false
       +    end
       +  end
        
       -        def store_location
       -                session[:return_to] = request.fullpath
       -        end
       +  def store_location
       +    session[:return_to] = request.fullpath
       +  end
        
       -        def redirect_back_or_default(default)
       -                redirect_to(session[:return_to] || default)
       -                session[:return_to] = nil
       -        end
       +  def redirect_back_or_default(default)
       +    redirect_to(session[:return_to] || default)
       +    session[:return_to] = nil
       +  end
        
       -        def load_project
       -                # Only load this when we are logged in
       -                return true unless current_user
       +  def load_project
       +    # Only load this when we are logged in
       +    return true unless current_user
        
       -                if params[:project_id]
       -                        @project = Project.where(:id => params[:project_id].to_i).first
       -                elsif session[:project_id]
       -                        @project = Project.where(:id => session[:project_id].to_i).first
       -                end
       +    if params[:project_id]
       +      @project = Project.where(:id => params[:project_id].to_i).first
       +    elsif session[:project_id]
       +      @project = Project.where(:id => session[:project_id].to_i).first
       +    end
        
       -                if @project and @project.id and not (session[:project_id] and session[:project_id] == @project.id)
       -                        session[:project_id] = @project.id
       -                end
       +    if @project and @project.id and not (session[:project_id] and session[:project_id] == @project.id)
       +      session[:project_id] = @project.id
       +    end
        
       -                true
       -        end
       +    true
       +  end
        
        
        end
 (DIR) diff --git a/app/controllers/calls_controller.rb b/app/controllers/calls_controller.rb
       @@ -4,9 +4,9 @@ class CallsController < ApplicationController
          # GET /calls.xml
          def index
            @jobs = @project.jobs.order('id DESC').where('task = ? AND completed_at IS NOT NULL', 'dialer').paginate(
       -                :page => params[:page],
       -                :per_page => 30
       -        )
       +    :page => params[:page],
       +    :per_page => 30
       +  )
        
            respond_to do |format|
              format.html # index.html.erb
       @@ -18,21 +18,21 @@ class CallsController < ApplicationController
          # GET /calls/1/view.xml
          def view
            @calls = Call.order('id DESC').where(:job_id => params[:id]).paginate(
       -                :page => params[:page],
       -                :per_page => 30
       -        )
       -
       -        unless @calls and @calls.length > 0
       -                redirect_to :action => :index
       -                return
       -        end
       -        @call_results = {
       -                :Timeout  => Call.count(:conditions =>['job_id = ? and answered = ?', params[:id], false]),
       -                :Busy     => Call.count(:conditions =>['job_id = ? and busy = ?', params[:id], true]),
       -                :Answered => Call.count(:conditions =>['job_id = ? and answered = ?', params[:id], true]),
       -        }
       -
       -        respond_to do |format|
       +    :page => params[:page],
       +    :per_page => 30
       +  )
       +
       +  unless @calls and @calls.length > 0
       +    redirect_to :action => :index
       +    return
       +  end
       +  @call_results = {
       +    :Timeout  => Call.count(:conditions =>['job_id = ? and answered = ?', params[:id], false]),
       +    :Busy     => Call.count(:conditions =>['job_id = ? and busy = ?', params[:id], true]),
       +    :Answered => Call.count(:conditions =>['job_id = ? and answered = ?', params[:id], true]),
       +  }
       +
       +  respond_to do |format|
              format.html # index.html.erb
              format.xml  { render :xml => @calls }
            end
       @@ -43,10 +43,10 @@ class CallsController < ApplicationController
          def show
            @call = Call.find(params[:id])
        
       -        unless @call
       -                redirect_to :action => :index
       -                return
       -        end
       +  unless @call
       +    redirect_to :action => :index
       +    return
       +  end
        
            respond_to do |format|
              format.html # show.html.erb
       @@ -109,7 +109,7 @@ class CallsController < ApplicationController
          def destroy
        
            @job = Job.find(params[:id])
       -        @job.destroy
       +  @job.destroy
        
            respond_to do |format|
              format.html { redirect_to :action => 'index' }
 (DIR) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
       @@ -1,27 +1,27 @@
        class HomeController < ApplicationController
        
       -        def index
       +  def index
        
       -        end
       +  end
        
       -        def about
       -                begin
       -                        @has_kissfft = "MISSING"
       -                        require 'kissfft'
       -                        @has_kissfft = $LOADED_FEATURES.grep(/kissfft/)[0]
       -                rescue ::LoadError
       -                end
       -        end
       +  def about
       +    begin
       +      @has_kissfft = "MISSING"
       +      require 'kissfft'
       +      @has_kissfft = $LOADED_FEATURES.grep(/kissfft/)[0]
       +    rescue ::LoadError
       +    end
       +  end
        
       -        def help
       -        end
       +  def help
       +  end
        
       -        def check
       -                @has_project  = ( Project.count > 0 )
       -                @has_provider = ( Provider.where(:enabled => true).count > 0 )
       -                @has_job      = ( Job.where(:task => 'dialer').count > 0 )
       -                @has_result   = ( Call.where(:answered => true ).count > 0 )
       -                @has_analysis = ( Call.where('analysis_completed_at IS NOT NULL').count > 0 )
       -        end
       +  def check
       +    @has_project  = ( Project.count > 0 )
       +    @has_provider = ( Provider.where(:enabled => true).count > 0 )
       +    @has_job      = ( Job.where(:task => 'dialer').count > 0 )
       +    @has_result   = ( Call.where(:answered => true ).count > 0 )
       +    @has_analysis = ( Call.where('analysis_completed_at IS NOT NULL').count > 0 )
       +  end
        
        end
 (DIR) diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb
       @@ -1,346 +1,346 @@
        class JobsController < ApplicationController
        
       -        require 'shellwords'
       -
       -        def index
       -                @reload_interval = 20000
       -
       -                @submitted_jobs = Job.where(:status => ['submitted', 'scheduled'], :completed_at => nil)
       -                @active_jobs    = Job.where(:status => 'running', :completed_at => nil)
       -                @inactive_jobs  = Job.order('id DESC').where('status NOT IN (?)', ['submitted', 'scheduled', 'running']).paginate(
       -                        :page => params[:page],
       -                        :per_page => 30
       -                )
       -
       -                if @active_jobs.length > 0
       -                        @reload_interval = 5000
       -                end
       -
       -                if @submitted_jobs.length > 0
       -                        @reload_interval = 3000
       -                end
       -
       -                respond_to do |format|
       -                        format.html
       -                end
       -        end
       -
       -        def results
       -                @jobs = @project.jobs.order('id DESC').where('(task = ? OR task = ?) AND completed_at IS NOT NULL', 'dialer', 'import').paginate(
       -                        :page => params[:page],
       -                        :per_page => 30
       -                )
       -
       -                respond_to do |format|
       -                        format.html
       -                end
       -        end
       -
       -        def view_results
       -                @job = Job.find(params[:id])
       -
       -                @call_results = {
       -                        :Timeout  => @job.calls.count(:conditions => { :answered => false }),
       -                        :Busy     => @job.calls.count(:conditions => { :busy                 => true }),
       -                        :Answered => @job.calls.count(:conditions => { :answered => true }),
       -                }
       -
       -                sort_by         = params[:sort_by] || 'number'
       -                sort_dir = params[:sort_dir] || 'asc'
       -
       -                @results = []
       -                @results_total_count = @job.calls.count()
       -
       -                if request.format.json?
       -                        if params[:iDisplayLength] == '-1'
       -                                @results_per_page = nil
       -                        else
       -                                @results_per_page = (params[:iDisplayLength] || 20).to_i
       -                        end
       -                        @results_offset = (params[:iDisplayStart] || 0).to_i
       -
       -                        calls_search
       -                        @results = @job.calls.includes(:provider).where(@search_conditions).limit(@results_per_page).offset(@results_offset).order(calls_sort_option)
       -                        @results_total_display_count = @job.calls.includes(:provider).where(@search_conditions).count()
       -                end
       -
       -                respond_to do |format|
       -                        format.html
       -                        format.json {
       -                                render :content_type => 'application/json', :json => render_to_string(:partial => 'view_results', :results => @results, :call_results => @call_results )
       -                        }
       -                end
       -        end
       -
       -        # Generate a SQL sort by option based on the incoming DataTables paramater.
       -        #
       -        # Returns the SQL String.
       -        def calls_sort_option
       -                column = case params[:iSortCol_0].to_s
       -                        when '1'
       -                                'number'
       -                        when '2'
       -                                'caller_id'
       -                        when '3'
       -                                'providers.name'
       -                        when '4'
       -                                'answered'
       -                        when '5'
       -                                'busy'
       -                        when '6'
       -                                'audio_length'
       -                        when '7'
       -                                'ring_length'
       -                end
       -                column + ' ' + (params[:sSortDir_0] =~ /^A/i ? 'asc' : 'desc') if column
       -        end
       -
       -        def calls_search
       -                @search_conditions = []
       -                terms = params[:sSearch].to_s
       -                terms = Shellword.shellwords(terms) rescue terms.split(/\s+/)
       -                where = ""
       -                param = []
       -                glue        = ""
       -                terms.each do |w|
       -                        next if w.downcase == 'undefined'
       -                        where << glue
       -                        case w
       -                                when 'answered'
       -                                        where << "answered = ? "
       -                                        param << true
       -                                when 'busy'
       -                                        where << "busy = ? "
       -                                        param << true
       -                                else
       -                                        where << "( number ILIKE ? OR caller_id ILIKE ? ) "
       -                                        param << "%#{w}%"
       -                                        param << "%#{w}%"
       -                        end
       -                        glue = "AND " if glue.empty?
       -                        @search_conditions = [ where, *param ]
       -                end
       -        end
       -
       -        def new_dialer
       -                @job = Job.new
       -                if @project
       -                        @job.project = @project
       -                else
       -                        @job.project = Project.last
       -                end
       -
       -                if params[:result_ids]
       -                        nums = ""
       -                        Call.find_each(:conditions => { :id => params[:result_ids] }) do |call|
       -                                nums << call.number + "\n"
       -                        end
       -                        @job.range = nums
       -                end
       -
       -                respond_to do |format|
       -                        format.html
       -                 end
       -        end
       -
       -        def purge_calls
       -                Call.delete_all(:id => params[:result_ids])
       -                CallMedium.delete_all(:call_id => params[:result_ids])
       -                flash[:notice] = "Purged #{params[:result_ids].length} calls"
       -                if params[:id]
       -                        @job = Job.find(params[:id])
       -                        redirect_to view_results_path(@job.project_id, @job.id)
       -                else
       -                        redirect_to analyze_path(@project)
       -                end
       -        end
       -
       -        def dialer
       -                @job = Job.new(params[:job])
       -                @job.created_by = @current_user.login
       -                @job.task = 'dialer'
       -                @job.range.to_s.gsub!(/[^0-9X:,\n]/, '')
       -                @job.cid_mask.to_s.gsub!(/[^0-9X]/, '') if @job.cid_mask != "SELF"
       -
       -                if @job.range_file.to_s != ""
       -                        @job.range = @job.range_file.read.gsub(/[^0-9X:,\n]/, '')
       -                end
       -
       -                respond_to do |format|
       -                        if @job.schedule
       -                                flash[:notice] = 'Job was successfully created.'
       -                                format.html { redirect_to :action => :index }
       -                        else
       -                                format.html { render :action => "new_dialer" }
       -                        end
       -                end
       -        end
       -
       -        def new_analyze
       -                @job = Job.new
       -                if @project
       -                        @job.project = @project
       -                else
       -                        @job.project = Project.last
       -                end
       -
       -                if params[:result_ids]
       -                        nums = ""
       -                        Call.find_each(:conditions => { :id => params[:result_ids] }) do |call|
       -                                nums << call.number + "\n"
       -                        end
       -                        @job.range = nums
       -                end
       -
       -                respond_to do |format|
       -                        format.html
       -                 end
       -        end
       -
       -        def new_identify
       -                @job = Job.new
       -                if @project
       -                        @job.project = @project
       -                else
       -                        @job.project = Project.last
       -                end
       -
       -                if params[:result_ids]
       -                        nums = ""
       -                        Call.find_each(:conditions => { :id => params[:result_ids] }) do |call|
       -                                nums << call.number + "\n"
       -                        end
       -                        @job.range = nums
       -                end
       -
       -                respond_to do |format|
       -                        format.html
       -                 end
       -        end
       -
       -        def reanalyze_job
       -                @job = Job.find(params[:id])
       -                @new = Job.new({
       -                        :task => 'analysis', :scope => 'job', :target_id => @job.id, :force => true,
       -                        :project_id => @project.id, :status => 'submitted'
       -                })
       -                @new.created_by = @current_user.login
       -                respond_to do |format|
       -                        if @new.schedule
       -                                flash[:notice] = 'Analysis job was successfully created.'
       -                                format.html { redirect_to jobs_path }
       -                        else
       -                                flash[:notice] = 'Analysis job could not run: ' + @new.errors.inspect
       -                                format.html { redirect_to results_path(@project) }
       -                        end
       -                end
       -        end
       -
       -        def analyze_job
       -                @job = Job.find(params[:id])
       -
       -                # Handle analysis of specific call IDs via checkbox submission
       -                if params[:result_ids]
       -                        @new = Job.new({
       -                                :task => 'analysis', :scope => 'calls', :target_ids => params[:result_ids],
       -                                :project_id => @project.id, :status => 'submitted'
       -                        })
       -                else
       -                # Otherwise analyze the entire Job
       -                        @new = Job.new({
       -                                :task => 'analysis', :scope => 'job', :target_id => @job.id,
       -                                :project_id => @project.id, :status => 'submitted'
       -                        })
       -                end
       -
       -                @new.created_by = @current_user.login
       -
       -                respond_to do |format|
       -                        if @new.schedule
       -                                flash[:notice] = 'Analysis job was successfully created.'
       -                                format.html { redirect_to jobs_path }
       -                        else
       -                                flash[:notice] = 'Analysis job could not run: ' + @new.errors.inspect
       -                                format.html { redirect_to results_path(@project) }
       -                        end
       -                end
       -        end
       -
       -
       -        def analyze_project
       -
       -                # Handle analysis of specific call IDs via checkbox submission
       -                if params[:result_ids]
       -                        @new = Job.new({
       -                                :task => 'analysis', :scope => 'calls', :target_ids => params[:result_ids],
       -                                :project_id => @project.id, :status => 'submitted'
       -                        })
       -                else
       -                # Otherwise analyze the entire Project
       -                        @new = Job.new({
       -                                :task => 'analysis', :scope => 'project', :target_id => @project.id,
       -                                :project_id => @project.id, :status => 'submitted'
       -                        })
       -                end
       -
       -                @new.created_by = @current_user.login
       -
       -                respond_to do |format|
       -                        if @new.schedule
       -                                flash[:notice] = 'Analysis job was successfully created.'
       -                                format.html { redirect_to jobs_path }
       -                        else
       -                                flash[:notice] = 'Analysis job could not run: ' + @new.errors.inspect
       -                                format.html { redirect_to results_path(@project) }
       -                        end
       -                end
       -        end
       -
       -        def identify_job
       -                @job = Job.find(params[:id])
       -
       -                # Handle identification of specific lines via checkbox submission
       -                if params[:result_ids]
       -                        @new = Job.new({
       -                                :task => 'identify', :scope => 'calls', :target_ids => params[:result_ids],
       -                                :project_id => @project.id, :status => 'submitted'
       -                        })
       -                else
       -                # Otherwise analyze the entire Job
       -                        @new = Job.new({
       -                                :task => 'identify', :scope => 'job', :target_id => @job.id,
       -                                :project_id => @project.id, :status => 'submitted'
       -                        })
       -                end
       -
       -                @new.created_by = @current_user.login
       -
       -                respond_to do |format|
       -                        if @new.schedule
       -                                flash[:notice] = 'Identify job was successfully created.'
       -                                format.html { redirect_to jobs_path }
       -                        else
       -                                flash[:notice] = 'Identify job could not run: ' + @new.errors.inspect
       -                                format.html { redirect_to results_path(@project) }
       -                        end
       -                end
       -        end
       -
       -        def stop
       -                @job = Job.find(params[:id])
       -                @job.stop
       -                flash[:notice] = "Job has been cancelled"
       -                redirect_to :action => 'index'
       -        end
       -
       -        def destroy
       -                @job = Job.find(params[:id])
       -                @job.destroy
       -
       -                respond_to do |format|
       -                        format.html { redirect_to(jobs_url) }
       -                        format.xml        { head :ok }
       -                end
       -        end
       +  require 'shellwords'
       +
       +  def index
       +    @reload_interval = 20000
       +
       +    @submitted_jobs = Job.where(:status => ['submitted', 'scheduled'], :completed_at => nil)
       +    @active_jobs    = Job.where(:status => 'running', :completed_at => nil)
       +    @inactive_jobs  = Job.order('id DESC').where('status NOT IN (?)', ['submitted', 'scheduled', 'running']).paginate(
       +      :page => params[:page],
       +      :per_page => 30
       +    )
       +
       +    if @active_jobs.length > 0
       +      @reload_interval = 5000
       +    end
       +
       +    if @submitted_jobs.length > 0
       +      @reload_interval = 3000
       +    end
       +
       +    respond_to do |format|
       +      format.html
       +    end
       +  end
       +
       +  def results
       +    @jobs = @project.jobs.order('id DESC').where('(task = ? OR task = ?) AND completed_at IS NOT NULL', 'dialer', 'import').paginate(
       +      :page => params[:page],
       +      :per_page => 30
       +    )
       +
       +    respond_to do |format|
       +      format.html
       +    end
       +  end
       +
       +  def view_results
       +    @job = Job.find(params[:id])
       +
       +    @call_results = {
       +      :Timeout  => @job.calls.count(:conditions => { :answered => false }),
       +      :Busy     => @job.calls.count(:conditions => { :busy     => true }),
       +      :Answered => @job.calls.count(:conditions => { :answered => true }),
       +    }
       +
       +    sort_by   = params[:sort_by] || 'number'
       +    sort_dir = params[:sort_dir] || 'asc'
       +
       +    @results = []
       +    @results_total_count = @job.calls.count()
       +
       +    if request.format.json?
       +      if params[:iDisplayLength] == '-1'
       +        @results_per_page = nil
       +      else
       +        @results_per_page = (params[:iDisplayLength] || 20).to_i
       +      end
       +      @results_offset = (params[:iDisplayStart] || 0).to_i
       +
       +      calls_search
       +      @results = @job.calls.includes(:provider).where(@search_conditions).limit(@results_per_page).offset(@results_offset).order(calls_sort_option)
       +      @results_total_display_count = @job.calls.includes(:provider).where(@search_conditions).count()
       +    end
       +
       +    respond_to do |format|
       +      format.html
       +      format.json {
       +        render :content_type => 'application/json', :json => render_to_string(:partial => 'view_results', :results => @results, :call_results => @call_results )
       +      }
       +    end
       +  end
       +
       +  # Generate a SQL sort by option based on the incoming DataTables paramater.
       +  #
       +  # Returns the SQL String.
       +  def calls_sort_option
       +    column = case params[:iSortCol_0].to_s
       +      when '1'
       +        'number'
       +      when '2'
       +        'caller_id'
       +      when '3'
       +        'providers.name'
       +      when '4'
       +        'answered'
       +      when '5'
       +        'busy'
       +      when '6'
       +        'audio_length'
       +      when '7'
       +        'ring_length'
       +    end
       +    column + ' ' + (params[:sSortDir_0] =~ /^A/i ? 'asc' : 'desc') if column
       +  end
       +
       +  def calls_search
       +    @search_conditions = []
       +    terms = params[:sSearch].to_s
       +    terms = Shellword.shellwords(terms) rescue terms.split(/\s+/)
       +    where = ""
       +    param = []
       +    glue  = ""
       +    terms.each do |w|
       +      next if w.downcase == 'undefined'
       +      where << glue
       +      case w
       +        when 'answered'
       +          where << "answered = ? "
       +          param << true
       +        when 'busy'
       +          where << "busy = ? "
       +          param << true
       +        else
       +          where << "( number ILIKE ? OR caller_id ILIKE ? ) "
       +          param << "%#{w}%"
       +          param << "%#{w}%"
       +      end
       +      glue = "AND " if glue.empty?
       +      @search_conditions = [ where, *param ]
       +    end
       +  end
       +
       +  def new_dialer
       +    @job = Job.new
       +    if @project
       +      @job.project = @project
       +    else
       +      @job.project = Project.last
       +    end
       +
       +    if params[:result_ids]
       +      nums = ""
       +      Call.find_each(:conditions => { :id => params[:result_ids] }) do |call|
       +        nums << call.number + "\n"
       +      end
       +      @job.range = nums
       +    end
       +
       +    respond_to do |format|
       +      format.html
       +     end
       +  end
       +
       +  def purge_calls
       +    Call.delete_all(:id => params[:result_ids])
       +    CallMedium.delete_all(:call_id => params[:result_ids])
       +    flash[:notice] = "Purged #{params[:result_ids].length} calls"
       +    if params[:id]
       +      @job = Job.find(params[:id])
       +      redirect_to view_results_path(@job.project_id, @job.id)
       +    else
       +      redirect_to analyze_path(@project)
       +    end
       +  end
       +
       +  def dialer
       +    @job = Job.new(params[:job])
       +    @job.created_by = @current_user.login
       +    @job.task = 'dialer'
       +    @job.range.to_s.gsub!(/[^0-9X:,\n]/, '')
       +    @job.cid_mask.to_s.gsub!(/[^0-9X]/, '') if @job.cid_mask != "SELF"
       +
       +    if @job.range_file.to_s != ""
       +      @job.range = @job.range_file.read.gsub(/[^0-9X:,\n]/, '')
       +    end
       +
       +    respond_to do |format|
       +      if @job.schedule
       +        flash[:notice] = 'Job was successfully created.'
       +        format.html { redirect_to :action => :index }
       +      else
       +        format.html { render :action => "new_dialer" }
       +      end
       +    end
       +  end
       +
       +  def new_analyze
       +    @job = Job.new
       +    if @project
       +      @job.project = @project
       +    else
       +      @job.project = Project.last
       +    end
       +
       +    if params[:result_ids]
       +      nums = ""
       +      Call.find_each(:conditions => { :id => params[:result_ids] }) do |call|
       +        nums << call.number + "\n"
       +      end
       +      @job.range = nums
       +    end
       +
       +    respond_to do |format|
       +      format.html
       +     end
       +  end
       +
       +  def new_identify
       +    @job = Job.new
       +    if @project
       +      @job.project = @project
       +    else
       +      @job.project = Project.last
       +    end
       +
       +    if params[:result_ids]
       +      nums = ""
       +      Call.find_each(:conditions => { :id => params[:result_ids] }) do |call|
       +        nums << call.number + "\n"
       +      end
       +      @job.range = nums
       +    end
       +
       +    respond_to do |format|
       +      format.html
       +     end
       +  end
       +
       +  def reanalyze_job
       +    @job = Job.find(params[:id])
       +    @new = Job.new({
       +      :task => 'analysis', :scope => 'job', :target_id => @job.id, :force => true,
       +      :project_id => @project.id, :status => 'submitted'
       +    })
       +    @new.created_by = @current_user.login
       +    respond_to do |format|
       +      if @new.schedule
       +        flash[:notice] = 'Analysis job was successfully created.'
       +        format.html { redirect_to jobs_path }
       +      else
       +        flash[:notice] = 'Analysis job could not run: ' + @new.errors.inspect
       +        format.html { redirect_to results_path(@project) }
       +      end
       +    end
       +  end
       +
       +  def analyze_job
       +    @job = Job.find(params[:id])
       +
       +    # Handle analysis of specific call IDs via checkbox submission
       +    if params[:result_ids]
       +      @new = Job.new({
       +        :task => 'analysis', :scope => 'calls', :target_ids => params[:result_ids],
       +        :project_id => @project.id, :status => 'submitted'
       +      })
       +    else
       +    # Otherwise analyze the entire Job
       +      @new = Job.new({
       +        :task => 'analysis', :scope => 'job', :target_id => @job.id,
       +        :project_id => @project.id, :status => 'submitted'
       +      })
       +    end
       +
       +    @new.created_by = @current_user.login
       +
       +    respond_to do |format|
       +      if @new.schedule
       +        flash[:notice] = 'Analysis job was successfully created.'
       +        format.html { redirect_to jobs_path }
       +      else
       +        flash[:notice] = 'Analysis job could not run: ' + @new.errors.inspect
       +        format.html { redirect_to results_path(@project) }
       +      end
       +    end
       +  end
       +
       +
       +  def analyze_project
       +
       +    # Handle analysis of specific call IDs via checkbox submission
       +    if params[:result_ids]
       +      @new = Job.new({
       +        :task => 'analysis', :scope => 'calls', :target_ids => params[:result_ids],
       +        :project_id => @project.id, :status => 'submitted'
       +      })
       +    else
       +    # Otherwise analyze the entire Project
       +      @new = Job.new({
       +        :task => 'analysis', :scope => 'project', :target_id => @project.id,
       +        :project_id => @project.id, :status => 'submitted'
       +      })
       +    end
       +
       +    @new.created_by = @current_user.login
       +
       +    respond_to do |format|
       +      if @new.schedule
       +        flash[:notice] = 'Analysis job was successfully created.'
       +        format.html { redirect_to jobs_path }
       +      else
       +        flash[:notice] = 'Analysis job could not run: ' + @new.errors.inspect
       +        format.html { redirect_to results_path(@project) }
       +      end
       +    end
       +  end
       +
       +  def identify_job
       +    @job = Job.find(params[:id])
       +
       +    # Handle identification of specific lines via checkbox submission
       +    if params[:result_ids]
       +      @new = Job.new({
       +        :task => 'identify', :scope => 'calls', :target_ids => params[:result_ids],
       +        :project_id => @project.id, :status => 'submitted'
       +      })
       +    else
       +    # Otherwise analyze the entire Job
       +      @new = Job.new({
       +        :task => 'identify', :scope => 'job', :target_id => @job.id,
       +        :project_id => @project.id, :status => 'submitted'
       +      })
       +    end
       +
       +    @new.created_by = @current_user.login
       +
       +    respond_to do |format|
       +      if @new.schedule
       +        flash[:notice] = 'Identify job was successfully created.'
       +        format.html { redirect_to jobs_path }
       +      else
       +        flash[:notice] = 'Identify job could not run: ' + @new.errors.inspect
       +        format.html { redirect_to results_path(@project) }
       +      end
       +    end
       +  end
       +
       +  def stop
       +    @job = Job.find(params[:id])
       +    @job.stop
       +    flash[:notice] = "Job has been cancelled"
       +    redirect_to :action => 'index'
       +  end
       +
       +  def destroy
       +    @job = Job.find(params[:id])
       +    @job.destroy
       +
       +    respond_to do |format|
       +      format.html { redirect_to(jobs_url) }
       +      format.xml  { head :ok }
       +    end
       +  end
        
        end
 (DIR) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
       @@ -1,136 +1,136 @@
        class ProjectsController < ApplicationController
        
       -        def index
       -                 @projects = Project.order('id DESC').paginate(
       -                        :page => params[:page],
       -                        :per_page => 10
       -                )
       -
       -                @new_project = Project.new
       -
       -                respond_to do |format|
       -                        format.html
       -                        format.xml        { render :xml => @projects }
       -                end
       -        end
       -
       -        def show
       -                @project = Project.find(params[:id])
       -                @active_jobs = @project.jobs.where(:status => 'running', :completed_at => nil)
       -                @inactive_jobs        = @project.jobs.order('id DESC').where('status NOT IN (?)', ['submitted', 'scheduled', 'running']).paginate(
       -                        :page => params[:page],
       -                        :per_page => 30
       -                )
       -
       -                @boxes = {
       -                        :called    => { :cnt => @project.calls.count },
       -                        :answered  => { :cnt => @project.calls.where(:answered => true).count },
       -                        :analyzed  => { :cnt => @project.calls.where('analysis_completed_at IS NOT NULL').count },
       -                        :voice     => { :cnt => @project.lines.where(:line_type => 'voice').count },
       -                        :voicemail => { :cnt => @project.lines.where(:line_type => 'voicemail').count },
       -                        :fax       => { :cnt => @project.lines.where(:line_type => 'fax').count },
       -                        :modem     => { :cnt => @project.lines.where(:line_type => 'modem').count }
       -                }
       -
       -                if @boxes[:called][:cnt] == 0
       -                        @boxes[:called][:txt] = '0'
       -                        @boxes[:called][:cls] = 'nodata'
       -
       -                        # No calls, so everything else is unknown
       -                        [ :answered, :analyzed, :voice, :voicemail, :fax, :modem ].each do |t|
       -                                @boxes[t][:txt] = '?'
       -                                @boxes[t][:cls] = 'nodata'
       -                        end
       -
       -                else
       -
       -                        [ :called, :answered, :analyzed].each do |t|
       -                                @boxes[t][:txt] = number_with_delimiter(@boxes[t][:cnt])
       -                                @boxes[t][:cls] = 'completed'
       -                        end
       -
       -                        if @boxes[:answered][:cnt] == 0
       -                                @boxes[:answered][:txt] = '0'
       -                                @boxes[:answered][:cls] = 'nodata'
       -                        end
       -
       -                        if @boxes[:analyzed][:cnt] == 0
       -                                [ :voice, :voicemail, :fax, :modem ].each do |t|
       -                                        @boxes[t][:txt] = '?'
       -                                        @boxes[t][:cls] = 'nodata'
       -                                end
       -                                @boxes[:analyzed][:cls] = 'nodata'
       -                        else
       -
       -                                @boxes[:voice][:txt] = number_with_delimiter(@boxes[:voice][:cnt])
       -                                @boxes[:voice][:cls] = 'voice'
       -
       -                                @boxes[:voicemail][:txt] = number_with_delimiter(@boxes[:voicemail][:cnt])
       -                                @boxes[:voicemail][:cls] = 'voicemail'
       -
       -                                @boxes[:fax][:txt] = number_with_delimiter(@boxes[:fax][:cnt])
       -                                @boxes[:fax][:cls] = 'fax'
       -
       -                                @boxes[:modem][:txt] = number_with_delimiter(@boxes[:modem][:cnt])
       -                                @boxes[:modem][:cls] = 'modem'
       -                        end
       -                end
       -
       -                respond_to do |format|
       -                        format.html
       -                        format.xml        { render :xml => @project }
       -                end
       -        end
       -
       -        def new
       -                @new_project = Project.new
       -                respond_to do |format|
       -                        format.html
       -                        format.xml        { render :xml => @new_project }
       -                end
       -        end
       -
       -
       -        def edit
       -                @project = Project.find(params[:id])
       -        end
       -
       -        def create
       -                @new_project = Project.new(params[:project])
       -                @new_project.created_by = current_user.login
       -
       -                respond_to do |format|
       -                        if @new_project.save
       -                                format.html { redirect_to(project_path(@new_project)) }
       -                                format.xml        { render :xml => @project, :status => :created, :location => @new_project }
       -                        else
       -                                format.html { render :action => "new" }
       -                                format.xml        { render :xml => @new_project.errors, :status => :unprocessable_entity }
       -                        end
       -                end
       -        end
       -
       -        def update
       -                @project = Project.find(params[:id])
       -
       -                respond_to do |format|
       -                        if @project.update_attributes(params[:project])
       -                                format.html { redirect_to projects_path }
       -                                format.xml        { head :ok }
       -                        else
       -                                format.html { render :action => "edit" }
       -                                format.xml        { render :xml => @project.errors, :status => :unprocessable_entity }
       -                        end
       -                end
       -        end
       -
       -        def destroy
       -                @project = Project.find(params[:id])
       -                @project.destroy
       -
       -                respond_to do |format|
       -                        format.html { redirect_to(projects_url) }
       -                        format.xml        { head :ok }
       -                end
       -        end
       +  def index
       +     @projects = Project.order('id DESC').paginate(
       +      :page => params[:page],
       +      :per_page => 10
       +    )
       +
       +    @new_project = Project.new
       +
       +    respond_to do |format|
       +      format.html
       +      format.xml  { render :xml => @projects }
       +    end
       +  end
       +
       +  def show
       +    @project = Project.find(params[:id])
       +    @active_jobs = @project.jobs.where(:status => 'running', :completed_at => nil)
       +    @inactive_jobs  = @project.jobs.order('id DESC').where('status NOT IN (?)', ['submitted', 'scheduled', 'running']).paginate(
       +      :page => params[:page],
       +      :per_page => 30
       +    )
       +
       +    @boxes = {
       +      :called    => { :cnt => @project.calls.count },
       +      :answered  => { :cnt => @project.calls.where(:answered => true).count },
       +      :analyzed  => { :cnt => @project.calls.where('analysis_completed_at IS NOT NULL').count },
       +      :voice     => { :cnt => @project.lines.where(:line_type => 'voice').count },
       +      :voicemail => { :cnt => @project.lines.where(:line_type => 'voicemail').count },
       +      :fax       => { :cnt => @project.lines.where(:line_type => 'fax').count },
       +      :modem     => { :cnt => @project.lines.where(:line_type => 'modem').count }
       +    }
       +
       +    if @boxes[:called][:cnt] == 0
       +      @boxes[:called][:txt] = '0'
       +      @boxes[:called][:cls] = 'nodata'
       +
       +      # No calls, so everything else is unknown
       +      [ :answered, :analyzed, :voice, :voicemail, :fax, :modem ].each do |t|
       +        @boxes[t][:txt] = '?'
       +        @boxes[t][:cls] = 'nodata'
       +      end
       +
       +    else
       +
       +      [ :called, :answered, :analyzed].each do |t|
       +        @boxes[t][:txt] = number_with_delimiter(@boxes[t][:cnt])
       +        @boxes[t][:cls] = 'completed'
       +      end
       +
       +      if @boxes[:answered][:cnt] == 0
       +        @boxes[:answered][:txt] = '0'
       +        @boxes[:answered][:cls] = 'nodata'
       +      end
       +
       +      if @boxes[:analyzed][:cnt] == 0
       +        [ :voice, :voicemail, :fax, :modem ].each do |t|
       +          @boxes[t][:txt] = '?'
       +          @boxes[t][:cls] = 'nodata'
       +        end
       +        @boxes[:analyzed][:cls] = 'nodata'
       +      else
       +
       +        @boxes[:voice][:txt] = number_with_delimiter(@boxes[:voice][:cnt])
       +        @boxes[:voice][:cls] = 'voice'
       +
       +        @boxes[:voicemail][:txt] = number_with_delimiter(@boxes[:voicemail][:cnt])
       +        @boxes[:voicemail][:cls] = 'voicemail'
       +
       +        @boxes[:fax][:txt] = number_with_delimiter(@boxes[:fax][:cnt])
       +        @boxes[:fax][:cls] = 'fax'
       +
       +        @boxes[:modem][:txt] = number_with_delimiter(@boxes[:modem][:cnt])
       +        @boxes[:modem][:cls] = 'modem'
       +      end
       +    end
       +
       +    respond_to do |format|
       +      format.html
       +      format.xml  { render :xml => @project }
       +    end
       +  end
       +
       +  def new
       +    @new_project = Project.new
       +    respond_to do |format|
       +      format.html
       +      format.xml  { render :xml => @new_project }
       +    end
       +  end
       +
       +
       +  def edit
       +    @project = Project.find(params[:id])
       +  end
       +
       +  def create
       +    @new_project = Project.new(params[:project])
       +    @new_project.created_by = current_user.login
       +
       +    respond_to do |format|
       +      if @new_project.save
       +        format.html { redirect_to(project_path(@new_project)) }
       +        format.xml  { render :xml => @project, :status => :created, :location => @new_project }
       +      else
       +        format.html { render :action => "new" }
       +        format.xml  { render :xml => @new_project.errors, :status => :unprocessable_entity }
       +      end
       +    end
       +  end
       +
       +  def update
       +    @project = Project.find(params[:id])
       +
       +    respond_to do |format|
       +      if @project.update_attributes(params[:project])
       +        format.html { redirect_to projects_path }
       +        format.xml  { head :ok }
       +      else
       +        format.html { render :action => "edit" }
       +        format.xml  { render :xml => @project.errors, :status => :unprocessable_entity }
       +      end
       +    end
       +  end
       +
       +  def destroy
       +    @project = Project.find(params[:id])
       +    @project.destroy
       +
       +    respond_to do |format|
       +      format.html { redirect_to(projects_url) }
       +      format.xml  { head :ok }
       +    end
       +  end
        end
 (DIR) diff --git a/app/controllers/providers_controller.rb b/app/controllers/providers_controller.rb
       @@ -2,13 +2,13 @@ class ProvidersController < ApplicationController
        
          def index
        
       -           @providers = Provider.order('id DESC').paginate(
       -                :page => params[:page],
       -                :per_page => 10
       -        )
       +     @providers = Provider.order('id DESC').paginate(
       +    :page => params[:page],
       +    :per_page => 10
       +  )
        
       -        @new_provider = Provider.new
       -        @new_provider.enabled = true
       +  @new_provider = Provider.new
       +  @new_provider.enabled = true
        
            respond_to do |format|
              format.html # index.html.erb
       @@ -18,8 +18,8 @@ class ProvidersController < ApplicationController
        
          def new
            @provider = Provider.new
       -        @provider.enabled = true
       -        @provider.port = 4569
       +  @provider.enabled = true
       +  @provider.port = 4569
        
            respond_to do |format|
              format.html # new.html.erb
       @@ -29,12 +29,12 @@ class ProvidersController < ApplicationController
        
          def edit
            @provider = Provider.find(params[:id])
       -        @provider.pass = "********"
       +  @provider.pass = "********"
          end
        
          def create
            @provider = Provider.new(params[:provider])
       -        @provider.enabled = true
       +  @provider.enabled = true
        
            respond_to do |format|
              if @provider.save
       @@ -52,10 +52,10 @@ class ProvidersController < ApplicationController
          def update
            @provider = Provider.find(params[:id])
        
       -        # Dont set the password if its the placeholder
       -        if params[:provider] and params[:provider][:pass] and params[:provider][:pass] == "********"
       -                params[:provider].delete(:pass)
       -        end
       +  # Dont set the password if its the placeholder
       +  if params[:provider] and params[:provider][:pass] and params[:provider][:pass] == "********"
       +    params[:provider].delete(:pass)
       +  end
        
            respond_to do |format|
              if @provider.update_attributes(params[:provider])
 (DIR) diff --git a/app/controllers/user_sessions_controller.rb b/app/controllers/user_sessions_controller.rb
       @@ -1,23 +1,23 @@
        class UserSessionsController < ApplicationController
       -        before_filter :require_no_user, :only => [:new, :create]
       -        before_filter :require_user, :only => :destroy
       -        layout 'login'
       +  before_filter :require_no_user, :only => [:new, :create]
       +  before_filter :require_user, :only => :destroy
       +  layout 'login'
        
       -        def new
       -                @user_session = UserSession.new
       -        end
       +  def new
       +    @user_session = UserSession.new
       +  end
        
       -        def create
       -                @user_session = UserSession.new(params[:user_session])
       -                if @user_session.save
       -                        redirect_back_or_default projects_path
       -                else
       -                        render :action => :new
       -                end
       -        end
       +  def create
       +    @user_session = UserSession.new(params[:user_session])
       +    if @user_session.save
       +      redirect_back_or_default projects_path
       +    else
       +      render :action => :new
       +    end
       +  end
        
       -        def destroy
       -                current_user_session.destroy
       -                redirect_back_or_default login_url
       -        end
       +  def destroy
       +    current_user_session.destroy
       +    redirect_back_or_default login_url
       +  end
        end
 (DIR) diff --git a/app/helpers/analyze_helper.rb b/app/helpers/analyze_helper.rb
       @@ -2,21 +2,21 @@ module AnalyzeHelper
        
        
        def fwd_match_html(pct)
       -        %Q|<span class="badge fwd_match_span" style='background-color: #{pct_to_color(pct)};'>
       +  %Q|<span class="badge fwd_match_span" style='background-color: #{pct_to_color(pct)};'>
        
       -        #{"%.3f" % pct.to_f}% Match
       +  #{"%.3f" % pct.to_f}% Match
        
       -        </span>
       +  </span>
        
       -        |
       +  |
        end
        
        def rev_match_html(pct)
       -        %Q|<span class="rev_match_span" style='padding-left: #{ (pct.to_i * 2).to_i }px; background-color: #{pct_to_color(pct)};'>#{pct}%</span>|
       +  %Q|<span class="rev_match_span" style='padding-left: #{ (pct.to_i * 2).to_i }px; background-color: #{pct_to_color(pct)};'>#{pct}%</span>|
        end
        
        def pct_to_color(pct)
       -        "#" + "20" + (pct.to_i * 2.00).to_i.to_s(16).rjust(2, "0") + "20"
       +  "#" + "20" + (pct.to_i * 2.00).to_i.to_s(16).rjust(2, "0") + "20"
        end
        
        end
 (DIR) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
       @@ -1,178 +1,178 @@
        # Methods added to this helper will be available to all templates in the application.
        module ApplicationHelper
        
       -        def select_tag_for_filter(nvpairs, params)
       -          _url = ( url_for :overwrite_params => { }).split('?')[0]
       -          _html = %{<span class="pull-left filter-label">Filter: </span> }
       -          _html << %{<select name="show" class="filter-select" }
       -          _html << %{onchange="window.location='#{_url}' + '?show=' + this.value"> }
       -          nvpairs.each do |pair|
       -            _html << %{<option value="#{h(pair[:scope])}" }
       -            if params[:show] == pair[:scope] || ((params[:show].nil? || params[:show].empty?) && pair[:scope] == "all")
       -              _html << %{ selected="selected" }
       -            end
       -            _html << %{>#{pair[:label]} }
       -            _html << %{</option>}
       -          end
       -          _html << %{</select>}
       -          raw(_html)
       -        end
       -
       -        def select_match_scope(nvpairs, params)
       -          _url = ( url_for :overwrite_params => { }).split('?')[0]
       -          _html = %{<span class="pull-left filter-label">Matching Scope: </span> }
       -          _html << %{<select name="match_scope" class="filter-select" }
       -          _html << %{onchange="window.location='#{_url}' + '?match_scope=' + this.value"> }
       -          nvpairs.each do |pair|
       -            _html << %{<option value="#{h(pair[:scope])}" }
       -            if params[:match_scope] == pair[:scope] || ((params[:match_scope].nil? || params[:match_scope].empty?) && pair[:scope] == "job")
       -              _html << %{ selected="selected" }
       -            end
       -            _html << %{>#{pair[:label]} }
       -            _html << %{</option>}
       -          end
       -          _html << %{</select>}
       -          raw(_html)
       -        end
       -
       -        def set_focus(element_id)
       -                javascript_tag(" $elem = $(\"#{element_id}\"); if (null !== $elem && $elem.length > 0){$elem.focus()}")
       -        end
       -
       -        def format_job_details(job)
       -                begin
       -                        info = Marshal.load(job.args.to_s)
       -
       -                        ttip = raw("<div class='task_args_formatted'>")
       -                        info.each_pair do |k,v|
       -                                ttip << raw("<div class='task_args_var'>") + h(truncate(k.to_s, :length => 20)) + raw(": </div> ")
       -                                ttip << raw("<div class='task_args_val'>") + h(truncate((v.to_s), :length => 20)) + raw("&nbsp;</div>")
       -                        end
       -                        ttip << raw("</div>\n")
       -                        outp = raw("<span class='xpopover' rel='popover' data-title=\"#{job.task.capitalize} Task ##{job.id}\" data-content=\"#{ttip}\">#{h job.task.capitalize}</span>")
       -                        outp
       -                rescue ::Exception => e
       -                        job.status.to_s.capitalize
       -                end
       -        end
       -
       -        def format_job_status(job)
       -                case job.status
       -                when 'error'
       -                        ttip = h(job.error.to_s)
       -                        outp = raw("<span class='xpopover' rel='popover' data-title=\"Task Details\" data-content=\"#{ttip}\">#{h job.status.capitalize}</span>")
       -                        outp
       -                else
       -                        job.status.to_s.capitalize
       -                end
       -        end
       -
       -        def format_job_rate(job)
       -                pluralize( (job.rate * 60.0).to_i, "call") + "/min"
       -        end
       -
       -        #
       -        # Includes any javascripts specific to this view. The hosts/show view
       -        # will automatically include any javascripts at public/javascripts/hosts/show.js.
       -        #
       -        # @return [void]
       -        def include_view_javascript
       -                #
       -                # Sprockets treats index.js as special, so the js for the index action must be called _index.js instead.
       -                # http://guides.rubyonrails.org/asset_pipeline.html#using-index-files
       -                #
       -
       -                controller_action_name = controller.action_name
       -
       -                if controller_action_name == 'index'
       -                        safe_action_name = '_index'
       -                else
       -                        safe_action_name = controller_action_name
       -                end
       -
       -                include_view_javascript_named(safe_action_name)
       -        end
       -
       -        # Includes the named javascript for this controller if it exists.
       -        #
       -        # @return [void]
       -        def include_view_javascript_named(name)
       -
       -                controller_path = controller.controller_path
       -                extensions = ['.coffee', '.js.coffee']
       -                javascript_controller_pathname = Rails.root.join('app', 'assets', 'javascripts', controller_path)
       -                pathnames = extensions.collect { |extension|
       -                        javascript_controller_pathname.join("#{name}#{extension}")
       -                }
       -
       -                if pathnames.any?(&:exist?)
       -                        path = File.join(controller_path, name)
       -                        content_for(:view_javascript) do
       -                                javascript_include_tag path
       -                        end
       -                end
       -        end
       -
       -        def escape_javascript_dq(str)
       -                escape_javascript(str.strip).gsub("\\'", "'").gsub("\t", "    ")
       -        end
       -
       -        def submit_checkboxes_to(name, path, html={})
       -                if html[:confirm]
       -                        confirm = html.delete(:confirm)
       -                        link_to(name, "#", html.merge({:onclick => "if(confirm('#{h confirm}')){ submit_checkboxes_to('#{path}','#{form_authenticity_token}')}else{return false;}" }))
       -                else
       -                        link_to(name, "#", html.merge({:onclick => "submit_checkboxes_to('#{path}','#{form_authenticity_token}')" }))
       -                end
       -        end
       -
       -        # Scrub out data that can break the JSON parser
       -        #
       -        # data - The String json to be scrubbed.
       -        #
       -        # Returns the String json with invalid data removed.
       -        def json_data_scrub(data)
       -                data.to_s.gsub(/[\x00-\x1f]/){ |x| "\\x%.2x" % x.unpack("C*")[0] }
       -        end
       -
       -        # Returns the properly escaped sEcho parameter that DataTables expects.
       -        def echo_data_tables
       -                h(params[:sEcho]).to_json.html_safe
       -        end
       -
       -        # Generate the markup for the call's row checkbox.
       -        # Returns the String markup html, escaped for json.
       -        def call_checkbox_tag(call)
       -                check_box_tag("result_ids[]", call.id, false, :id => nil).to_json.html_safe
       -        end
       -
       -        def call_number_html(call)
       -                json_data_scrub(h(call.number)).to_json.html_safe
       -        end
       -
       -        def call_caller_id_html(call)
       -                json_data_scrub(h(call.caller_id)).to_json.html_safe
       -        end
       -
       -        def call_provider_html(call)
       -                json_data_scrub(h(call.provider.name)).to_json.html_safe
       -        end
       -
       -        def call_answered_html(call)
       -                json_data_scrub(h(call.answered ? "Yes" : "No")).to_json.html_safe
       -        end
       -
       -        def call_busy_html(call)
       -                json_data_scrub(h(call.busy ? "Yes" : "No")).to_json.html_safe
       -        end
       -
       -        def call_audio_length_html(call)
       -                json_data_scrub(h(call.audio_length.to_s)).to_json.html_safe
       -        end
       -
       -        def call_ring_length_html(call)
       -                json_data_scrub(h(call.ring_length.to_s)).to_json.html_safe
       -        end
       +  def select_tag_for_filter(nvpairs, params)
       +    _url = ( url_for :overwrite_params => { }).split('?')[0]
       +    _html = %{<span class="pull-left filter-label">Filter: </span> }
       +    _html << %{<select name="show" class="filter-select" }
       +    _html << %{onchange="window.location='#{_url}' + '?show=' + this.value"> }
       +    nvpairs.each do |pair|
       +      _html << %{<option value="#{h(pair[:scope])}" }
       +      if params[:show] == pair[:scope] || ((params[:show].nil? || params[:show].empty?) && pair[:scope] == "all")
       +        _html << %{ selected="selected" }
       +      end
       +      _html << %{>#{pair[:label]} }
       +      _html << %{</option>}
       +    end
       +    _html << %{</select>}
       +    raw(_html)
       +  end
       +
       +  def select_match_scope(nvpairs, params)
       +    _url = ( url_for :overwrite_params => { }).split('?')[0]
       +    _html = %{<span class="pull-left filter-label">Matching Scope: </span> }
       +    _html << %{<select name="match_scope" class="filter-select" }
       +    _html << %{onchange="window.location='#{_url}' + '?match_scope=' + this.value"> }
       +    nvpairs.each do |pair|
       +      _html << %{<option value="#{h(pair[:scope])}" }
       +      if params[:match_scope] == pair[:scope] || ((params[:match_scope].nil? || params[:match_scope].empty?) && pair[:scope] == "job")
       +        _html << %{ selected="selected" }
       +      end
       +      _html << %{>#{pair[:label]} }
       +      _html << %{</option>}
       +    end
       +    _html << %{</select>}
       +    raw(_html)
       +  end
       +
       +  def set_focus(element_id)
       +    javascript_tag(" $elem = $(\"#{element_id}\"); if (null !== $elem && $elem.length > 0){$elem.focus()}")
       +  end
       +
       +  def format_job_details(job)
       +    begin
       +      info = Marshal.load(job.args.to_s)
       +
       +      ttip = raw("<div class='task_args_formatted'>")
       +      info.each_pair do |k,v|
       +        ttip << raw("<div class='task_args_var'>") + h(truncate(k.to_s, :length => 20)) + raw(": </div> ")
       +        ttip << raw("<div class='task_args_val'>") + h(truncate((v.to_s), :length => 20)) + raw("&nbsp;</div>")
       +      end
       +      ttip << raw("</div>\n")
       +      outp = raw("<span class='xpopover' rel='popover' data-title=\"#{job.task.capitalize} Task ##{job.id}\" data-content=\"#{ttip}\">#{h job.task.capitalize}</span>")
       +      outp
       +    rescue ::Exception => e
       +      job.status.to_s.capitalize
       +    end
       +  end
       +
       +  def format_job_status(job)
       +    case job.status
       +    when 'error'
       +      ttip = h(job.error.to_s)
       +      outp = raw("<span class='xpopover' rel='popover' data-title=\"Task Details\" data-content=\"#{ttip}\">#{h job.status.capitalize}</span>")
       +      outp
       +    else
       +      job.status.to_s.capitalize
       +    end
       +  end
       +
       +  def format_job_rate(job)
       +    pluralize( (job.rate * 60.0).to_i, "call") + "/min"
       +  end
       +
       +  #
       +  # Includes any javascripts specific to this view. The hosts/show view
       +  # will automatically include any javascripts at public/javascripts/hosts/show.js.
       +  #
       +  # @return [void]
       +  def include_view_javascript
       +    #
       +    # Sprockets treats index.js as special, so the js for the index action must be called _index.js instead.
       +    # http://guides.rubyonrails.org/asset_pipeline.html#using-index-files
       +    #
       +
       +    controller_action_name = controller.action_name
       +
       +    if controller_action_name == 'index'
       +      safe_action_name = '_index'
       +    else
       +      safe_action_name = controller_action_name
       +    end
       +
       +    include_view_javascript_named(safe_action_name)
       +  end
       +
       +  # Includes the named javascript for this controller if it exists.
       +  #
       +  # @return [void]
       +  def include_view_javascript_named(name)
       +
       +    controller_path = controller.controller_path
       +    extensions = ['.coffee', '.js.coffee']
       +    javascript_controller_pathname = Rails.root.join('app', 'assets', 'javascripts', controller_path)
       +    pathnames = extensions.collect { |extension|
       +      javascript_controller_pathname.join("#{name}#{extension}")
       +    }
       +
       +    if pathnames.any?(&:exist?)
       +      path = File.join(controller_path, name)
       +      content_for(:view_javascript) do
       +        javascript_include_tag path
       +      end
       +    end
       +  end
       +
       +  def escape_javascript_dq(str)
       +    escape_javascript(str.strip).gsub("\\'", "'").gsub("\t", "    ")
       +  end
       +
       +  def submit_checkboxes_to(name, path, html={})
       +    if html[:confirm]
       +      confirm = html.delete(:confirm)
       +      link_to(name, "#", html.merge({:onclick => "if(confirm('#{h confirm}')){ submit_checkboxes_to('#{path}','#{form_authenticity_token}')}else{return false;}" }))
       +    else
       +      link_to(name, "#", html.merge({:onclick => "submit_checkboxes_to('#{path}','#{form_authenticity_token}')" }))
       +    end
       +  end
       +
       +  # Scrub out data that can break the JSON parser
       +  #
       +  # data - The String json to be scrubbed.
       +  #
       +  # Returns the String json with invalid data removed.
       +  def json_data_scrub(data)
       +    data.to_s.gsub(/[\x00-\x1f]/){ |x| "\\x%.2x" % x.unpack("C*")[0] }
       +  end
       +
       +  # Returns the properly escaped sEcho parameter that DataTables expects.
       +  def echo_data_tables
       +    h(params[:sEcho]).to_json.html_safe
       +  end
       +
       +  # Generate the markup for the call's row checkbox.
       +  # Returns the String markup html, escaped for json.
       +  def call_checkbox_tag(call)
       +    check_box_tag("result_ids[]", call.id, false, :id => nil).to_json.html_safe
       +  end
       +
       +  def call_number_html(call)
       +    json_data_scrub(h(call.number)).to_json.html_safe
       +  end
       +
       +  def call_caller_id_html(call)
       +    json_data_scrub(h(call.caller_id)).to_json.html_safe
       +  end
       +
       +  def call_provider_html(call)
       +    json_data_scrub(h(call.provider.name)).to_json.html_safe
       +  end
       +
       +  def call_answered_html(call)
       +    json_data_scrub(h(call.answered ? "Yes" : "No")).to_json.html_safe
       +  end
       +
       +  def call_busy_html(call)
       +    json_data_scrub(h(call.busy ? "Yes" : "No")).to_json.html_safe
       +  end
       +
       +  def call_audio_length_html(call)
       +    json_data_scrub(h(call.audio_length.to_s)).to_json.html_safe
       +  end
       +
       +  def call_ring_length_html(call)
       +    json_data_scrub(h(call.ring_length.to_s)).to_json.html_safe
       +  end
        
        
        end
 (DIR) diff --git a/app/models/call_medium.rb b/app/models/call_medium.rb
       @@ -15,6 +15,6 @@
        #
        
        class CallMedium < ActiveRecord::Base
       -        belongs_to :call
       -        belongs_to :project
       +  belongs_to :call
       +  belongs_to :project
        end
 (DIR) diff --git a/app/models/job.rb b/app/models/job.rb
       @@ -20,152 +20,152 @@
        
        class Job < ActiveRecord::Base
        
       -        reportable :hourly, :aggregation => :count, :grouping => :hour, :date_column => :created_at, :cacheable => false
       -        reportable :daily, :aggregation => :count, :grouping => :day, :date_column => :created_at, :cacheable => false
       -        reportable :weeky, :aggregation => :count, :grouping => :week, :date_column => :created_at, :cacheable => false
       -        reportable :monthly, :aggregation => :count, :grouping => :month, :date_column => :created_at, :cacheable => false
       -
       -        class JobValidator < ActiveModel::Validator
       -                def validate(record)
       -                        case record.task
       -                        when 'dialer'
       -
       -                                cracked_range = WarVOX::Phone.crack_mask(record.range) rescue []
       -                                unless cracked_range.length > 0
       -                                        record.errors[:range] << "No valid ranges were specified"
       -                                end
       -
       -                                cracked_mask = WarVOX::Phone.crack_mask(record.cid_mask) rescue []
       -                                unless cracked_mask.length > 0
       -                                        record.errors[:cid_mask] << "No valid Caller ID mask was specified"
       -                                end
       -
       -                                unless record.seconds.to_i > 0 and record.seconds.to_i < 300
       -                                        record.errors[:seconds] << "Seconds should be between 1 and 300"
       -                                end
       -
       -                                unless record.lines.to_i > 0 and record.lines.to_i < 10000
       -                                        record.errors[:lines] << "Lines should be between 1 and 10,000"
       -                                end
       -
       -                                $stderr.puts "Errors: #{record.errors.map{|x| x.inspect}}"
       -
       -                        when 'analysis'
       -                                unless ['calls', 'job', 'project', 'global'].include?(record.scope)
       -                                        record.errors[:scope] << "Scope must be calls, job, project, or global"
       -                                end
       -                                if record.scope == "job" and Job.where(:id => record.target_id.to_i, :task => ['import', 'dialer']).count == 0
       -                                        record.errors[:job_id] << "The job_id is not valid"
       -                                end
       -                                if record.scope == "project" and Project.where(:id => record.target_id.to_i).count == 0
       -                                        record.errors[:project_id] << "The project_id is not valid"
       -                                end
       -                                if record.scope == "calls" and (record.target_ids.nil? or record.target_ids.length == 0)
       -                                        record.errors[:target_ids] << "The target_ids list is empty"
       -                                end
       -                        when 'import'
       -                        else
       -                                record.errors[:base] << "Invalid task specified"
       -                        end
       -                end
       -        end
       -
       -        # XXX: Purging a single job will be slow, but deleting the project is fast
       -        has_many :calls, :dependent => :destroy
       -
       -        belongs_to :project
       -
       -        attr_accessible :task, :status, :progress
       -
       -        validates_presence_of :project_id
       -
       -        attr_accessible :project_id
       -
       -
       -        # Allow the base Job class to be used for Dial Jobs
       -        attr_accessor :range
       -        attr_accessor :range_file
       -        attr_accessor :lines
       -        attr_accessor :seconds
       -        attr_accessor :cid_mask
       -
       -        attr_accessible :range, :seconds, :lines, :cid_mask, :range_file
       -
       -        attr_accessor :scope
       -        attr_accessor :force
       -        attr_accessor :target_id
       -        attr_accessor :target_ids
       -
       -        attr_accessible :scope, :force, :target_id, :target_ids
       -
       -
       -        validates_with JobValidator
       -
       -        def stop
       -                self.class.where(id: self.id).update_all(status: 'cancelled')
       -        end
       -
       -        def update_progress(pct)
       -                if pct >= 100
       -                        self.class.where(id: self.id).update_all(:progress => pct, :completed_at => Time.now, :status => 'completed')
       -                else
       -                        self.class.where(id: self.id).update_all(:progress => pct)
       -                end
       -        end
       -
       -        def details
       -                Marshal.load(self.args) rescue {}
       -        end
       -
       -        def schedule
       -                case task
       -                when 'dialer'
       -                        self.status = 'submitted'
       -                        self.args   = Marshal.dump({
       -                                :range    => self.range,
       -                                :lines    => self.lines.to_i,
       -                                :seconds  => self.seconds.to_i,
       -                                :cid_mask => self.cid_mask
       -                        })
       -
       -                        return self.save
       -
       -                when 'analysis'
       -                        self.status = 'submitted'
       -                        d = {
       +  reportable :hourly, :aggregation => :count, :grouping => :hour, :date_column => :created_at, :cacheable => false
       +  reportable :daily, :aggregation => :count, :grouping => :day, :date_column => :created_at, :cacheable => false
       +  reportable :weeky, :aggregation => :count, :grouping => :week, :date_column => :created_at, :cacheable => false
       +  reportable :monthly, :aggregation => :count, :grouping => :month, :date_column => :created_at, :cacheable => false
       +
       +  class JobValidator < ActiveModel::Validator
       +    def validate(record)
       +      case record.task
       +      when 'dialer'
       +
       +        cracked_range = WarVOX::Phone.crack_mask(record.range) rescue []
       +        unless cracked_range.length > 0
       +          record.errors[:range] << "No valid ranges were specified"
       +        end
       +
       +        cracked_mask = WarVOX::Phone.crack_mask(record.cid_mask) rescue []
       +        unless cracked_mask.length > 0
       +          record.errors[:cid_mask] << "No valid Caller ID mask was specified"
       +        end
       +
       +        unless record.seconds.to_i > 0 and record.seconds.to_i < 300
       +          record.errors[:seconds] << "Seconds should be between 1 and 300"
       +        end
       +
       +        unless record.lines.to_i > 0 and record.lines.to_i < 10000
       +          record.errors[:lines] << "Lines should be between 1 and 10,000"
       +        end
       +
       +        $stderr.puts "Errors: #{record.errors.map{|x| x.inspect}}"
       +
       +      when 'analysis'
       +        unless ['calls', 'job', 'project', 'global'].include?(record.scope)
       +          record.errors[:scope] << "Scope must be calls, job, project, or global"
       +        end
       +        if record.scope == "job" and Job.where(:id => record.target_id.to_i, :task => ['import', 'dialer']).count == 0
       +          record.errors[:job_id] << "The job_id is not valid"
       +        end
       +        if record.scope == "project" and Project.where(:id => record.target_id.to_i).count == 0
       +          record.errors[:project_id] << "The project_id is not valid"
       +        end
       +        if record.scope == "calls" and (record.target_ids.nil? or record.target_ids.length == 0)
       +          record.errors[:target_ids] << "The target_ids list is empty"
       +        end
       +      when 'import'
       +      else
       +        record.errors[:base] << "Invalid task specified"
       +      end
       +    end
       +  end
       +
       +  # XXX: Purging a single job will be slow, but deleting the project is fast
       +  has_many :calls, :dependent => :destroy
       +
       +  belongs_to :project
       +
       +  attr_accessible :task, :status, :progress
       +
       +  validates_presence_of :project_id
       +
       +  attr_accessible :project_id
       +
       +
       +  # Allow the base Job class to be used for Dial Jobs
       +  attr_accessor :range
       +  attr_accessor :range_file
       +  attr_accessor :lines
       +  attr_accessor :seconds
       +  attr_accessor :cid_mask
       +
       +  attr_accessible :range, :seconds, :lines, :cid_mask, :range_file
       +
       +  attr_accessor :scope
       +  attr_accessor :force
       +  attr_accessor :target_id
       +  attr_accessor :target_ids
       +
       +  attr_accessible :scope, :force, :target_id, :target_ids
       +
       +
       +  validates_with JobValidator
       +
       +  def stop
       +    self.class.where(id: self.id).update_all(status: 'cancelled')
       +  end
       +
       +  def update_progress(pct)
       +    if pct >= 100
       +      self.class.where(id: self.id).update_all(:progress => pct, :completed_at => Time.now, :status => 'completed')
       +    else
       +      self.class.where(id: self.id).update_all(:progress => pct)
       +    end
       +  end
       +
       +  def details
       +    Marshal.load(self.args) rescue {}
       +  end
       +
       +  def schedule
       +    case task
       +    when 'dialer'
       +      self.status = 'submitted'
       +      self.args   = Marshal.dump({
       +        :range    => self.range,
       +        :lines    => self.lines.to_i,
       +        :seconds  => self.seconds.to_i,
       +        :cid_mask => self.cid_mask
       +      })
       +
       +      return self.save
       +
       +    when 'analysis'
       +      self.status = 'submitted'
       +      d = {
                                        :scope      => self.scope,          # job / project/ global
                                        :force      => !!(self.force),      # true / false
                                        :target_id  => self.target_id.to_i, # job_id or project_id or nil
                                        :target_ids => (self.target_ids || []).map{|x| x.to_i }
                                }
       -                        $stderr.puts d.inspect
       -
       -                        self.args = Marshal.dump({
       -                                :scope      => self.scope,          # job / project/ global
       -                                :force      => !!(self.force),      # true / false
       -                                :target_id  => self.target_id.to_i, # job_id or project_id or nil
       -                                :target_ids => (self.target_ids || []).map{|x| x.to_i }
       -                        })
       -                        return self.save
       -                else
       -                        raise ::RuntimeError, "Unsupported Job type"
       -                end
       -        end
       -
       -        def rate
       -                tend = (self.completed_at || Time.now)
       -                tlen = tend.to_f - self.started_at.to_f
       -
       -                case self.task
       -                when 'dialer'
       -                        Call.where('job_id = ?', self.id).count() / tlen
       -                when 'analysis'
       -                        Call.where('job_id = ? AND analysis_completed_at > ? AND analysis_completed_at < ?', self.details[:target_id], self.created_at, tend).count() / tlen
       -                when 'import'
       -                        Call.where('job_id = ?', self.id).count() / tlen
       -                else
       -                        0
       -                end
       -        end
       +      $stderr.puts d.inspect
       +
       +      self.args = Marshal.dump({
       +        :scope      => self.scope,          # job / project/ global
       +        :force      => !!(self.force),      # true / false
       +        :target_id  => self.target_id.to_i, # job_id or project_id or nil
       +        :target_ids => (self.target_ids || []).map{|x| x.to_i }
       +      })
       +      return self.save
       +    else
       +      raise ::RuntimeError, "Unsupported Job type"
       +    end
       +  end
       +
       +  def rate
       +    tend = (self.completed_at || Time.now)
       +    tlen = tend.to_f - self.started_at.to_f
       +
       +    case self.task
       +    when 'dialer'
       +      Call.where('job_id = ?', self.id).count() / tlen
       +    when 'analysis'
       +      Call.where('job_id = ? AND analysis_completed_at > ? AND analysis_completed_at < ?', self.details[:target_id], self.created_at, tend).count() / tlen
       +    when 'import'
       +      Call.where('job_id = ?', self.id).count() / tlen
       +    else
       +      0
       +    end
       +  end
        
        end
 (DIR) diff --git a/app/models/line.rb b/app/models/line.rb
       @@ -12,18 +12,18 @@
        #
        
        class Line < ActiveRecord::Base
       -        has_many :line_attributes, :dependent => :delete_all
       -        belongs_to :project
       +  has_many :line_attributes, :dependent => :delete_all
       +  belongs_to :project
        
       -        def set_attribute(name, value, ctype='text/plain')
       -                la = LineAttribute.where(line_id: self.id, project_id: self.project_id, name: name).first_or_create
       -                la.value = value
       -                la.ctype = ctype
       -                la.save
       -                la
       -        end
       +  def set_attribute(name, value, ctype='text/plain')
       +    la = LineAttribute.where(line_id: self.id, project_id: self.project_id, name: name).first_or_create
       +    la.value = value
       +    la.ctype = ctype
       +    la.save
       +    la
       +  end
        
       -        def get_attribute(name)
       -                LineAttribute.where(:line_id => self[:id], :name => name).first
       -        end
       +  def get_attribute(name)
       +    LineAttribute.where(:line_id => self[:id], :name => name).first
       +  end
        end
 (DIR) diff --git a/app/models/line_attribute.rb b/app/models/line_attribute.rb
       @@ -13,6 +13,6 @@
        #
        
        class LineAttribute < ActiveRecord::Base
       -        belongs_to :line
       -        belongs_to :project
       +  belongs_to :line
       +  belongs_to :project
        end
 (DIR) diff --git a/app/models/project.rb b/app/models/project.rb
       @@ -14,15 +14,15 @@
        
        class Project < ActiveRecord::Base
        
       -        validates_presence_of :name
       -        validates_uniqueness_of :name
       +  validates_presence_of :name
       +  validates_uniqueness_of :name
        
       -        attr_accessible :name, :description, :included, :excluded
       +  attr_accessible :name, :description, :included, :excluded
        
       -        # This is optimized for fast project deletion, even with thousands of calls/jobs/lines
       -        has_many :lines, :dependent => :delete_all
       -        has_many :line_attributes, :dependent => :delete_all
       -        has_many :calls, :dependent => :delete_all
       -        has_many :call_media, :dependent => :delete_all
       -        has_many :jobs, :dependent => :delete_all
       +  # This is optimized for fast project deletion, even with thousands of calls/jobs/lines
       +  has_many :lines, :dependent => :delete_all
       +  has_many :line_attributes, :dependent => :delete_all
       +  has_many :calls, :dependent => :delete_all
       +  has_many :call_media, :dependent => :delete_all
       +  has_many :jobs, :dependent => :delete_all
        end
 (DIR) diff --git a/app/models/provider.rb b/app/models/provider.rb
       @@ -15,11 +15,11 @@
        #
        
        class Provider < ActiveRecord::Base
       -        has_many :dial_results
       +  has_many :dial_results
        
       -        validates_presence_of :name, :host, :port, :user, :pass, :lines
       -        validates_numericality_of :port, :less_than => 65536, :greater_than => 0
       -        validates_numericality_of :lines, :less_than => 255, :greater_than => 0
       +  validates_presence_of :name, :host, :port, :user, :pass, :lines
       +  validates_numericality_of :port, :less_than => 65536, :greater_than => 0
       +  validates_numericality_of :lines, :less_than => 255, :greater_than => 0
        
       -        attr_accessible :enabled, :name, :host, :port, :user, :pass, :lines
       +  attr_accessible :enabled, :name, :host, :port, :user, :pass, :lines
        end
 (DIR) diff --git a/app/models/settings.rb b/app/models/settings.rb
       @@ -12,5 +12,5 @@
        #
        
        class Settings < RailsSettings::CachedSettings
       -        attr_accessible :var
       +  attr_accessible :var
        end
 (DIR) diff --git a/app/models/signature.rb b/app/models/signature.rb
       @@ -14,6 +14,6 @@
        #
        
        class Signature < ActiveRecord::Base
       -        has_many :signature_fps
       +  has_many :signature_fps
        
        end
 (DIR) diff --git a/app/models/signature_fp.rb b/app/models/signature_fp.rb
       @@ -1,4 +1,4 @@
        class SignatureFp < ActiveRecord::Base
       -        belongs_to :signature
       +  belongs_to :signature
        
        end
 (DIR) diff --git a/app/models/user.rb b/app/models/user.rb
       @@ -24,11 +24,11 @@
        #
        
        class User < ActiveRecord::Base
       -        include RailsSettings::Extend
       -        acts_as_authentic do |c|
       -                c.validate_email_field = false
       -                c.merge_validates_length_of_password_field_options :minimum => 8
       -                c.merge_validates_length_of_password_confirmation_field_options :minimum => 8
       -                c.logged_in_timeout = 1.day
       -        end
       +  include RailsSettings::Extend
       +  acts_as_authentic do |c|
       +    c.validate_email_field = false
       +    c.merge_validates_length_of_password_field_options :minimum => 8
       +    c.merge_validates_length_of_password_confirmation_field_options :minimum => 8
       +    c.logged_in_timeout = 1.day
       +  end
        end
 (DIR) diff --git a/app/models/user_session.rb b/app/models/user_session.rb
       @@ -1,3 +1,3 @@
        class UserSession < Authlogic::Session::Base
       -        logout_on_timeout true
       +  logout_on_timeout true
        end
 (DIR) diff --git a/bin/adduser b/bin/adduser
       @@ -17,19 +17,19 @@ require APP_PATH
        Rails.application.require_environment!
        
        def generate_password
       -        set = ( [*(0x21 .. 0x2f)] + [*(0x3a .. 0x3F)] + [*(0x5b .. 0x60)] + [*(0x7b .. 0x7e)] ).flatten.pack("C*")
       -        set << "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
       -        str = ''
       -        cnt = 0
       -        while not (str.length >= 8 and str =~ /[A-Za-z]/ and str =~ /[0-9]/ and str =~ /[\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5c\x5d\x5e\x5f\x60\x7b\x7c\x7d\x7e]/)
       -                if str.length > 12
       -                        str = str[0,4]
       -                        next
       -                end
       -                str << set[ rand(set.length), 1]
       -                cnt += 1
       -        end
       -        str
       +  set = ( [*(0x21 .. 0x2f)] + [*(0x3a .. 0x3F)] + [*(0x5b .. 0x60)] + [*(0x7b .. 0x7e)] ).flatten.pack("C*")
       +  set << "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
       +  str = ''
       +  cnt = 0
       +  while not (str.length >= 8 and str =~ /[A-Za-z]/ and str =~ /[0-9]/ and str =~ /[\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5c\x5d\x5e\x5f\x60\x7b\x7c\x7d\x7e]/)
       +    if str.length > 12
       +      str = str[0,4]
       +      next
       +    end
       +    str << set[ rand(set.length), 1]
       +    cnt += 1
       +  end
       +  str
        end
        
        
       @@ -40,32 +40,32 @@ user = username ? User.find_by_login(username) : nil
        
        if not user
        
       -           if ! username
       -                $stdout.write "[*] Please enter a username: "
       -                $stdout.flush
       -                username = $stdin.readline.strip
       -        end
       +     if ! username
       +    $stdout.write "[*] Please enter a username: "
       +    $stdout.flush
       +    username = $stdin.readline.strip
       +  end
        
       -        if ! (username and username.strip.length > 0)
       -                $stdout.puts "[-] Invalid username specified"
       -                exit(0)
       -        end
       +  if ! (username and username.strip.length > 0)
       +    $stdout.puts "[-] Invalid username specified"
       +    exit(0)
       +  end
        
       -        if not password
       -                randpass = generate_password
       -                $stdout.puts ""
       -                $stdout.puts "[*] Creating user '#{username}' with password '#{randpass}' ..."
       -                $stdout.puts ""
       -                password = randpass
       -        end
       +  if not password
       +    randpass = generate_password
       +    $stdout.puts ""
       +    $stdout.puts "[*] Creating user '#{username}' with password '#{randpass}' ..."
       +    $stdout.puts ""
       +    password = randpass
       +  end
        
       -        user = User.new()
       -        user.login = username
       -        user.password = password
       -        user.password_confirmation = password
       -        user.save!
       +  user = User.new()
       +  user.login = username
       +  user.password = password
       +  user.password_confirmation = password
       +  user.save!
        
       -        $stdout.puts "[*] User #{user.login} has been created, please change your password on login."
       +  $stdout.puts "[*] User #{user.login} has been created, please change your password on login."
        else
       -        $stdout.puts "[*] That user account already exists, please try 'resetpw' script"
       +  $stdout.puts "[*] That user account already exists, please try 'resetpw' script"
        end
 (DIR) diff --git a/bin/analyze_result.rb b/bin/analyze_result.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        require 'warvox'
       @@ -23,13 +23,13 @@ $0  = "warvox(analyzer): #{inp} #{num}"
        begin
        
        $stdout.write(
       -        Marshal.dump(
       -                WarVOX::Jobs::Analysis.analyze_call(
       -                        inp, num
       -                )
       -        )
       +  Marshal.dump(
       +    WarVOX::Jobs::Analysis.analyze_call(
       +      inp, num
       +    )
       +  )
        )
        
        rescue ::Errno::EPIPE
       -        # Hide pipe errors (parent is killed when task was cancelled)
       +  # Hide pipe errors (parent is killed when task was cancelled)
        end
 (DIR) diff --git a/bin/audio_raw_to_fprint.rb b/bin/audio_raw_to_fprint.rb
       @@ -6,15 +6,15 @@
        # 
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
        require 'warvox'
        
        def usage
       -        $stderr.puts "Usage: #{$0} <input.raw> <output.txt>"
       -        exit
       +  $stderr.puts "Usage: #{$0} <input.raw> <output.txt>"
       +  exit
        end
        
        #
       @@ -25,14 +25,14 @@ inp = ARGV.shift
        out = ARGV.shift
        
        if (inp and inp == "-h") or not inp
       -        usage()
       +  usage()
        end
        
        raw = WarVOX::Audio::Raw.from_file(inp)
        if out 
       -        ::File.open(out, "wb") do |fd|
       -                fd.write( "{" + raw.to_freq_sig.map{|x| x.to_s}.join(",") + "}" )
       -        end
       +  ::File.open(out, "wb") do |fd|
       +    fd.write( "{" + raw.to_freq_sig.map{|x| x.to_s}.join(",") + "}" )
       +  end
        else 
       -        $stdout.write( "{" + raw.to_freq_sig.map{|x| x.to_s}.join(",") + "}" )
       +  $stdout.write( "{" + raw.to_freq_sig.map{|x| x.to_s}.join(",") + "}" )
        end
 (DIR) diff --git a/bin/audio_raw_to_wav.rb b/bin/audio_raw_to_wav.rb
       @@ -6,15 +6,15 @@
        # 
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
        require 'warvox'
        
        def usage
       -        $stderr.puts "Usage: #{$0} <input.raw> <output.wav>"
       -        exit
       +  $stderr.puts "Usage: #{$0} <input.raw> <output.wav>"
       +  exit
        end
        
        #
       @@ -25,14 +25,14 @@ inp = ARGV.shift
        out = ARGV.shift
        
        if (inp and inp == "-h") or not inp
       -        usage()
       +  usage()
        end
        
        raw = WarVOX::Audio::Raw.from_file(inp)
        if out 
       -        ::File.open(out, "wb") do |fd|
       -                fd.write(raw.to_wav)
       -        end
       +  ::File.open(out, "wb") do |fd|
       +    fd.write(raw.to_wav)
       +  end
        else 
       -        $stdout.write(raw.to_wav)
       +  $stdout.write(raw.to_wav)
        end
 (DIR) diff --git a/bin/audio_trim.rb b/bin/audio_trim.rb
       @@ -6,15 +6,15 @@
        # 
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
        require 'warvox'
        
        def usage
       -        $stderr.puts "Usage: #{$0} [offset] [length] <input.raw> <output.raw>"
       -        exit
       +  $stderr.puts "Usage: #{$0} [offset] [length] <input.raw> <output.raw>"
       +  exit
        end
        
        #
       @@ -27,26 +27,26 @@ inp = ARGV.shift
        out = ARGV.shift
        
        if (off and off == "-h") or not off
       -        usage()
       +  usage()
        end
        
        buf = ''
        ifd = nil
        
        if inp 
       -        ifd = ::File.open(inp, "rb")
       +  ifd = ::File.open(inp, "rb")
        else 
       -        $stdin.binmode
       -        ifd = $stdin
       +  $stdin.binmode
       +  ifd = $stdin
        end
        
        ofd = nil
        
        if out 
       -        ofd = ::File.open(out, "wb")
       +  ofd = ::File.open(out, "wb")
        else 
       -        $stdout.binmode
       -        ofd = $stdout
       +  $stdout.binmode
       +  ofd = $stdout
        end
        
        
 (DIR) diff --git a/bin/cache_clear.rb b/bin/cache_clear.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
 (DIR) diff --git a/bin/export_audio.rb b/bin/export_audio.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
       @@ -18,8 +18,8 @@ ENV['RAILS_ENV'] ||= 'production'
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..'))
        
        def usage
       -        $stderr.puts "Usage: #{$0} [Output Dir] [Project ID] <Line Type>"
       -        exit
       +  $stderr.puts "Usage: #{$0} [Output Dir] [Project ID] <Line Type>"
       +  exit
        end
        
        #
       @@ -31,61 +31,61 @@ project_id = ARGV.shift
        line_type  = ARGV.shift
        
        if(output and output == "-h") or (! output)
       -        usage()
       +  usage()
        end
        
        require 'config/boot'
        require 'config/environment'
        
        if project_id.to_i == 0
       -        $stderr.puts "Listing all projects"
       -        $stderr.puts "===================="
       -        Project.all.each do |j|
       -                puts "#{j.id}\t#{j.name}\t#{j.created_at}"
       -        end
       -        exit
       +  $stderr.puts "Listing all projects"
       +  $stderr.puts "===================="
       +  Project.all.each do |j|
       +    puts "#{j.id}\t#{j.name}\t#{j.created_at}"
       +  end
       +  exit
        end
        
        FileUtils.mkdir_p(output)
        
        begin
       -        cond = { :project_id => project_id.to_i, :answered => true, :busy => false }
       -        if line_type
       -                cond[:line_type] = line_type.downcase
       -        end
       -
       -        Call.where(cond).order(number: :asc).each do |r|
       -                m = r.media
       -                if m and m.audio
       -
       -                        ::File.open(File.join(output, "#{r.number}.raw"), "wb") do |fd|
       -                                fd.write(m.audio)
       -                        end
       -
       -                        ::File.open(File.join(output, "#{r.number}.yml"), "wb") do |fd|
       -                                fd.write(r.to_yaml)
       -                        end
       -
       -                        if m.mp3
       -                                ::File.open(File.join(output, "#{r.number}.mp3"), "wb") do |fd|
       -                                        fd.write(m.mp3)
       -                                end
       -                        end
       -
       -                        if m.png_big
       -                                ::File.open(File.join(output, "#{r.number}_wave.png"), "wb") do |fd|
       -                                        fd.write(m.png_big)
       -                                end
       -                        end
       -
       -                        if m.png_big_freq
       -                                ::File.open(File.join(output, "#{r.number}_freq.png"), "wb") do |fd|
       -                                        fd.write(m.png_big_freq)
       -                                end
       -                        end
       -
       -                        $stderr.puts "[*] Exported #{r.number}..."
       -
       -                end
       -        end
       +  cond = { :project_id => project_id.to_i, :answered => true, :busy => false }
       +  if line_type
       +    cond[:line_type] = line_type.downcase
       +  end
       +
       +  Call.where(cond).order(number: :asc).each do |r|
       +    m = r.media
       +    if m and m.audio
       +
       +      ::File.open(File.join(output, "#{r.number}.raw"), "wb") do |fd|
       +        fd.write(m.audio)
       +      end
       +
       +      ::File.open(File.join(output, "#{r.number}.yml"), "wb") do |fd|
       +        fd.write(r.to_yaml)
       +      end
       +
       +      if m.mp3
       +        ::File.open(File.join(output, "#{r.number}.mp3"), "wb") do |fd|
       +          fd.write(m.mp3)
       +        end
       +      end
       +
       +      if m.png_big
       +        ::File.open(File.join(output, "#{r.number}_wave.png"), "wb") do |fd|
       +          fd.write(m.png_big)
       +        end
       +      end
       +
       +      if m.png_big_freq
       +        ::File.open(File.join(output, "#{r.number}_freq.png"), "wb") do |fd|
       +          fd.write(m.png_big_freq)
       +        end
       +      end
       +
       +      $stderr.puts "[*] Exported #{r.number}..."
       +
       +    end
       +  end
        end
 (DIR) diff --git a/bin/export_list.rb b/bin/export_list.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
       @@ -17,8 +17,8 @@ ENV['RAILS_ENV'] ||= 'production'
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..'))
        
        def usage
       -        $stderr.puts "Usage: #{$0} [Job ID] <Type>"
       -        exit
       +  $stderr.puts "Usage: #{$0} [Job ID] <Type>"
       +  exit
        end
        
        #
       @@ -29,37 +29,37 @@ project_id = ARGV.shift
        line_type  = ARGV.shift
        
        if(project_id and project_id == "-h")
       -        usage()
       +  usage()
        end
        
        if project_id.to_i == 0
       -        usage()
       +  usage()
        end
        
        require 'config/boot'
        require 'config/environment'
        
        if(not project_id)
       -        $stderr.puts "Listing all projects"
       -        $stderr.puts "===================="
       -        Project.all.each do |j|
       -                puts "#{j.id}\t#{j.name}\t#{j.created_at}"
       -        end
       -        exit
       +  $stderr.puts "Listing all projects"
       +  $stderr.puts "===================="
       +  Project.all.each do |j|
       +    puts "#{j.id}\t#{j.name}\t#{j.created_at}"
       +  end
       +  exit
        end
        
        fields = %W{ number line_type caller_id answered busy audio_length ring_length peak_freq }
        begin
       -        $stdout.puts fields.to_csv
       -        cond = { :project_id => project_id.to_i }
       -        if line_type
       -                cond[:line_type] = line_type.downcase
       -        end
       -        Call.where(cond).order(number: :asc).each do |r|
       -                out = []
       -                fields.each do |f|
       -                        out << r[f].to_s
       -                end
       -                $stdout.puts out.to_csv
       -        end
       +  $stdout.puts fields.to_csv
       +  cond = { :project_id => project_id.to_i }
       +  if line_type
       +    cond[:line_type] = line_type.downcase
       +  end
       +  Call.where(cond).order(number: :asc).each do |r|
       +    out = []
       +    fields.each do |f|
       +      out << r[f].to_s
       +    end
       +    $stdout.puts out.to_csv
       +  end
        end
 (DIR) diff --git a/bin/iaxrecord.rb b/bin/iaxrecord.rb
       @@ -4,7 +4,7 @@ $:.unshift(::File.join(::File.dirname(__FILE__), "..", "lib"))
        
        
        def stop
       -        exit(0)
       +  exit(0)
        end
        
        trap("SIGINT")  { stop() }
       @@ -16,56 +16,56 @@ require "optparse"
        
        parser = OptionParser.new
        opts   = {
       -        :recording_time => 52
       +  :recording_time => 52
        }
        
        parser.banner = "Usage: #{$0} [options]"
        parser.on("-s server") do |v|
       -        opts[:server_host] = v
       +  opts[:server_host] = v
        end
        
        parser.on("-u user") do |v|
       -        opts[:username] = v
       +  opts[:username] = v
        end
        
        parser.on("-p pass") do |v|
       -        opts[:password] = v
       +  opts[:password] = v
        end
        
        parser.on("-o output") do |v|
       -        opts[:output] = v
       +  opts[:output] = v
        end
        
        parser.on("-n number") do |v|
       -        opts[:called_number] = v
       +  opts[:called_number] = v
        end
        
        parser.on("-c cid") do |v|
       -        opts[:caller_number] = v
       +  opts[:caller_number] = v
        end
        
        parser.on("-l seconds") do |v|
       -        opts[:recording_time] = v.to_i
       +  opts[:recording_time] = v.to_i
        end
        
        parser.on("-d") do |v|
       -        opts[:debugging] = true
       +  opts[:debugging] = true
        end
        
        parser.on("-k keys") do |v|
       -        opts[:sendkeys] = v
       +  opts[:sendkeys] = v
        end
        
        parser.on("-h") do
       -        $stderr.puts parser
       -        exit(1)
       +  $stderr.puts parser
       +  exit(1)
        end
        
        parser.parse!(ARGV)
        
        if not (opts[:server_host] and opts[:username] and opts[:password] and opts[:called_number] and opts[:output])
       -        $stderr.puts parser
       -        exit(1)
       +  $stderr.puts parser
       +  exit(1)
        end
        
        
       @@ -74,33 +74,33 @@ cli = WarVOX::Proto::IAX2::Client.new(opts)
        reg = cli.create_call
        r   = reg.register
        if not r
       -        $stderr.puts "ERROR: Unable to register with the IAX server"
       -        exit(0)
       +  $stderr.puts "ERROR: Unable to register with the IAX server"
       +  exit(0)
        end
        
        c = cli.create_call
        r = c.dial( opts[:called_number] )
        if not r
       -        $stderr.puts "ERROR: Unable to dial the requested number"
       -        exit(0)
       +  $stderr.puts "ERROR: Unable to dial the requested number"
       +  exit(0)
        end
        
        begin
        
        ::Timeout.timeout( opts[:recording_time] ) do
       -        while (c.state != :hangup)
       -                case c.state
       -                when :ringing
       -                when :answered
       -                when :hangup
       -                        break
       -                end
       -                select(nil,nil,nil, 0.25)
       -        end
       +  while (c.state != :hangup)
       +    case c.state
       +    when :ringing
       +    when :answered
       +    when :hangup
       +      break
       +    end
       +    select(nil,nil,nil, 0.25)
       +  end
        end
        rescue ::Timeout::Error
        ensure
       -        c.hangup rescue nil
       +  c.hangup rescue nil
        end
        
        cli.shutdown
       @@ -108,8 +108,8 @@ cli.shutdown
        cnt = 0
        fd = ::File.open( opts[:output], "wb")
        c.each_audio_frame do |frame|
       -        fd.write(frame)
       -        cnt += frame.length
       +  fd.write(frame)
       +  cnt += frame.length
        end
        fd.close
        
 (DIR) diff --git a/bin/identify_matches.rb b/bin/identify_matches.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
       @@ -19,8 +19,8 @@ require 'config/boot'
        require 'config/environment'
        
        def usage
       -        $stderr.puts "Usage: #{$0} [job|all] <fprint>"
       -        exit
       +  $stderr.puts "Usage: #{$0} [job|all] <fprint>"
       +  exit
        end
        
        #
       @@ -31,49 +31,49 @@ job = ARGV.shift
        fp  = ARGV.shift
        
        if(job and job == "-h")
       -        usage()
       +  usage()
        end
        
        if(not job)
       -        $stderr.puts "Listing all available jobs"
       -        $stderr.puts "=========================="
       -        DialJob.find(:all).each do |j|
       -                puts "#{j.id}\t#{j.started_at} --> #{j.completed_at}"
       -        end
       -        exit
       +  $stderr.puts "Listing all available jobs"
       +  $stderr.puts "=========================="
       +  DialJob.find(:all).each do |j|
       +    puts "#{j.id}\t#{j.started_at} --> #{j.completed_at}"
       +  end
       +  exit
        end
        
        fp  = $stdin.read.strip if fp == "-"
        job = nil if job.downcase == "all"
        
        if not fp
       -        usage()
       +  usage()
        end
        
        
        begin
       -        res = nil
       -        job = DialJob.find(job.to_i) if job
       -        if job
       -                res = DialResult.find_by_sql "SELECT dial_results.*,  " +
       -                        " (( icount('#{fp}'::int[] & dial_results.fprint::int[]) / icount('#{fp}'::int[])::float ) * 100.0 ) AS matchscore " +
       -                        "FROM dial_results " +
       -                        "WHERE " +
       -                        " icount(dial_results.fprint) > 0 AND " +
       -                        " dial_results.dial_job_id = '#{job.id}' " +
       -                        "ORDER BY matchscore DESC"
       -        else
       -                res = DialResult.find_by_sql "SELECT dial_results.*,  " +
       -                        " (( icount('#{fp}'::int[] & dial_results.fprint::int[]) / icount('#{fp}'::int[])::float ) * 100.0 ) AS matchscore " +
       -                        "FROM dial_results " +
       -                        "WHERE " +
       -                        " icount(dial_results.fprint) > 0 " +
       -                        "ORDER BY matchscore DESC"
       -        end
       -        res.each do |r|
       -                $stdout.puts "#{"%.2f" % r.matchscore}\t#{r.dial_job_id}\t#{r.number}"
       -        end
       +  res = nil
       +  job = DialJob.find(job.to_i) if job
       +  if job
       +    res = DialResult.find_by_sql "SELECT dial_results.*,  " +
       +      " (( icount('#{fp}'::int[] & dial_results.fprint::int[]) / icount('#{fp}'::int[])::float ) * 100.0 ) AS matchscore " +
       +      "FROM dial_results " +
       +      "WHERE " +
       +      " icount(dial_results.fprint) > 0 AND " +
       +      " dial_results.dial_job_id = '#{job.id}' " +
       +      "ORDER BY matchscore DESC"
       +  else
       +    res = DialResult.find_by_sql "SELECT dial_results.*,  " +
       +      " (( icount('#{fp}'::int[] & dial_results.fprint::int[]) / icount('#{fp}'::int[])::float ) * 100.0 ) AS matchscore " +
       +      "FROM dial_results " +
       +      "WHERE " +
       +      " icount(dial_results.fprint) > 0 " +
       +      "ORDER BY matchscore DESC"
       +  end
       +  res.each do |r|
       +    $stdout.puts "#{"%.2f" % r.matchscore}\t#{r.dial_job_id}\t#{r.number}"
       +  end
        rescue ActiveRecord::RecordNotFound
       -        $stderr.puts "Job not found"
       -        exit
       +  $stderr.puts "Job not found"
       +  exit
        end
 (DIR) diff --git a/bin/import_audio.rb b/bin/import_audio.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
       @@ -19,8 +19,8 @@ ENV['RAILS_ENV'] ||= 'production'
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..'))
        
        def usage
       -        $stderr.puts "Usage: #{$0} [Input Directory] <Project ID> <Provider ID>"
       -        exit(1)
       +  $stderr.puts "Usage: #{$0} [Input Directory] <Project ID> <Provider ID>"
       +  exit(1)
        end
        
        #
       @@ -29,7 +29,7 @@ end
        
        dir = ARGV.shift() || usage()
        if (dir and dir =="-h") or (! dir)
       -        usage()
       +  usage()
        end
        
        require 'config/boot'
       @@ -41,47 +41,47 @@ provider_id = ARGV.shift
        todo = Dir["#{dir}/**/*.raw"].to_a
        
        if todo.empty?
       -        $stderr.puts "Error: No raw audio files found within #{dir}"
       -        exit(1)
       +  $stderr.puts "Error: No raw audio files found within #{dir}"
       +  exit(1)
        end
        
        project  = nil
        provider = nil
        
        if project_id
       -        project = Project.where(:id => project_id).first
       -        unless project
       -                $stderr.puts "Error: Specified Project ID not found"
       -                exit(1)
       -        end
       +  project = Project.where(:id => project_id).first
       +  unless project
       +    $stderr.puts "Error: Specified Project ID not found"
       +    exit(1)
       +  end
        end
        
        if provider_id
       -        provider = Provider.where(:id => provider_id).first
       -        unless provider
       -                $stderr.puts "Error: Specified Provider ID not found"
       -                exit(1)
       -        end
       +  provider = Provider.where(:id => provider_id).first
       +  unless provider
       +    $stderr.puts "Error: Specified Provider ID not found"
       +    exit(1)
       +  end
        end
        
        unless project
       -        project = Project.create(
       -                :name       => "Import from #{dir} at #{Time.now.utc.to_s}",
       -                :created_by => "importer"
       -        )
       +  project = Project.create(
       +    :name       => "Import from #{dir} at #{Time.now.utc.to_s}",
       +    :created_by => "importer"
       +  )
        end
        
        provider = Provider.first
        unless provider
       -        provider = Provider.create(
       -                :name    => 'Import Provider',
       -                :host    => 'localhost',
       -                :port    => 4369,
       -                :user    => "null",
       -                :pass    => "null",
       -                :lines   => 1,
       -                :enabled => false
       -        )
       +  provider = Provider.create(
       +    :name    => 'Import Provider',
       +    :host    => 'localhost',
       +    :port    => 4369,
       +    :user    => "null",
       +    :pass    => "null",
       +    :lines   => 1,
       +    :enabled => false
       +  )
        end
        
        
       @@ -100,32 +100,32 @@ pct  = 0
        cnt  = 0
        
        todo.each do |rfile|
       -        num, ext = File.basename(rfile).split(".", 2)
       -        dr = Call.new
       -        dr.number        = num
       -        dr.job_id        = job.id
       -        dr.project_id    = project.id
       -        dr.provider_id   = provider.id
       -        dr.answered      = true
       -        dr.busy          = false
       -        dr.audio_length  = File.size(rfile) / 16000.0
       -        dr.ring_length   = 0
       -        dr.caller_id     = num
       -        dr.save
       -
       -        mr = dr.media
       -        ::File.open(rfile, "rb") do |fd|
       -                mr.audio = fd.read(fd.stat.size)
       -                mr.save
       -        end
       -
       -        cnt += 1
       -        pct = (cnt / todo.length.to_f) * 100.0
       -        if cnt % 10 == 0
       -                job.update_progress(pct)
       -        end
       -
       -        $stdout.puts "[ %#{"%.3d" % pct.to_i} ] Imported #{num} into project '#{project.name}' ##{project.id}"
       +  num, ext = File.basename(rfile).split(".", 2)
       +  dr = Call.new
       +  dr.number        = num
       +  dr.job_id        = job.id
       +  dr.project_id    = project.id
       +  dr.provider_id   = provider.id
       +  dr.answered      = true
       +  dr.busy          = false
       +  dr.audio_length  = File.size(rfile) / 16000.0
       +  dr.ring_length   = 0
       +  dr.caller_id     = num
       +  dr.save
       +
       +  mr = dr.media
       +  ::File.open(rfile, "rb") do |fd|
       +    mr.audio = fd.read(fd.stat.size)
       +    mr.save
       +  end
       +
       +  cnt += 1
       +  pct = (cnt / todo.length.to_f) * 100.0
       +  if cnt % 10 == 0
       +    job.update_progress(pct)
       +  end
       +
       +  $stdout.puts "[ %#{"%.3d" % pct.to_i} ] Imported #{num} into project '#{project.name}' ##{project.id}"
        end
        
        job.update_progress(100)
 (DIR) diff --git a/bin/resetpw b/bin/resetpw
       @@ -20,31 +20,31 @@ uname = ARGV.shift
        upass = ARGV.shift
        
        def generate_password
       -        set = ( [*(0x21 .. 0x2f)] + [*(0x3a .. 0x3F)] + [*(0x5b .. 0x60)] + [*(0x7b .. 0x7e)] ).flatten.pack("C*")
       -        set << "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
       -        str = ''
       -        cnt = 0
       -        while not (str.length >= 8 and str =~ /[A-Za-z]/ and str =~ /[0-9]/ and str =~ /[\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5c\x5d\x5e\x5f\x60\x7b\x7c\x7d\x7e]/)
       -                if str.length > 12
       -                        str = str[0,4]
       -                        next
       -                end
       -                str << set[ rand(set.length), 1]
       -                cnt += 1
       -        end
       -        str
       +  set = ( [*(0x21 .. 0x2f)] + [*(0x3a .. 0x3F)] + [*(0x5b .. 0x60)] + [*(0x7b .. 0x7e)] ).flatten.pack("C*")
       +  set << "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
       +  str = ''
       +  cnt = 0
       +  while not (str.length >= 8 and str =~ /[A-Za-z]/ and str =~ /[0-9]/ and str =~ /[\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5c\x5d\x5e\x5f\x60\x7b\x7c\x7d\x7e]/)
       +    if str.length > 12
       +      str = str[0,4]
       +      next
       +    end
       +    str << set[ rand(set.length), 1]
       +    cnt += 1
       +  end
       +  str
        end
        
        
        user = uname ? User.find_by_login(uname) : User.first
        if uname and not user
       -        $stderr.puts "[-] User #{uname} was not found"
       -        exit(1)
       +  $stderr.puts "[-] User #{uname} was not found"
       +  exit(1)
        end
        
        if not user
       -        $stderr.puts "[-] No user account has been created"
       -        exit(1)
       +  $stderr.puts "[-] No user account has been created"
       +  exit(1)
        end
        
        randpass = upass || generate_password()
       @@ -69,9 +69,9 @@ $stdout.flush
        inp = $stdin.readline
        
        if inp.strip.downcase != 'yes'
       -        $stdout.puts "[*] Reset cancelled, hit enter to exit"
       -        $stdin.readline
       -        exit(0)
       +  $stdout.puts "[*] Reset cancelled, hit enter to exit"
       +  $stdin.readline
       +  exit(0)
        end
        
        
 (DIR) diff --git a/bin/verify_install.rb b/bin/verify_install.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        require 'warvox'
       @@ -24,34 +24,34 @@ puts(" ")
        
        
        begin
       -        require 'rubygems'
       -        puts "[*] RubyGems have been installed"
       +  require 'rubygems'
       +  puts "[*] RubyGems have been installed"
        rescue ::LoadError
       -        puts "[*] ERROR: The RubyGems package has not been installed:"
       -        puts "    $ sudo apt-get install rubygems"
       -        exit
       +  puts "[*] ERROR: The RubyGems package has not been installed:"
       +  puts "    $ sudo apt-get install rubygems"
       +  exit
        end
        
        begin
       -        require 'bundler'
       -        puts "[*] The Bundler gem has been installed"
       +  require 'bundler'
       +  puts "[*] The Bundler gem has been installed"
        rescue ::LoadError
       -        puts "[*] ERROR: The Bundler gem has not been installed:"
       -        puts "    $ sudo gem install bundler"
       -        exit
       +  puts "[*] ERROR: The Bundler gem has not been installed:"
       +  puts "    $ sudo gem install bundler"
       +  exit
        end
        
        if(not WarVOX::Config.tool_path('gnuplot'))
       -        puts "[*] ERROR: The 'gnuplot' binary could not be installed"
       -        puts "[*] $ sudo apt-get install gnuplot"
       -        exit
       +  puts "[*] ERROR: The 'gnuplot' binary could not be installed"
       +  puts "[*] $ sudo apt-get install gnuplot"
       +  exit
        end
        puts "[*] The GNUPlot binary appears to be available"
        
        if(not WarVOX::Config.tool_path('lame'))
       -        puts "[*] ERROR: The 'lame' binary could not be installed"
       -        puts "[*] $ sudo apt-get install lame"
       -        exit
       +  puts "[*] ERROR: The 'lame' binary could not be installed"
       +  puts "[*] $ sudo apt-get install lame"
       +  exit
        end
        puts "[*] The LAME binary appears to be available"
        
 (DIR) diff --git a/bin/warvox.rb b/bin/warvox.rb
       @@ -9,7 +9,7 @@ require 'open3'
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
       @@ -24,52 +24,52 @@ require 'warvox'
        Dir.chdir(voxroot)
        
        def stop
       -        $stderr.puts "[-] Interrupt received, shutting down workers and web server..."
       -        Process.kill("TERM", @manager_pid) if @manager_pid
       -        exit(0)
       +  $stderr.puts "[-] Interrupt received, shutting down workers and web server..."
       +  Process.kill("TERM", @manager_pid) if @manager_pid
       +  exit(0)
        end
        
        def usage
       -        $stderr.puts "#{$0} [--address IP] [--port PORT] --background"
       -        exit(0)
       +  $stderr.puts "#{$0} [--address IP] [--port PORT] --background"
       +  exit(0)
        end
        
        opts    =
        {
       -        'ServerPort' => 7777,
       -        'ServerHost' => '127.0.0.1',
       -        'Background' => false,
       +  'ServerPort' => 7777,
       +  'ServerHost' => '127.0.0.1',
       +  'Background' => false,
        }
        
        args = GetoptLong.new(
       -        ["--address", "-a", GetoptLong::REQUIRED_ARGUMENT ],
       -        ["--port", "-p", GetoptLong::REQUIRED_ARGUMENT ],
       -        ["--daemon", "-d", GetoptLong::NO_ARGUMENT ],
       -        ["--help", "-h", GetoptLong::NO_ARGUMENT]
       +  ["--address", "-a", GetoptLong::REQUIRED_ARGUMENT ],
       +  ["--port", "-p", GetoptLong::REQUIRED_ARGUMENT ],
       +  ["--daemon", "-d", GetoptLong::NO_ARGUMENT ],
       +  ["--help", "-h", GetoptLong::NO_ARGUMENT]
        )
        
        args.each do |opt,arg|
       -        case opt
       -        when '--address'
       -                opts['ServerHost'] = arg
       -        when '--port'
       -                opts['ServerPort'] = arg
       -        when '--daemon'
       -                opts['Background'] = true
       -        when '--help'
       -                usage()
       -        end
       +  case opt
       +  when '--address'
       +    opts['ServerHost'] = arg
       +  when '--port'
       +    opts['ServerPort'] = arg
       +  when '--daemon'
       +    opts['Background'] = true
       +  when '--help'
       +    usage()
       +  end
        end
        
        args = [
       -        'server',
       -        '-p', opts['ServerPort'].to_s,
       -        '-b', opts['ServerHost'],
       -        '-e', 'production',
       +  'server',
       +  '-p', opts['ServerPort'].to_s,
       +  '-b', opts['ServerHost'],
       +  '-e', 'production',
        ]
        
        if opts['Background']
       -        args.push("-d")
       +  args.push("-d")
        end
        
        
       @@ -86,10 +86,10 @@ WarVOX::Log.info("WarVOX is starting up...")
        
        @manager_pid = Process.fork()
        if not @manager_pid
       -        while ARGV.shift do
       -        end
       -        load(manager)
       -        exit(0)
       +  while ARGV.shift do
       +  end
       +  load(manager)
       +  exit(0)
        end
        
        WarVOX::Log.info("Worker Manager has PID #{@manager_pid}")
 (DIR) diff --git a/bin/worker.rb b/bin/worker.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
       @@ -21,18 +21,18 @@ $:.unshift(File.join(File.expand_path(File.dirname(base)), '..'))
        @job  = nil
        
        def usage
       -        $stderr.puts "Usage: #{$0} [JID]"
       -        exit(1)
       +  $stderr.puts "Usage: #{$0} [JID]"
       +  exit(1)
        end
        
        def stop
       -        if @task
       -                @task.stop() rescue nil
       -        end
       -        if @job
       -                Job.where(id: @job_id).update_all({ status: 'stopped', completed_at: Time.now })
       -        end
       -        exit(0)
       +  if @task
       +    @task.stop() rescue nil
       +  end
       +  if @job
       +    Job.where(id: @job_id).update_all({ status: 'stopped', completed_at: Time.now })
       +  end
       +  exit(0)
        end
        
        #
       @@ -41,7 +41,7 @@ end
        
        jid = ARGV.shift() || usage()
        if (jid and jid =="-h") or (! jid)
       -        usage()
       +  usage()
        end
        
        require 'config/boot'
       @@ -54,9 +54,9 @@ jid = jid.to_i
        @job = Job.where(id: jid).first
        
        unless @job
       -        $stderr.puts "Error: Specified job not found"
       -        WarVOX::Log.warn("Worker rejected invalid Job #{jid}")
       -        exit(1)
       +  $stderr.puts "Error: Specified job not found"
       +  WarVOX::Log.warn("Worker rejected invalid Job #{jid}")
       +  exit(1)
        end
        
        $0 = "warvox worker: #{jid} "
       @@ -72,20 +72,20 @@ begin
        
        case @job.task
        when 'dialer'
       -        @task = WarVOX::Jobs::Dialer.new(@job.id, args)
       -        @task.start
       +  @task = WarVOX::Jobs::Dialer.new(@job.id, args)
       +  @task.start
        when 'analysis'
       -        @task = WarVOX::Jobs::Analysis.new(@job.id, args)
       -        @task.start
       +  @task = WarVOX::Jobs::Analysis.new(@job.id, args)
       +  @task.start
        else
       -        Job.where(id: @job.id).update_all({ error: 'unsupported', status: 'error' })
       +  Job.where(id: @job.id).update_all({ error: 'unsupported', status: 'error' })
        end
        
        @job.update_progress(100)
        
        rescue ::SignalException, ::SystemExit
       -        raise $!
       +  raise $!
        rescue ::Exception => e
       -        WarVOX::Log.warn("Worker #{@job.id} #{@job.task} threw an exception: #{e.class} #{e} #{e.backtrace}")
       -        Job.where(id: @job.id).update_all({ error: "Exception: #{e.class} #{e}", status: 'error', completed_at: Time.now.utc })
       +  WarVOX::Log.warn("Worker #{@job.id} #{@job.task} threw an exception: #{e.class} #{e} #{e.backtrace}")
       +  Job.where(id: @job.id).update_all({ error: "Exception: #{e.class} #{e}", status: 'error', completed_at: Time.now.utc })
        end
 (DIR) diff --git a/bin/worker_manager.rb b/bin/worker_manager.rb
       @@ -6,7 +6,7 @@
        #
        base = __FILE__
        while File.symlink?(base)
       -        base = File.expand_path(File.readlink(base), File.dirname(base))
       +  base = File.expand_path(File.readlink(base), File.dirname(base))
        end
        $:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
        
       @@ -25,136 +25,136 @@ require 'config/environment'
        @jobs = []
        
        def stop
       -        WarVOX::Log.info("Worker Manager is terminating due to signal")
       +  WarVOX::Log.info("Worker Manager is terminating due to signal")
        
       -        unless @jobs.length > 0
       -                exit(0)
       -        end
       +  unless @jobs.length > 0
       +    exit(0)
       +  end
        
       -        # Update the database
       -        Job.update_all({ :status => "stopped", :completed_at => Time.now.utc}, { :id => @jobs.map{|j| j[:id] } })
       +  # Update the database
       +  Job.update_all({ :status => "stopped", :completed_at => Time.now.utc}, { :id => @jobs.map{|j| j[:id] } })
        
       -        # Signal running jobs to shut down
       -        @jobs.map{|j| Process.kill("TERM", j[:pid]) rescue nil }
       +  # Signal running jobs to shut down
       +  @jobs.map{|j| Process.kill("TERM", j[:pid]) rescue nil }
        
       -        # Sleep for five seconds
       -        sleep(5)
       +  # Sleep for five seconds
       +  sleep(5)
        
       -        # Forcibly kill any remaining job processes
       -        @jobs.map{|j| Process.kill("KILL", j[:pid]) rescue nil }
       +  # Forcibly kill any remaining job processes
       +  @jobs.map{|j| Process.kill("KILL", j[:pid]) rescue nil }
        
       -        exit(0)
       +  exit(0)
        end
        
        
        def clear_zombies
       -        while ( r = Process.waitpid(-1, Process::WNOHANG) rescue nil ) do
       -        end
       +  while ( r = Process.waitpid(-1, Process::WNOHANG) rescue nil ) do
       +  end
        end
        
        def schedule_job(j)
       -        WarVOX::Log.debug("Worker Manager is launching job #{j.id}")
       -        @jobs <<  {
       -                :id  => j.id,
       -                :pid => Process.fork { exec("#{@worker_path} #{j.id}") }
       -        }
       +  WarVOX::Log.debug("Worker Manager is launching job #{j.id}")
       +  @jobs <<  {
       +    :id  => j.id,
       +    :pid => Process.fork { exec("#{@worker_path} #{j.id}") }
       +  }
        end
        
        def stop_cancelled_jobs
       -        jids = []
       -        @jobs.each do |x|
       -                jids << x[:id]
       -        end
       -
       -        return if jids.length == 0
       -        Job.where(:status => 'cancelled', :id => jids).find_each do |j|
       -                job = @jobs.select{ |o| o[:id] == j.id }.first
       -                next unless job and job[:pid]
       -                pid = job[:pid]
       -
       -                WarVOX::Log.debug("Worker Manager is killing job #{j.id} with PID #{pid}")
       -                Process.kill('TERM', pid)
       -        end
       +  jids = []
       +  @jobs.each do |x|
       +    jids << x[:id]
       +  end
       +
       +  return if jids.length == 0
       +  Job.where(:status => 'cancelled', :id => jids).find_each do |j|
       +    job = @jobs.select{ |o| o[:id] == j.id }.first
       +    next unless job and job[:pid]
       +    pid = job[:pid]
       +
       +    WarVOX::Log.debug("Worker Manager is killing job #{j.id} with PID #{pid}")
       +    Process.kill('TERM', pid)
       +  end
        end
        
        def clear_completed_jobs
       -        dead_pids = []
       -        dead_jids = []
       +  dead_pids = []
       +  dead_jids = []
        
       -        @jobs.each do |j|
       -                alive = Process.kill(0, j[:pid]) rescue nil
       -                next if alive
       -                dead_pids << j[:pid]
       -                dead_jids << j[:id]
       -        end
       +  @jobs.each do |j|
       +    alive = Process.kill(0, j[:pid]) rescue nil
       +    next if alive
       +    dead_pids << j[:pid]
       +    dead_jids << j[:id]
       +  end
        
       -        return unless dead_jids.length > 0
       +  return unless dead_jids.length > 0
        
       -        WarVOX::Log.debug("Worker Manager is clearing #{dead_pids.length} completed jobs")
       +  WarVOX::Log.debug("Worker Manager is clearing #{dead_pids.length} completed jobs")
        
       -        @jobs = @jobs.reject{|x| dead_pids.include?( x[:pid] ) }
       +  @jobs = @jobs.reject{|x| dead_pids.include?( x[:pid] ) }
        
       -        # Mark failed/crashed jobs as completed
       -        Job.where(id: dead_jids, completed_at: nil).update_all({completed_at: Time.now.utc})
       +  # Mark failed/crashed jobs as completed
       +  Job.where(id: dead_jids, completed_at: nil).update_all({completed_at: Time.now.utc})
        end
        
        def clear_stale_jobs
       -        jids  = @jobs.map{|x| x[:id] }
       -        stale = nil
       -
       -        if jids.length > 0
       -                stale = Job.where("completed_at IS NULL AND locked_by LIKE ? AND id NOT IN (?)", Socket.gethostname + "^%", jids)
       -        else
       -                stale = Job.where("completed_at IS NULL AND locked_by LIKE ?", Socket.gethostname + "^%")
       -        end
       -
       -        dead = []
       -        pids = {}
       -
       -        # Extract the PID from the locked_by cookie for each job
       -        stale.each do |j|
       -                host, pid, uniq = j.locked_by.to_s.split("^", 3)
       -                next unless (pid and uniq)
       -                pids[pid] ||= []
       -                pids[pid]  << j
       -        end
       -
       -        # Identify dead processes (must be same user or root)
       -        pids.keys.each do |pid|
       -                alive =        Process.kill(0, pid.to_i) rescue nil
       -                next if alive
       -                pids[pid].each do |j|
       -                        dead << j.id
       -                end
       -        end
       -
       -        # Mark these jobs as abandoned
       -        if dead.length > 0
       -                WarVOX::Log.debug("Worker Manager is marking #{dead.length} jobs as abandoned")
       -                Job.where(:id => dead).update_all({locked_by: nil, status: 'abandoned'})
       -        end
       +  jids  = @jobs.map{|x| x[:id] }
       +  stale = nil
       +
       +  if jids.length > 0
       +    stale = Job.where("completed_at IS NULL AND locked_by LIKE ? AND id NOT IN (?)", Socket.gethostname + "^%", jids)
       +  else
       +    stale = Job.where("completed_at IS NULL AND locked_by LIKE ?", Socket.gethostname + "^%")
       +  end
       +
       +  dead = []
       +  pids = {}
       +
       +  # Extract the PID from the locked_by cookie for each job
       +  stale.each do |j|
       +    host, pid, uniq = j.locked_by.to_s.split("^", 3)
       +    next unless (pid and uniq)
       +    pids[pid] ||= []
       +    pids[pid]  << j
       +  end
       +
       +  # Identify dead processes (must be same user or root)
       +  pids.keys.each do |pid|
       +    alive =  Process.kill(0, pid.to_i) rescue nil
       +    next if alive
       +    pids[pid].each do |j|
       +      dead << j.id
       +    end
       +  end
       +
       +  # Mark these jobs as abandoned
       +  if dead.length > 0
       +    WarVOX::Log.debug("Worker Manager is marking #{dead.length} jobs as abandoned")
       +    Job.where(:id => dead).update_all({locked_by: nil, status: 'abandoned'})
       +  end
        end
        
        def schedule_submitted_jobs
       -        loop do
       -                # Look for a candidate job with no current owner
       -                j  = Job.where(status: 'submitted', locked_by: nil).limit(1).first
       -                return unless j
       +  loop do
       +    # Look for a candidate job with no current owner
       +    j  = Job.where(status: 'submitted', locked_by: nil).limit(1).first
       +    return unless j
        
       -                # Try to get a lock on this job
       -                Job.where(id: j.id, locked_by: nil).update_all({locked_by: @cookie, locked_at: Time.now.utc, status: 'scheduled'})
       +    # Try to get a lock on this job
       +    Job.where(id: j.id, locked_by: nil).update_all({locked_by: @cookie, locked_at: Time.now.utc, status: 'scheduled'})
        
       -                # See if we actually got the lock
       -                j  = Job.where(id: j.id, status: 'scheduled', locked_by: @cookie).limit(1).first
       +    # See if we actually got the lock
       +    j  = Job.where(id: j.id, status: 'scheduled', locked_by: @cookie).limit(1).first
        
       -                # Try again if we lost the race,
       -                next unless j
       +    # Try again if we lost the race,
       +    next unless j
        
       -                # Hurray, we got a job, run it
       -                schedule_job(j)
       +    # Hurray, we got a job, run it
       +    schedule_job(j)
        
       -                return true
       -        end
       +    return true
       +  end
        end
        
        #
       @@ -171,24 +171,24 @@ trap("SIGTERM") { stop() }
        WarVOX::Log.info("Worker Manager initialized with cookie #{@cookie}")
        
        loop do
       -        $0 = "warvox manager: #{@jobs.length} active jobs (cookie : #{@cookie})"
       +  $0 = "warvox manager: #{@jobs.length} active jobs (cookie : #{@cookie})"
        
       -        # Clear any zombie processes
       -        clear_zombies()
       +  # Clear any zombie processes
       +  clear_zombies()
        
       -        # Clear any completed jobs
       -        clear_completed_jobs()
       +  # Clear any completed jobs
       +  clear_completed_jobs()
        
       -        # Stop any jobs cancelled by the user
       -        stop_cancelled_jobs()
       +  # Stop any jobs cancelled by the user
       +  stop_cancelled_jobs()
        
       -        # Clear locks on any stale jobs from this host
       -        clear_stale_jobs()
       +  # Clear locks on any stale jobs from this host
       +  clear_stale_jobs()
        
       -        while @jobs.length < @max_jobs
       -                break unless schedule_submitted_jobs
       -        end
       +  while @jobs.length < @max_jobs
       +    break unless schedule_submitted_jobs
       +  end
        
       -        # Sleep between 3-8 seconds before re-entering the loop
       -        sleep(rand(5) + 3)
       +  # Sleep between 3-8 seconds before re-entering the loop
       +  sleep(rand(5) + 3)
        end
 (DIR) diff --git a/config/classifiers/01.default.rb b/config/classifiers/01.default.rb
       @@ -29,24 +29,24 @@ maxf = data[:maxf]
        # Look for modems by detecting a 2100hz answer + 2250hz tone
        #
        if( (fcnt[2100] > 1.0 or fcnt[2230] > 1.0) and fcnt[2250] > 0.5)
       -        @line_type = 'modem'
       -        raise Completed
       +  @line_type = 'modem'
       +  raise Completed
        end
        
        #
        # Look for modems by detecting a peak frequency of 2250hz
        #
        if(fcnt[2100] > 1.0 and (maxf > 2245.0 and maxf < 2255.0))
       -        @line_type = 'modem'
       -        raise Completed
       +  @line_type = 'modem'
       +  raise Completed
        end
        
        #
        # Look for modems by detecting a peak frequency of 3000hz
        #
        if(fcnt[2100] > 1.0 and (maxf > 2995.0 and maxf < 3005.0))
       -        @line_type = 'modem'
       -        raise Completed
       +  @line_type = 'modem'
       +  raise Completed
        end
        
        #
       @@ -54,22 +54,22 @@ end
        #
        fax_sum = 0
        [
       -        fcnt[1625], fcnt[1660], fcnt[1825], fcnt[2100],
       -        fcnt[600],  fcnt[1855], fcnt[1100], fcnt[2250],
       -        fcnt[2230], fcnt[2220], fcnt[1800], fcnt[2095],
       -        fcnt[2105]
       +  fcnt[1625], fcnt[1660], fcnt[1825], fcnt[2100],
       +  fcnt[600],  fcnt[1855], fcnt[1100], fcnt[2250],
       +  fcnt[2230], fcnt[2220], fcnt[1800], fcnt[2095],
       +  fcnt[2105]
        ].map{|x| fax_sum += [x,1.0].min }
        if(fax_sum >= 2.0)
       -        @line_type = 'fax'
       -        raise Completed
       +  @line_type = 'fax'
       +  raise Completed
        end
        
        #
        # Dial tone detection (440hz + 350hz)
        #
        if(fcnt[440] > 1.0 and fcnt[350] > 1.0)
       -        @line_type = 'dialtone'
       -        raise Completed
       +  @line_type = 'dialtone'
       +  raise Completed
        end
        
        #
 (DIR) diff --git a/config/classifiers/99.default.rb b/config/classifiers/99.default.rb
       @@ -15,16 +15,16 @@ maxf = data[:maxf]
        # this signature can fail. For non-US numbers, the beep
        # is often a different frequency entirely.
        if(fcnt[1000] >= 1.0)
       -        @line_type = 'voicemail'
       -        raise Completed
       +  @line_type = 'voicemail'
       +  raise Completed
        end
        
        # Look for voicemail by detecting a peak frequency of
        # 1000hz. Not as accurate, but thats why this is in
        # the fallback script.
        if(maxf > 995 and maxf < 1005)
       -        @line_type = 'voicemail'
       -        raise Completed
       +  @line_type = 'voicemail'
       +  raise Completed
        end
        
        
 (DIR) diff --git a/db/migrate/20121228171549_initial_schema.rb b/db/migrate/20121228171549_initial_schema.rb
       @@ -1,182 +1,182 @@
        class InitialSchema < ActiveRecord::Migration
       -        def up
       -
       -                # Require the intarray extension
       -                execute("CREATE EXTENSION IF NOT EXISTS intarray")
       -
       -                create_table :settings do |t|
       -                        t.string :var, :null => false
       -                        t.text   :value, :null => true
       -                        t.integer :thing_id, :null => true
       -                        t.string :thing_type, :limit => 30, :null => true
       -                        t.timestamps
       -                end
       -
       -                add_index :settings, [ :thing_type, :thing_id, :var ], :unique => true
       -
       -                create_table 'users' do |t|
       -                        t.string    :login,               :null => false                # optional, you can use email instead, or both
       -                        t.string    :email,               :null => true                 # optional, you can use login instead, or both
       -                        t.string    :crypted_password,    :null => false                # optional, see below
       -                        t.string    :password_salt,       :null => false                # optional, but highly recommended
       -                        t.string    :persistence_token,   :null => false                # required
       -                        t.string    :single_access_token, :null => false                # optional, see Authlogic::Session::Params
       -                        t.string    :perishable_token,    :null => false                # optional, see Authlogic::Session::Perishability
       -
       -                        # Magic columns, just like ActiveRecord's created_at and updated_at. These are automatically maintained by Authlogic if they are present.
       -                        t.integer   :login_count,         :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
       -                        t.integer   :failed_login_count,  :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
       -                        t.datetime  :last_request_at                                    # optional, see Authlogic::Session::MagicColumns
       -                        t.datetime  :current_login_at                                   # optional, see Authlogic::Session::MagicColumns
       -                        t.datetime  :last_login_at                                      # optional, see Authlogic::Session::MagicColumns
       -                        t.string    :current_login_ip                                   # optional, see Authlogic::Session::MagicColumns
       -                        t.string    :last_login_ip                                      # optional, see Authlogic::Session::MagicColumns
       -
       -                        t.timestamps
       -                        t.boolean   "enabled", :default => true
       -                        t.boolean   "admin",   :default => true
       -                end
       -
       -                create_table 'projects' do |t|
       -                        t.timestamps
       -                        t.text      "name", :null => false
       -                        t.text      "description"
       -                        t.text                "included"
       -                        t.text                "excluded"
       -                        t.string        "created_by"
       -                end
       -
       -                create_table "jobs" do |t|
       -                        t.timestamps
       -                        t.integer        "project_id", :null => false
       -                        t.string        "locked_by"
       -                        t.timestamp        "locked_at"
       -                        t.timestamp        "started_at"
       -                        t.timestamp        "completed_at"
       -                        t.string        "created_by"
       -                        t.string        "task", :null => false
       -                        t.binary        "args"
       -                        t.string        "status"
       -                        t.text                "error"
       -                        t.integer        "progress", :default => 0
       -                end
       -
       -                create_table "lines" do |t|
       -                        t.timestamps
       -                        t.text                        "number", :null => false
       -                        t.integer                "project_id", :null => false
       -                        t.text                        "line_type"
       -                        t.text                        "notes"
       -                end
       -
       -                create_table "line_attributes" do |t|
       -                        t.timestamps
       -                        t.integer                "line_id", :null => false
       -                        t.integer                "project_id", :null => false
       -                        t.text                        "name", :null => false
       -                        t.binary                "value", :null => false
       -                        t.string                "content_type", :default => "text"
       -                end
       -
       -                create_table "calls" do |t|
       -                        # Created by the dialer job
       -                        t.timestamps
       -                        t.text                        "number", :null => false
       -                        t.integer                "project_id", :null => false
       -                        t.integer                "job_id", :null => false
       -                        t.integer                "provider_id", :null => false
       -                        t.boolean                "answered"
       -                        t.boolean                "busy"
       -                        t.text                        "error"
       -                        t.integer                "audio_length"
       -                        t.integer                "ring_length"
       -                        t.text                        "caller_id"
       -
       -                        # Generated by the analysis job
       -                        t.integer                "analysis_job_id"
       -                        t.timestamp                "analysis_started_at"
       -                        t.timestamp                "analysis_completed_at"
       -                        t.float                        "peak_freq"
       -                        t.text                        "peak_freq_data"
       -                        t.text                        "line_type"
       -                        t.integer                "fprint", :array => true
       -                end
       -
       -                create_table "call_media" do |t|
       -                        t.integer                "call_id", :null => false
       -                        t.integer                "project_id", :null => false
       -                        t.binary                "audio"
       -                        t.binary                "mp3"
       -                        t.binary                "png_big"
       -                        t.binary                "png_big_dots"
       -                        t.binary                "png_big_freq"
       -                        t.binary                "png_sig"
       -                        t.binary                "png_sig_freq"
       -                end
       -
       -                create_table "signatures" do |t|
       -                        t.timestamps
       -                        t.text                        "name", :null => false
       -                        t.string                "source"
       -                        t.text                        "description"
       -                        t.string                "category"
       -                        t.string                "line_type"
       -                        t.integer                "risk"
       -                end
       -
       -                create_table "signature_fp" do |t|
       -                        t.integer                "signature_id", :null => false
       -                        t.integer                "fprint", :array => true
       -                end
       -
       -                create_table "providers" do |t|
       -                        t.timestamps
       -                        t.text                        "name", :null => false
       -                        t.text                        "host", :null => false
       -                        t.integer                "port", :null => false
       -                        t.text                        "user"
       -                        t.text                        "pass"
       -                        t.integer                "lines", :null => false, :default => 1
       -                        t.boolean                "enabled", :default => true
       -                end
       -
       -                add_index :jobs, :project_id
       -                add_index :lines, :number
       -                add_index :lines, :project_id
       -                add_index :line_attributes, :line_id
       -                add_index :line_attributes, :project_id
       -                add_index :calls, :number
       -                add_index :calls, :job_id
       -                add_index :calls, :provider_id
       -                add_index :call_media, :call_id
       -                add_index :call_media, :project_id
       -                add_index :signature_fp, :signature_id
       -
       -        end
       -
       -        def down
       -                remove_index :jobs, :project_id
       -                remove_index :lines, :number
       -                remove_index :lines, :project_id
       -                remove_index :line_attributes, :line_id
       -                remove_index :line_attributes, :project_id
       -                remove_index :calls, :number
       -                remove_index :calls, :job_id
       -                remove_index :calls, :provider_id
       -                remove_index :call_media, :call_id
       -                remove_index :call_media, :project_id
       -                remove_index :signature_fp, :signature_id
       -
       -                drop_table "providers"
       -                drop_table "signature_fp"
       -                drop_table "signatures"
       -                drop_table "call_media"
       -                drop_table "calls"
       -                drop_table "line_attributes"
       -                drop_table "lines"
       -                drop_table "jobs"
       -                drop_table "projects"
       -                drop_table "users"
       -                drop_table "settings"
       -        end
       +  def up
       +
       +    # Require the intarray extension
       +    execute("CREATE EXTENSION IF NOT EXISTS intarray")
       +
       +    create_table :settings do |t|
       +      t.string :var, :null => false
       +      t.text   :value, :null => true
       +      t.integer :thing_id, :null => true
       +      t.string :thing_type, :limit => 30, :null => true
       +      t.timestamps
       +    end
       +
       +    add_index :settings, [ :thing_type, :thing_id, :var ], :unique => true
       +
       +    create_table 'users' do |t|
       +      t.string    :login,               :null => false                # optional, you can use email instead, or both
       +      t.string    :email,               :null => true                 # optional, you can use login instead, or both
       +      t.string    :crypted_password,    :null => false                # optional, see below
       +      t.string    :password_salt,       :null => false                # optional, but highly recommended
       +      t.string    :persistence_token,   :null => false                # required
       +      t.string    :single_access_token, :null => false                # optional, see Authlogic::Session::Params
       +      t.string    :perishable_token,    :null => false                # optional, see Authlogic::Session::Perishability
       +
       +      # Magic columns, just like ActiveRecord's created_at and updated_at. These are automatically maintained by Authlogic if they are present.
       +      t.integer   :login_count,         :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
       +      t.integer   :failed_login_count,  :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
       +      t.datetime  :last_request_at                                    # optional, see Authlogic::Session::MagicColumns
       +      t.datetime  :current_login_at                                   # optional, see Authlogic::Session::MagicColumns
       +      t.datetime  :last_login_at                                      # optional, see Authlogic::Session::MagicColumns
       +      t.string    :current_login_ip                                   # optional, see Authlogic::Session::MagicColumns
       +      t.string    :last_login_ip                                      # optional, see Authlogic::Session::MagicColumns
       +
       +      t.timestamps
       +      t.boolean   "enabled", :default => true
       +      t.boolean   "admin",   :default => true
       +    end
       +
       +    create_table 'projects' do |t|
       +      t.timestamps
       +      t.text      "name", :null => false
       +      t.text      "description"
       +      t.text    "included"
       +      t.text    "excluded"
       +      t.string  "created_by"
       +    end
       +
       +    create_table "jobs" do |t|
       +      t.timestamps
       +      t.integer  "project_id", :null => false
       +      t.string  "locked_by"
       +      t.timestamp  "locked_at"
       +      t.timestamp  "started_at"
       +      t.timestamp  "completed_at"
       +      t.string  "created_by"
       +      t.string  "task", :null => false
       +      t.binary  "args"
       +      t.string  "status"
       +      t.text    "error"
       +      t.integer  "progress", :default => 0
       +    end
       +
       +    create_table "lines" do |t|
       +      t.timestamps
       +      t.text      "number", :null => false
       +      t.integer    "project_id", :null => false
       +      t.text      "line_type"
       +      t.text      "notes"
       +    end
       +
       +    create_table "line_attributes" do |t|
       +      t.timestamps
       +      t.integer    "line_id", :null => false
       +      t.integer    "project_id", :null => false
       +      t.text      "name", :null => false
       +      t.binary    "value", :null => false
       +      t.string    "content_type", :default => "text"
       +    end
       +
       +    create_table "calls" do |t|
       +      # Created by the dialer job
       +      t.timestamps
       +      t.text      "number", :null => false
       +      t.integer    "project_id", :null => false
       +      t.integer    "job_id", :null => false
       +      t.integer    "provider_id", :null => false
       +      t.boolean    "answered"
       +      t.boolean    "busy"
       +      t.text      "error"
       +      t.integer    "audio_length"
       +      t.integer    "ring_length"
       +      t.text      "caller_id"
       +
       +      # Generated by the analysis job
       +      t.integer    "analysis_job_id"
       +      t.timestamp    "analysis_started_at"
       +      t.timestamp    "analysis_completed_at"
       +      t.float      "peak_freq"
       +      t.text      "peak_freq_data"
       +      t.text      "line_type"
       +      t.integer    "fprint", :array => true
       +    end
       +
       +    create_table "call_media" do |t|
       +      t.integer    "call_id", :null => false
       +      t.integer    "project_id", :null => false
       +      t.binary    "audio"
       +      t.binary    "mp3"
       +      t.binary    "png_big"
       +      t.binary    "png_big_dots"
       +      t.binary    "png_big_freq"
       +      t.binary    "png_sig"
       +      t.binary    "png_sig_freq"
       +    end
       +
       +    create_table "signatures" do |t|
       +      t.timestamps
       +      t.text      "name", :null => false
       +      t.string    "source"
       +      t.text      "description"
       +      t.string    "category"
       +      t.string    "line_type"
       +      t.integer    "risk"
       +    end
       +
       +    create_table "signature_fp" do |t|
       +      t.integer    "signature_id", :null => false
       +      t.integer    "fprint", :array => true
       +    end
       +
       +    create_table "providers" do |t|
       +      t.timestamps
       +      t.text      "name", :null => false
       +      t.text      "host", :null => false
       +      t.integer    "port", :null => false
       +      t.text      "user"
       +      t.text      "pass"
       +      t.integer    "lines", :null => false, :default => 1
       +      t.boolean    "enabled", :default => true
       +    end
       +
       +    add_index :jobs, :project_id
       +    add_index :lines, :number
       +    add_index :lines, :project_id
       +    add_index :line_attributes, :line_id
       +    add_index :line_attributes, :project_id
       +    add_index :calls, :number
       +    add_index :calls, :job_id
       +    add_index :calls, :provider_id
       +    add_index :call_media, :call_id
       +    add_index :call_media, :project_id
       +    add_index :signature_fp, :signature_id
       +
       +  end
       +
       +  def down
       +    remove_index :jobs, :project_id
       +    remove_index :lines, :number
       +    remove_index :lines, :project_id
       +    remove_index :line_attributes, :line_id
       +    remove_index :line_attributes, :project_id
       +    remove_index :calls, :number
       +    remove_index :calls, :job_id
       +    remove_index :calls, :provider_id
       +    remove_index :call_media, :call_id
       +    remove_index :call_media, :project_id
       +    remove_index :signature_fp, :signature_id
       +
       +    drop_table "providers"
       +    drop_table "signature_fp"
       +    drop_table "signatures"
       +    drop_table "call_media"
       +    drop_table "calls"
       +    drop_table "line_attributes"
       +    drop_table "lines"
       +    drop_table "jobs"
       +    drop_table "projects"
       +    drop_table "users"
       +    drop_table "settings"
       +  end
        end
 (DIR) diff --git a/lib/warvox.rb b/lib/warvox.rb
       @@ -11,10 +11,10 @@ require 'logger'
        
        # Global configuration
        module WarVOX
       -        VERSION = '2.0.0-dev'
       -        Base = File.expand_path(File.join(File.dirname(__FILE__), '..'))
       -        Conf = File.expand_path(File.join(Base, 'config', 'warvox.conf'))
       -        Log  = Logger.new( WarVOX::Config.log_file )
       -        Log.level = WarVOX::Config.log_level
       +  VERSION = '2.0.0-dev'
       +  Base = File.expand_path(File.join(File.dirname(__FILE__), '..'))
       +  Conf = File.expand_path(File.join(Base, 'config', 'warvox.conf'))
       +  Log  = Logger.new( WarVOX::Config.log_file )
       +  Log.level = WarVOX::Config.log_level
        
        end
 (DIR) diff --git a/lib/warvox/audio/raw.rb b/lib/warvox/audio/raw.rb
       @@ -2,329 +2,329 @@ module WarVOX
        module Audio
        class Raw
        
       -        @@kissfft_loaded = false
       -        begin
       -                require 'kissfft'
       -                @@kissfft_loaded = true
       -        rescue ::LoadError
       -        end
       -
       -        require 'zlib'
       -
       -        ##
       -        # RAW AUDIO - 8khz little-endian 16-bit signed
       -        ##
       -
       -        ##
       -        # Static methods
       -        ##
       -
       -        def self.from_str(str)
       -                self.class.new(str)
       -        end
       -
       -        def self.from_file(path)
       -                if(not path)
       -                        raise Error, "No audio path specified"
       -                end
       -
       -                if(path == "-")
       -                        return self.new($stdin.read)
       -                end
       -
       -                if(not File.readable?(path))
       -                        raise Error, "The specified audio file does not exist"
       -                end
       -
       -                if(path =~ /\.gz$/)
       -                        return self.new(Zlib::GzipReader.open(path).read)
       -                end
       -
       -                self.new(File.read(path, File.size(path)))
       -        end
       -
       -        ##
       -        # Class methods
       -        ##
       -
       -        attr_accessor :samples
       -
       -        def initialize(data)
       -                self.samples = data.unpack('v*').map do |s|
       -                        (s > 0x7fff) ? (0x10000 - s) * -1 : s
       -                end
       -        end
       -
       -        def to_raw
       -                self.samples.pack("v*")
       -        end
       -
       -        def to_wav
       -                raw = self.to_raw
       -                wav =
       -                        "RIFF" +
       -                                [raw.length + 36].pack("V") +
       -                        "WAVE" +
       -                        "fmt " +
       -                                [16, 1, 1, 8000, 16000, 2, 16 ].pack("VvvVVvv") +
       -                        "data" +
       -                                [ raw.length ].pack("V") +
       -                        raw
       -        end
       -
       -        def to_flow(opts={})
       -
       -                lo_lim = (opts[:lo_lim] || 100).to_i
       -                lo_min = (opts[:lo_min] || 5).to_i
       -                hi_min = (opts[:hi_min] || 5).to_i
       -                lo_cnt = 0
       -                hi_cnt = 0
       -
       -                data = self.samples.map {|c| c.abs}
       -
       -                #
       -                # Granular hi/low state change list
       -                #
       -                fprint = []
       -                state  = :lo
       -                idx    = 0
       -                buff   = []
       -
       -                while (idx < data.length)
       -                        case state
       -                        when :lo
       -                                while(idx < data.length and data[idx] <= lo_lim)
       -                                        buff << data[idx]
       -                                        idx += 1
       -                                end
       -
       -                                # Ignore any sequence that is too small
       -                                fprint << [:lo, buff.length, buff - [0]] if buff.length > lo_min
       -                                state  = :hi
       -                                buff   = []
       -                                next
       -                        when :hi
       -                                while(idx < data.length and data[idx] > lo_lim)
       -                                        buff << data[idx]
       -                                        idx += 1
       -                                end
       -
       -                                # Ignore any sequence that is too small
       -                                fprint << [:hi, buff.length, buff] if buff.length > hi_min
       -                                state  = :lo
       -                                buff   = []
       -                                next
       -                        end
       -                end
       -
       -                #
       -                # Merge similar blocks
       -                #
       -                final = []
       -                prev  = fprint[0]
       -                idx   = 1
       -
       -                while(idx < fprint.length)
       -
       -                        if(fprint[idx][0] == prev[0])
       -                                prev[1] += fprint[idx][1]
       -                                prev[2] += fprint[idx][2]
       -                        else
       -                                final << prev
       -                                prev  = fprint[idx]
       -                        end
       -
       -                        idx += 1
       -                end
       -                final << prev
       -
       -                #
       -                # Process results
       -                #
       -                sig = ""
       -
       -                final.each do |f|
       -                        sum = 0
       -                        f[2].each {|i| sum += i }
       -                        avg = (sum == 0) ? 0 : sum / f[2].length
       -                        sig << "#{f[0].to_s.upcase[0,1]},#{f[1]},#{avg} "
       -                end
       -
       -                # Return the results
       -                return sig
       -        end
       -
       -        def to_freq(opts={})
       -
       -                if(not @@kissfft_loaded)
       -                        raise RuntimeError, "The KissFFT module is not availabale, raw.to_freq() failed"
       -                end
       -
       -                freq_cnt = opts[:frequency_count] || 20
       -
       -                # Perform a DFT on the samples
       -                ffts = KissFFT.fftr(8192, 8000, 1, self.samples)
       -
       -                self.class.fft_to_freq_sig(ffts, freq_cnt)
       -        end
       -
       -        def to_freq_sig(opts={})
       -                fcnt = opts[:frequency_count] || 5
       -
       -                ffts = []
       -
       -                # Obtain 20 DFTs for the sample, at 1/20th second offsets into the stream
       -                0.upto(19) do |i|
       -                        ffts[i] = KissFFT.fftr(8192, 8000, 1, self.samples[ i * 400, self.samples.length - (i * 400)])
       -                end
       -
       -                # Create a frequency table at 100hz boundaries
       -                f = [ *(0.step(4000, 100)) ]
       -
       -                # Create a worker method to find the closest frequency
       -                barker = Proc.new do |t|
       -                        t = t.to_i
       -                        f.sort { |a,b|
       -                                (a-t).abs <=> (b-t).abs
       -                        }.first
       -                end
       -
       -                # Purge any empty fft slices
       -                ffts.delete(nil)
       -
       -                # Map each slice of the audio's FFT with each FFT chunk (8k samples) and then work on it
       -                tops = ffts.map{|x| x.map{|y| y.map{|z|
       -
       -                        frq,pwr = z
       -
       -                        # Toss any signals with a strength under 100
       -                        if pwr < 100.0
       -                                frq,pwr = [0,0]
       -                        # Map the signal to the closest offset of 100hz
       -                        else
       -                                frq = barker.call(frq)
       -                        end
       -
       -                        # Make sure the strength is an integer
       -                        pwr = pwr.to_i
       -
       -                        # Sort by signal strength and take the top fcnt items
       -                        [frq, pwr]}.sort{|a,b|
       -                                b[1] <=> a[1]
       -                        }[0, fcnt].map{|w|
       -                        # Grab just the frequency (drop the strength)
       -                                w[0]
       -                        # Remove any duplicates due to hz mapping
       -                        }.uniq
       -
       -                } }
       -
       -                # Track the generated 4-second chunk signatures
       -                sigs = []
       -
       -                # Expand the list of top frequencies per sample into a flat list of each permutation
       -                tops.each do |t|
       -                        next if t.length < 4
       -                        0.upto(t.length - 4) { |i| t[i].each { |a| t[i+1].each { |b| t[i+2].each { |c| t[i+3].each { |d| sigs << [a,b,c,d] } } } } }
       -                end
       -
       -                # Dump any duplicate signatures
       -                sigs = sigs.uniq
       -
       -                # Convert each signature into a single 32-bit integer
       -                # This is essentially [0-40, 0-40, 0-40, 0-40]
       -                sigs.map{|x| x.map{|y| y / 100}.pack("C4").unpack("N")[0] }
       -        end
       -
       -        # Converts a signature to a postgresql integer array (text) format
       -        def to_freq_sig_txt(opts={})
       -                "{" + to_freq_sig(opts).sort.join(",") + "}"
       -        end
       -
       -        def to_freq_sig_arr(opts={})
       -                to_freq_sig(opts)
       -        end
       -
       -        def self.fft_to_freq_sig(ffts, freq_cnt)
       -                sig = []
       -                ffts.each do |s|
       -                        res = []
       -                        maxp = 0
       -                        maxf = 0
       -                        s.each do |f|
       -                                if( f[1] > maxp )
       -                                        maxf,maxp = f
       -                                end
       -
       -                                if(maxf > 0 and f[1] < maxp and (maxf + 4.5 < f[0]))
       -                                        res << [maxf, maxp]
       -                                        maxf,maxp = [0,0]
       -                                end
       -                        end
       -
       -                        sig << res.sort{ |a,b|                             # sort by signal strength
       -                                a[1] <=> b[1]
       -                        }.reverse[0,freq_cnt].sort { |a,b|                 # take the top 20 and sort by frequency
       -                                a[0] <=> b[0]
       -                        }.map {|a| [a[0].round, a[1].round ] }             # round to whole numbers
       -                end
       -
       -                sig
       -        end
       -
       -        # Find pattern inside of sample
       -        def self.compare_freq_sig(pat, zam, opts)
       -
       -                fuzz_f = opts[:fuzz_f] || 7
       -                fuzz_p = opts[:fuzz_p] || 10
       -                final  = []
       -
       -                0.upto(zam.length - 1) do |si|
       -                        res = []
       -                        sam = zam[si, zam.length]
       -
       -                        0.upto(pat.length - 1) do |pi|
       -                                diff = []
       -                                next if not pat[pi]
       -                                next if pat[pi].length == 0
       -                                pat[pi].each do |x|
       -                                        next if not sam[pi]
       -                                        next if sam[pi].length == 0
       -                                        sam[pi].each do |y|
       -                                                if(
       -                                                        (x[0] - fuzz_f) < y[0] and
       -                                                        (x[0] + fuzz_f) > y[0] and
       -                                                        (x[1] - fuzz_p) < y[1] and
       -                                                        (x[1] + fuzz_p) > y[1]
       -                                                )
       -                                                        diff << x
       -                                                        break
       -                                                end
       -                                        end
       -                                end
       -                                res << diff
       -                        end
       -                        next if res.length == 0
       -
       -                        prev = 0
       -                        rsum = 0
       -                        ridx = 0
       -                        res.each_index do |xi|
       -                                len = res[xi].length
       -                                if(xi == 0)
       -                                        rsum += (len < 2) ? -40 : +20
       -                                else
       -                                        rsum += 20 if(prev > 11 and len > 11)
       -                                        rsum += len
       -                                end
       -                                prev = len
       -                        end
       -
       -                        final << [ (rsum / res.length.to_f), res.map {|x| x.length}]
       -                end
       -
       -                final
       -        end
       +  @@kissfft_loaded = false
       +  begin
       +    require 'kissfft'
       +    @@kissfft_loaded = true
       +  rescue ::LoadError
       +  end
       +
       +  require 'zlib'
       +
       +  ##
       +  # RAW AUDIO - 8khz little-endian 16-bit signed
       +  ##
       +
       +  ##
       +  # Static methods
       +  ##
       +
       +  def self.from_str(str)
       +    self.class.new(str)
       +  end
       +
       +  def self.from_file(path)
       +    if(not path)
       +      raise Error, "No audio path specified"
       +    end
       +
       +    if(path == "-")
       +      return self.new($stdin.read)
       +    end
       +
       +    if(not File.readable?(path))
       +      raise Error, "The specified audio file does not exist"
       +    end
       +
       +    if(path =~ /\.gz$/)
       +      return self.new(Zlib::GzipReader.open(path).read)
       +    end
       +
       +    self.new(File.read(path, File.size(path)))
       +  end
       +
       +  ##
       +  # Class methods
       +  ##
       +
       +  attr_accessor :samples
       +
       +  def initialize(data)
       +    self.samples = data.unpack('v*').map do |s|
       +      (s > 0x7fff) ? (0x10000 - s) * -1 : s
       +    end
       +  end
       +
       +  def to_raw
       +    self.samples.pack("v*")
       +  end
       +
       +  def to_wav
       +    raw = self.to_raw
       +    wav =
       +      "RIFF" +
       +        [raw.length + 36].pack("V") +
       +      "WAVE" +
       +      "fmt " +
       +        [16, 1, 1, 8000, 16000, 2, 16 ].pack("VvvVVvv") +
       +      "data" +
       +        [ raw.length ].pack("V") +
       +      raw
       +  end
       +
       +  def to_flow(opts={})
       +
       +    lo_lim = (opts[:lo_lim] || 100).to_i
       +    lo_min = (opts[:lo_min] || 5).to_i
       +    hi_min = (opts[:hi_min] || 5).to_i
       +    lo_cnt = 0
       +    hi_cnt = 0
       +
       +    data = self.samples.map {|c| c.abs}
       +
       +    #
       +    # Granular hi/low state change list
       +    #
       +    fprint = []
       +    state  = :lo
       +    idx    = 0
       +    buff   = []
       +
       +    while (idx < data.length)
       +      case state
       +      when :lo
       +        while(idx < data.length and data[idx] <= lo_lim)
       +          buff << data[idx]
       +          idx += 1
       +        end
       +
       +        # Ignore any sequence that is too small
       +        fprint << [:lo, buff.length, buff - [0]] if buff.length > lo_min
       +        state  = :hi
       +        buff   = []
       +        next
       +      when :hi
       +        while(idx < data.length and data[idx] > lo_lim)
       +          buff << data[idx]
       +          idx += 1
       +        end
       +
       +        # Ignore any sequence that is too small
       +        fprint << [:hi, buff.length, buff] if buff.length > hi_min
       +        state  = :lo
       +        buff   = []
       +        next
       +      end
       +    end
       +
       +    #
       +    # Merge similar blocks
       +    #
       +    final = []
       +    prev  = fprint[0]
       +    idx   = 1
       +
       +    while(idx < fprint.length)
       +
       +      if(fprint[idx][0] == prev[0])
       +        prev[1] += fprint[idx][1]
       +        prev[2] += fprint[idx][2]
       +      else
       +        final << prev
       +        prev  = fprint[idx]
       +      end
       +
       +      idx += 1
       +    end
       +    final << prev
       +
       +    #
       +    # Process results
       +    #
       +    sig = ""
       +
       +    final.each do |f|
       +      sum = 0
       +      f[2].each {|i| sum += i }
       +      avg = (sum == 0) ? 0 : sum / f[2].length
       +      sig << "#{f[0].to_s.upcase[0,1]},#{f[1]},#{avg} "
       +    end
       +
       +    # Return the results
       +    return sig
       +  end
       +
       +  def to_freq(opts={})
       +
       +    if(not @@kissfft_loaded)
       +      raise RuntimeError, "The KissFFT module is not availabale, raw.to_freq() failed"
       +    end
       +
       +    freq_cnt = opts[:frequency_count] || 20
       +
       +    # Perform a DFT on the samples
       +    ffts = KissFFT.fftr(8192, 8000, 1, self.samples)
       +
       +    self.class.fft_to_freq_sig(ffts, freq_cnt)
       +  end
       +
       +  def to_freq_sig(opts={})
       +    fcnt = opts[:frequency_count] || 5
       +
       +    ffts = []
       +
       +    # Obtain 20 DFTs for the sample, at 1/20th second offsets into the stream
       +    0.upto(19) do |i|
       +      ffts[i] = KissFFT.fftr(8192, 8000, 1, self.samples[ i * 400, self.samples.length - (i * 400)])
       +    end
       +
       +    # Create a frequency table at 100hz boundaries
       +    f = [ *(0.step(4000, 100)) ]
       +
       +    # Create a worker method to find the closest frequency
       +    barker = Proc.new do |t|
       +      t = t.to_i
       +      f.sort { |a,b|
       +        (a-t).abs <=> (b-t).abs
       +      }.first
       +    end
       +
       +    # Purge any empty fft slices
       +    ffts.delete(nil)
       +
       +    # Map each slice of the audio's FFT with each FFT chunk (8k samples) and then work on it
       +    tops = ffts.map{|x| x.map{|y| y.map{|z|
       +
       +      frq,pwr = z
       +
       +      # Toss any signals with a strength under 100
       +      if pwr < 100.0
       +        frq,pwr = [0,0]
       +      # Map the signal to the closest offset of 100hz
       +      else
       +        frq = barker.call(frq)
       +      end
       +
       +      # Make sure the strength is an integer
       +      pwr = pwr.to_i
       +
       +      # Sort by signal strength and take the top fcnt items
       +      [frq, pwr]}.sort{|a,b|
       +        b[1] <=> a[1]
       +      }[0, fcnt].map{|w|
       +      # Grab just the frequency (drop the strength)
       +        w[0]
       +      # Remove any duplicates due to hz mapping
       +      }.uniq
       +
       +    } }
       +
       +    # Track the generated 4-second chunk signatures
       +    sigs = []
       +
       +    # Expand the list of top frequencies per sample into a flat list of each permutation
       +    tops.each do |t|
       +      next if t.length < 4
       +      0.upto(t.length - 4) { |i| t[i].each { |a| t[i+1].each { |b| t[i+2].each { |c| t[i+3].each { |d| sigs << [a,b,c,d] } } } } }
       +    end
       +
       +    # Dump any duplicate signatures
       +    sigs = sigs.uniq
       +
       +    # Convert each signature into a single 32-bit integer
       +    # This is essentially [0-40, 0-40, 0-40, 0-40]
       +    sigs.map{|x| x.map{|y| y / 100}.pack("C4").unpack("N")[0] }
       +  end
       +
       +  # Converts a signature to a postgresql integer array (text) format
       +  def to_freq_sig_txt(opts={})
       +    "{" + to_freq_sig(opts).sort.join(",") + "}"
       +  end
       +
       +  def to_freq_sig_arr(opts={})
       +    to_freq_sig(opts)
       +  end
       +
       +  def self.fft_to_freq_sig(ffts, freq_cnt)
       +    sig = []
       +    ffts.each do |s|
       +      res = []
       +      maxp = 0
       +      maxf = 0
       +      s.each do |f|
       +        if( f[1] > maxp )
       +          maxf,maxp = f
       +        end
       +
       +        if(maxf > 0 and f[1] < maxp and (maxf + 4.5 < f[0]))
       +          res << [maxf, maxp]
       +          maxf,maxp = [0,0]
       +        end
       +      end
       +
       +      sig << res.sort{ |a,b|                             # sort by signal strength
       +        a[1] <=> b[1]
       +      }.reverse[0,freq_cnt].sort { |a,b|                 # take the top 20 and sort by frequency
       +        a[0] <=> b[0]
       +      }.map {|a| [a[0].round, a[1].round ] }             # round to whole numbers
       +    end
       +
       +    sig
       +  end
       +
       +  # Find pattern inside of sample
       +  def self.compare_freq_sig(pat, zam, opts)
       +
       +    fuzz_f = opts[:fuzz_f] || 7
       +    fuzz_p = opts[:fuzz_p] || 10
       +    final  = []
       +
       +    0.upto(zam.length - 1) do |si|
       +      res = []
       +      sam = zam[si, zam.length]
       +
       +      0.upto(pat.length - 1) do |pi|
       +        diff = []
       +        next if not pat[pi]
       +        next if pat[pi].length == 0
       +        pat[pi].each do |x|
       +          next if not sam[pi]
       +          next if sam[pi].length == 0
       +          sam[pi].each do |y|
       +            if(
       +              (x[0] - fuzz_f) < y[0] and
       +              (x[0] + fuzz_f) > y[0] and
       +              (x[1] - fuzz_p) < y[1] and
       +              (x[1] + fuzz_p) > y[1]
       +            )
       +              diff << x
       +              break
       +            end
       +          end
       +        end
       +        res << diff
       +      end
       +      next if res.length == 0
       +
       +      prev = 0
       +      rsum = 0
       +      ridx = 0
       +      res.each_index do |xi|
       +        len = res[xi].length
       +        if(xi == 0)
       +          rsum += (len < 2) ? -40 : +20
       +        else
       +          rsum += 20 if(prev > 11 and len > 11)
       +          rsum += len
       +        end
       +        prev = len
       +      end
       +
       +      final << [ (rsum / res.length.to_f), res.map {|x| x.length}]
       +    end
       +
       +    final
       +  end
        
        
        end
 (DIR) diff --git a/lib/warvox/config.rb b/lib/warvox/config.rb
       @@ -1,160 +1,160 @@
        module WarVOX
        module Config
       -        require 'yaml'
       -
       -        def self.authentication_creds
       -                user = nil
       -                pass = nil
       -                info = YAML.load_file(WarVOX::Conf)
       -                if( info and
       -                        info['authentication'] and
       -                        info['authentication']['user'] and
       -                        info['authentication']['pass']
       -                  )
       -                        user = info['authentication']['user']
       -                        pass = info['authentication']['pass']
       -                end
       -                [user,pass]
       -        end
       -
       -        def self.authenticate(user,pass)
       -                wuser,wpass = authentication_creds
       -                (wuser == user and wpass == pass) ? true : false
       -        end
       -
       -        def self.tool_path(name)
       -                info = YAML.load_file(WarVOX::Conf)
       -                return nil if not info
       -                return nil if not info['tools']
       -                return nil if not info['tools'][name]
       -                find_full_path(
       -                        info['tools'][name].gsub('%BASE%', WarVOX::Base)
       -                )
       -        end
       -
       -        def self.analysis_threads
       -                core_count = File.read("/proc/cpuinfo").scan(/^processor\s+:/).length rescue 1
       -
       -                info = YAML.load_file(WarVOX::Conf)
       -                return core_count if not info
       -                return core_count if not info['analysis_threads']
       -                return core_count if info['analysis_threads'] == 0
       -                [ info['analysis_threads'].to_i, core_count ].min
       -        end
       -
       -        def self.blacklist_path
       -                info = YAML.load_file(WarVOX::Conf)
       -                return nil if not info
       -                return nil if not info['blacklist']
       -                File.expand_path(info['blacklist'].gsub('%BASE%', WarVOX::Base))
       -        end
       -
       -        def self.blacklist_load
       -                path = blacklist_path
       -                return if not path
       -                data = File.read(path, File.size(path))
       -                sigs = []
       -
       -                File.open(path, 'r') do |fd|
       -                        lno = 0
       -                        fd.each_line do |line|
       -                                lno += 1
       -                                next if line =~ /^#/
       -                                next if line =~ /^\s+$/
       -                                line.strip!
       -                                sigs << [lno, line]
       -                        end
       -                        sigs
       -                end
       -
       -        end
       -
       -        def self.signatures_path
       -                info = YAML.load_file(WarVOX::Conf)
       -                return nil if not info
       -                return nil if not info['signatures']
       -                File.expand_path(info['signatures'].gsub('%BASE%', WarVOX::Base))
       -        end
       -
       -        def self.classifiers_path
       -                info = YAML.load_file(WarVOX::Conf)
       -                return nil if not info
       -                return nil if not info['classifiers']
       -                File.expand_path(info['classifiers'].gsub('%BASE%', WarVOX::Base))
       -        end
       -
       -        def self.log_file
       -                STDOUT
       -        end
       -
       -        def self.log_level
       -                Logger::DEBUG
       -        end
       -
       -        def self.classifiers_load
       -                path = classifiers_path
       -                sigs = []
       -                return sigs if not path
       -
       -                Dir.new(path).entries.sort{ |a,b|
       -                        a.to_i <=> b.to_i
       -                }.map{ |ent|
       -                        File.join(path, ent)
       -                }.each do |ent|
       -                        sigs << ent if File.file?(ent)
       -                end
       -
       -                sigs
       -        end
       -
       -        # This method searches the PATH environment variable for
       -        # a fully qualified path to the supplied file name.
       -        # Stolen from Rex
       -        def self.find_full_path(file_name)
       -
       -                # Return absolute paths unmodified
       -                if(file_name[0,1] == ::File::SEPARATOR)
       -                        return file_name
       -                end
       -
       -                path = ENV['PATH']
       -                if (path)
       -                        path.split(::File::PATH_SEPARATOR).each { |base|
       -                                begin
       -                                        path = base + ::File::SEPARATOR + file_name
       -                                        if (::File::Stat.new(path))
       -                                                return path
       -                                        end
       -                                rescue
       -                                end
       -                        }
       -                end
       -                return nil
       -        end
       -
       -        # This method prevents two installations of WarVOX from using the same
       -        # rails session key. The first time this method is called, it generates
       -        # a new key and stores it in the rails directory, afterwards this key
       -        # will be used every time.
       -        def self.load_session_key
       -                kfile = File.join(WarVOX::Base, 'config', 'session.key')
       -                if(not File.exists?(kfile))
       -                        # XXX: assume /dev/urandom exists
       -                        kdata = File.read('/dev/urandom', 64).unpack("H*")[0]
       -
       -                        # Create the new session key file
       -                        fd = File.new(kfile, 'w')
       -
       -                        # Make this file mode 0600
       -                        File.chmod(0600, kfile)
       -
       -                        # Write it and close
       -                        fd.write(kdata)
       -                        fd.close
       -                        return kdata
       -                end
       -                File.read(kfile)
       -        end
       +  require 'yaml'
       +
       +  def self.authentication_creds
       +    user = nil
       +    pass = nil
       +    info = YAML.load_file(WarVOX::Conf)
       +    if( info and
       +      info['authentication'] and
       +      info['authentication']['user'] and
       +      info['authentication']['pass']
       +      )
       +      user = info['authentication']['user']
       +      pass = info['authentication']['pass']
       +    end
       +    [user,pass]
       +  end
       +
       +  def self.authenticate(user,pass)
       +    wuser,wpass = authentication_creds
       +    (wuser == user and wpass == pass) ? true : false
       +  end
       +
       +  def self.tool_path(name)
       +    info = YAML.load_file(WarVOX::Conf)
       +    return nil if not info
       +    return nil if not info['tools']
       +    return nil if not info['tools'][name]
       +    find_full_path(
       +      info['tools'][name].gsub('%BASE%', WarVOX::Base)
       +    )
       +  end
       +
       +  def self.analysis_threads
       +    core_count = File.read("/proc/cpuinfo").scan(/^processor\s+:/).length rescue 1
       +
       +    info = YAML.load_file(WarVOX::Conf)
       +    return core_count if not info
       +    return core_count if not info['analysis_threads']
       +    return core_count if info['analysis_threads'] == 0
       +    [ info['analysis_threads'].to_i, core_count ].min
       +  end
       +
       +  def self.blacklist_path
       +    info = YAML.load_file(WarVOX::Conf)
       +    return nil if not info
       +    return nil if not info['blacklist']
       +    File.expand_path(info['blacklist'].gsub('%BASE%', WarVOX::Base))
       +  end
       +
       +  def self.blacklist_load
       +    path = blacklist_path
       +    return if not path
       +    data = File.read(path, File.size(path))
       +    sigs = []
       +
       +    File.open(path, 'r') do |fd|
       +      lno = 0
       +      fd.each_line do |line|
       +        lno += 1
       +        next if line =~ /^#/
       +        next if line =~ /^\s+$/
       +        line.strip!
       +        sigs << [lno, line]
       +      end
       +      sigs
       +    end
       +
       +  end
       +
       +  def self.signatures_path
       +    info = YAML.load_file(WarVOX::Conf)
       +    return nil if not info
       +    return nil if not info['signatures']
       +    File.expand_path(info['signatures'].gsub('%BASE%', WarVOX::Base))
       +  end
       +
       +  def self.classifiers_path
       +    info = YAML.load_file(WarVOX::Conf)
       +    return nil if not info
       +    return nil if not info['classifiers']
       +    File.expand_path(info['classifiers'].gsub('%BASE%', WarVOX::Base))
       +  end
       +
       +  def self.log_file
       +    STDOUT
       +  end
       +
       +  def self.log_level
       +    Logger::DEBUG
       +  end
       +
       +  def self.classifiers_load
       +    path = classifiers_path
       +    sigs = []
       +    return sigs if not path
       +
       +    Dir.new(path).entries.sort{ |a,b|
       +      a.to_i <=> b.to_i
       +    }.map{ |ent|
       +      File.join(path, ent)
       +    }.each do |ent|
       +      sigs << ent if File.file?(ent)
       +    end
       +
       +    sigs
       +  end
       +
       +  # This method searches the PATH environment variable for
       +  # a fully qualified path to the supplied file name.
       +  # Stolen from Rex
       +  def self.find_full_path(file_name)
       +
       +    # Return absolute paths unmodified
       +    if(file_name[0,1] == ::File::SEPARATOR)
       +      return file_name
       +    end
       +
       +    path = ENV['PATH']
       +    if (path)
       +      path.split(::File::PATH_SEPARATOR).each { |base|
       +        begin
       +          path = base + ::File::SEPARATOR + file_name
       +          if (::File::Stat.new(path))
       +            return path
       +          end
       +        rescue
       +        end
       +      }
       +    end
       +    return nil
       +  end
       +
       +  # This method prevents two installations of WarVOX from using the same
       +  # rails session key. The first time this method is called, it generates
       +  # a new key and stores it in the rails directory, afterwards this key
       +  # will be used every time.
       +  def self.load_session_key
       +    kfile = File.join(WarVOX::Base, 'config', 'session.key')
       +    if(not File.exists?(kfile))
       +      # XXX: assume /dev/urandom exists
       +      kdata = File.read('/dev/urandom', 64).unpack("H*")[0]
       +
       +      # Create the new session key file
       +      fd = File.new(kfile, 'w')
       +
       +      # Make this file mode 0600
       +      File.chmod(0600, kfile)
       +
       +      # Write it and close
       +      fd.write(kdata)
       +      fd.close
       +      return kdata
       +    end
       +    File.read(kfile)
       +  end
        
        
        end
 (DIR) diff --git a/lib/warvox/jobs.rb b/lib/warvox/jobs.rb
       @@ -1,75 +1,75 @@
        module WarVOX
        class JobQueue
       -        attr_accessor :active_job, :active_thread, :queue, :queue_thread
       +  attr_accessor :active_job, :active_thread, :queue, :queue_thread
        
       -        require "thread"
       +  require "thread"
        
       -        def initialize
       -                @mutex = ::Mutex.new
       -                @queue = []
       -                @queue_thread = Thread.new{ manage_queue }
       +  def initialize
       +    @mutex = ::Mutex.new
       +    @queue = []
       +    @queue_thread = Thread.new{ manage_queue }
        
       -                super
       -        end
       +    super
       +  end
        
       -        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 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(klass, job_id)
       -                begin
       -                return false if scheduled?(klass, job_id)
       -                @queue.push(klass.new(job_id))
       -                rescue ::Exception => e
       -                        $stderr.puts "ERROR!!!!!: #{e} #{e.backtrace}"
       -                        false
       -                end
       -        end
       +  def schedule(klass, job_id)
       +    begin
       +    return false if scheduled?(klass, job_id)
       +    @queue.push(klass.new(job_id))
       +    rescue ::Exception => e
       +      $stderr.puts "ERROR!!!!!: #{e} #{e.backtrace}"
       +      false
       +    end
       +  end
        
       -        def stop(job_id)
       -                @mutex.synchronize do
       -                        [@active_job, *(@queue)].each do |c|
       -                                next if not c
       -                                if c.name == job_id
       -                                        # Actively running
       -                                        if @active_job == c
       +  def stop(job_id)
       +    @mutex.synchronize do
       +      [@active_job, *(@queue)].each do |c|
       +        next if not c
       +        if c.name == job_id
       +          # Actively running
       +          if @active_job == c
        
       -                                        else
       +          else
        
       -                                        end
       -                                end
       -                        end
       -                end
       -        end
       +          end
       +        end
       +      end
       +    end
       +  end
        
       -        def manage_queue
       -                begin
       -                while(true)
       -                        @mutex.synchronize do
       -                                if(@active_job and @active_job.status == 'completed')
       -                                        @active_job    = nil
       -                                        @active_thread = nil
       -                                end
       +  def manage_queue
       +    begin
       +    while(true)
       +      @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
       +        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)
       -                end
       -                rescue ::Exception
       -                        $stderr.puts "QUEUE MANAGER:#{$!.class} #{$!}"
       -                        $stderr.flush
       -                end
       -        end
       +      Kernel.select(nil, nil, nil, 1)
       +    end
       +    rescue ::Exception
       +      $stderr.puts "QUEUE MANAGER:#{$!.class} #{$!}"
       +      $stderr.flush
       +    end
       +  end
        
        end
        end
 (DIR) diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
       @@ -2,420 +2,420 @@ module WarVOX
        module Jobs
        class Analysis < Base
        
       -        require 'fileutils'
       -        require 'tempfile'
       -        require 'open3'
       -
       -        # This is required by the verify_install.rb script, so dont error
       -        # out if the gem is not yet available
       -        begin
       -                require 'kissfft'
       -        rescue ::LoadError
       -        end
       -
       -        class Classifier
       -
       -                class Completed < RuntimeError
       -                end
       -
       -                attr_accessor :line_type
       -                attr_accessor :signatures
       -                attr_accessor :data
       -
       -                def initialize
       -                        @signatures = []
       -                        @data = {}
       -                end
       -
       -                def proc(str)
       -                        begin
       -                                eval(str)
       -                        rescue Completed
       -                        end
       -                end
       -        end
       -
       -        def type
       -                'analysis'
       -        end
       -
       -        def initialize(job_id, conf)
       -                @job_id = job_id
       -                @conf   = conf
       -                @tasks  = []
       -                @calls  = []
       -        end
       -
       -        def stop
       -                @calls = []
       -                @tasks.each do |t|
       -                        t.kill rescue nil
       -                end
       -                @tasks = []
       -        end
       -
       -        def start
       -
       -                @calls = []
       -
       -                query = nil
       -
       -                ::ActiveRecord::Base.connection_pool.with_connection {
       -
       -                begin
       -
       -                job = Job.find(@job_id)
       -                if not job
       -                        raise RuntimeError, "The parent job no longer exists"
       -                end
       -
       -                case @conf[:scope]
       -                when 'calls'
       -                        if @conf[:force]
       -                                query = {:id => @conf[:target_ids], :answered => true, :busy => false}
       -                        else
       -                                query = {:id => @conf[:target_ids], :answered => true, :busy => false, :analysis_started_at => nil}
       -                        end
       -                when 'job'
       -                        if @conf[:force]
       -                                query = {:job_id => @conf[:target_id], :answered => true, :busy => false}
       -                        else
       -                                query = {:job_id => @conf[:target_id], :answered => true, :busy => false, :analysis_started_at => nil}
       -                        end
       -                when 'project'
       -                        if @conf[:force]
       -                                query = {:project_id => @conf[:target_id], :answered => true, :busy => false}
       -                        else
       -                                query = {:project_id => @conf[:target_id], :answered => true, :busy => false, :analysis_started_at => nil}
       -                        end
       -                when 'global'
       -                        if @conf[:force]
       -                                query = {:answered => true, :busy => false}
       -                        else
       -                                query = {:answered => true, :busy => false, :analysis_started_at => nil}
       -                        end
       -                else
       -                        # Bail if we don't have a valid scope
       -                        return
       -                end
       -
       -                # Build a list of call IDs, as find_each() gets confused if the DB changes mid-iteration
       -                calls = Call.where(query).map{|c| c.id }
       -
       -                @total_calls     = calls.length
       -                @completed_calls = 0
       -
       -                max_threads = WarVOX::Config.analysis_threads
       -                last_update = Time.now
       -
       -                while(calls.length > 0)
       -                        if @tasks.length < max_threads
       -                                @tasks << Thread.new(calls.shift, job.id) { |c,j| ::ActiveRecord::Base.connection_pool.with_connection { run_analyze_call(c,j) }}
       -                        else
       -                                clear_stale_tasks
       -
       -                                # Update progress every 10 seconds or so
       -                                if Time.now.to_f - last_update.to_f > 10
       -                                        update_progress((@completed_calls / @total_calls.to_f) * 100)
       -                                        last_update = Time.now
       -                                end
       -
       -                                clear_zombies
       -                        end
       -                end
       -
       -                @tasks.map {|t| t.join }
       -                clear_stale_tasks
       -                clear_zombies
       -
       -                rescue ::Exception => e
       -                        WarVOX::Log.error("Exception: #{e.class} #{e} #{e.backtrace}")
       -                end
       -
       -                }
       -        end
       -
       -        def clear_stale_tasks
       -                @tasks = @tasks.select{ |x| x.status }
       -                IO.select(nil, nil, nil, 0.25)
       -        end
       -
       -        def update_progress(pct)
       -                ::ActiveRecord::Base.connection_pool.with_connection {
       -                        Job.where(id: @job_id).update_all(progress: pct)
       -                }
       -        end
       -
       -        def run_analyze_call(cid, jid)
       -
       -                dr = Call.includes(:job).where(id: cid).first
       -                dr.analysis_started_at = Time.now
       -                dr.analysis_job_id = jid
       -                dr.save!
       -
       -                WarVOX::Log.debug("Worker processing audio for #{dr.number}...")
       -
       -                bin = File.join(WarVOX::Base, 'bin', 'analyze_result.rb')
       -                tmp = Tempfile.new("Analysis")
       -                begin
       -
       -                mr = dr.media
       -                ::File.open(tmp.path, "wb") do |fd|
       -                        fd.write(mr.audio)
       -                end
       -
       -                pfd = IO.popen("nice #{bin} '#{tmp.path}' '#{ dr.number.gsub(/[^0-9a-zA-Z\-\+]+/, '') }'")
       -                out = Marshal.load(pfd.read) rescue nil
       -                pfd.close
       -
       -                return if not out
       -
       -                mf = dr.media_fields
       -                out.each_key do |k|
       -                        if mf.include?(k.to_s)
       -                                mr[k] = out[k]
       -                        else
       -                                dr[k] = out[k]
       -                        end
       -                end
       -
       -                dr.analysis_completed_at = Time.now
       -
       -                rescue ::Interrupt
       -                ensure
       -                        tmp.close
       -                        tmp.unlink
       -                end
       -
       -                mr.save
       -                dr.save
       -
       -                @completed_calls += 1
       -        end
       -
       -        # Takes the raw file path as an argument, returns a hash
       -        def self.analyze_call(input, num=nil)
       -
       -                return if not input
       -                return if not File.exist?(input)
       -
       -                bname   = File.expand_path(File.dirname(input))
       -                num   ||= File.basename(input)
       -                res     = {}
       -
       -                #
       -                # Create the signature database
       -                #
       -                raw  = WarVOX::Audio::Raw.from_file(input)
       -                fft  = KissFFT.fftr(8192, 8000, 1, raw.samples) || []
       -
       -                freq = raw.to_freq_sig_arr()
       -
       -                # Save the signature data
       -                res[:fprint] = freq
       -
       -                #
       -                # Create a raw decompressed file
       -                #
       -
       -                # Decompress the audio file
       -                rawfile = Tempfile.new("rawfile")
       -                datfile = Tempfile.new("datfile")
       -
       -                # Data files for audio processing and signal graph
       -                cnt = 0
       -                rawfile.write(raw.samples.pack('v*'))
       -                datfile.write(raw.samples.map{|val| cnt +=1; "#{cnt/8000.0} #{val}"}.join("\n"))
       -                rawfile.flush
       -                datfile.flush
       -
       -                # Data files for spectrum plotting
       -                frefile = Tempfile.new("frefile")
       -
       -                # Calculate the peak frequencies for the sample
       -                maxf = 0
       -                maxp = 0
       -                tones = {}
       -                fft.each do |x|
       -                        rank = x.sort{|a,b| a[1].to_i <=> b[1].to_i }.reverse
       -                        rank[0..10].each do |t|
       -                                f = t[0].round
       -                                p = t[1].round
       -                                next if f == 0
       -                                next if p < 1
       -                                tones[ f ] ||= []
       -                                tones[ f ] << t
       -                                if(t[1] > maxp)
       -                                        maxf = t[0]
       -                                        maxp = t[1]
       -                                end
       -                        end
       -                end
       -
       -                # Save the peak frequency
       -                res[:peak_freq] = maxf
       -
       -                # Calculate average frequency and peaks over time
       -                avg = {}
       -                pks = []
       -                pkz = []
       -                fft.each do |slot|
       -                        pks << slot.sort{|a,b| a[1] <=> b[1] }.reverse[0]
       -                        pkz << slot.sort{|a,b| a[1] <=> b[1] }.reverse[0..9]
       -                        slot.each do |f|
       -                                avg[ f[0] ] ||= 0
       -                                avg[ f[0] ] +=  f[1]
       -                        end
       -                end
       -
       -                # Save the peak frequencies over time
       -                res[:peak_freq_data] = pks.map{|f| "#{f[0]}-#{f[1]}" }.join(" ")
       -
       -                # Generate the frequency file
       -                avg.keys.sort.each do |k|
       -                        avg[k] = avg[k] / fft.length
       -                        frefile.write("#{k} #{avg[k]}\n")
       -                end
       -                frefile.flush
       -
       -                # Count significant frequencies across the sample
       -                fcnt = {}
       -                0.step(4000, 5) {|f| fcnt[f] = 0 }
       -                pkz.each do |fb|
       -                        fb.each do |f|
       -                                fdx = ((f[0] / 5.0).round * 5.0).to_i
       -                                fcnt[fdx]  += 0.1
       -                        end
       -                end
       -
       -                #
       -                # Classifier processing
       -                #
       -
       -                sproc = Classifier.new
       -                sproc.data =
       -                {
       -                        :raw  => raw,
       -                        :freq => freq,
       -                        :fcnt => fcnt,
       -                        :fft  => fft,
       -                        :pks  => pks,
       -                        :pkz  => pkz,
       -                        :maxf => maxf,
       -                        :maxp => maxp
       -                }
       -
       -                WarVOX::Config.classifiers_load.each do |sigfile|
       -                        begin
       -                                str = File.read(sigfile, File.size(sigfile))
       -                                sproc.proc(str)
       -                        rescue ::Exception => e
       -                                $stderr.puts "DEBUG: Caught exception in #{sigfile}: #{e} #{e.backtrace}"
       -                        end
       -                        break if sproc.line_type
       -                end
       -
       -                # Save the guessed line type
       -                res[:line_type] = sproc.line_type
       -
       -                png_big       = Tempfile.new("big")
       -                png_big_dots  = Tempfile.new("bigdots")
       -                png_big_freq  = Tempfile.new("bigfreq")
       -                png_sig       = Tempfile.new("signal")
       -                png_sig_freq  = Tempfile.new("sigfreq")
       -
       -                # Plot samples to a graph
       -                plotter = Tempfile.new("gnuplot")
       -
       -
       -                plotter.puts("set autoscale")
       -                plotter.puts("set yrange [-15000:15000]")
       -                plotter.puts("set ylabel \"Signal\"")
       -                plotter.puts("set xlabel \"Seconds\"")
       -                plotter.puts("set terminal png medium size 640,480 transparent")
       -                plotter.puts("set output \"#{png_big.path}\"")
       -                plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with lines")
       -                plotter.puts("set output \"#{png_big_dots.path}\"")
       -                plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with dots")
       -
       -
       -                plotter.puts("unset yrange")
       -                plotter.puts("set autoscale")
       -                plotter.puts("set xrange [0:4000]")
       -                plotter.puts("set terminal png medium size 640,480 transparent")
       -                plotter.puts("set ylabel \"Power\"")
       -                plotter.puts("set xlabel \"Frequency\"")
       -                plotter.puts("set output \"#{png_big_freq.path}\"")
       -                plotter.puts("plot \"#{frefile.path}\" using 1:2 title \"#{num} - Peak #{maxf.round}hz\" with lines")
       -
       -
       -                plotter.puts("unset xrange")
       -                plotter.puts("set autoscale")
       -                plotter.puts("set yrange [-15000:15000]")
       -                plotter.puts("unset border")
       -                plotter.puts("unset xtics")
       -                plotter.puts("unset ytics")
       -                plotter.puts("set ylabel \"\"")
       -                plotter.puts("set xlabel \"\"")
       -                plotter.puts("set terminal png small size 80,60 transparent")
       -                plotter.puts("set format x ''")
       -                plotter.puts("set format y ''")
       -                plotter.puts("set output \"#{png_sig.path}\"")
       -                plotter.puts("plot \"#{datfile.path}\" using 1:2 notitle with lines")
       -
       -                plotter.puts("unset yrange")
       -                plotter.puts("set autoscale")
       -                plotter.puts("set xrange [0:4000]")
       -                plotter.puts("unset border")
       -                plotter.puts("unset xtics")
       -                plotter.puts("unset ytics")
       -                plotter.puts("set ylabel \"\"")
       -                plotter.puts("set xlabel \"\"")
       -                plotter.puts("set terminal png small size 80,60 transparent")
       -                plotter.puts("set format x ''")
       -                plotter.puts("set format y ''")
       -                plotter.puts("set output \"#{png_sig_freq.path}\"")
       -                plotter.puts("plot \"#{frefile.path}\" using 1:2 notitle with lines")
       -                plotter.flush
       -
       -                system("#{WarVOX::Config.tool_path('gnuplot')} #{plotter.path}")
       -                File.unlink(plotter.path)
       -                File.unlink(datfile.path)
       -                File.unlink(frefile.path)
       -                plotter.close
       -                datfile.close
       -                frefile.path
       -
       -                ::File.open(png_big.path, 'rb')      { |fd| res[:png_big]      = fd.read }
       -                ::File.open(png_big_dots.path, 'rb') { |fd| res[:png_big_dots] = fd.read }
       -                ::File.open(png_big_freq.path, 'rb') { |fd| res[:png_big_freq] = fd.read }
       -                ::File.open(png_sig.path, 'rb')      { |fd| res[:png_sig]      = fd.read }
       -                ::File.open(png_sig_freq.path, 'rb') { |fd| res[:png_sig_freq] = fd.read }
       -
       -                [png_big, png_big_dots, png_big_freq, png_sig, png_sig_freq ].map {|x| x.unlink; x.close }
       -
       -                tmp_wav = Tempfile.new("wav")
       -                tmp_mp3 = Tempfile.new("mp3")
       -
       -                # Generate a WAV file from raw linear PCM
       -                ::File.open(tmp_wav.path, "wb") do |fd|
       -                        fd.write(raw.to_wav)
       -                end
       -
       -                # Default samples at 8k, bump it to 32k to get better quality
       -                system("#{WarVOX::Config.tool_path('lame')} -b 32 #{tmp_wav.path} #{tmp_mp3.path} >/dev/null 2>&1")
       -
       -                File.unlink(rawfile.path)
       -                rawfile.close
       -
       -                ::File.open(tmp_mp3.path, "rb") { |fd| res[:mp3] = fd.read }
       -
       -                [tmp_wav, tmp_mp3].map {|x| x.unlink; x.close }
       -
       -                clear_zombies()
       -
       -                res
       -        end
       +  require 'fileutils'
       +  require 'tempfile'
       +  require 'open3'
       +
       +  # This is required by the verify_install.rb script, so dont error
       +  # out if the gem is not yet available
       +  begin
       +    require 'kissfft'
       +  rescue ::LoadError
       +  end
       +
       +  class Classifier
       +
       +    class Completed < RuntimeError
       +    end
       +
       +    attr_accessor :line_type
       +    attr_accessor :signatures
       +    attr_accessor :data
       +
       +    def initialize
       +      @signatures = []
       +      @data = {}
       +    end
       +
       +    def proc(str)
       +      begin
       +        eval(str)
       +      rescue Completed
       +      end
       +    end
       +  end
       +
       +  def type
       +    'analysis'
       +  end
       +
       +  def initialize(job_id, conf)
       +    @job_id = job_id
       +    @conf   = conf
       +    @tasks  = []
       +    @calls  = []
       +  end
       +
       +  def stop
       +    @calls = []
       +    @tasks.each do |t|
       +      t.kill rescue nil
       +    end
       +    @tasks = []
       +  end
       +
       +  def start
       +
       +    @calls = []
       +
       +    query = nil
       +
       +    ::ActiveRecord::Base.connection_pool.with_connection {
       +
       +    begin
       +
       +    job = Job.find(@job_id)
       +    if not job
       +      raise RuntimeError, "The parent job no longer exists"
       +    end
       +
       +    case @conf[:scope]
       +    when 'calls'
       +      if @conf[:force]
       +        query = {:id => @conf[:target_ids], :answered => true, :busy => false}
       +      else
       +        query = {:id => @conf[:target_ids], :answered => true, :busy => false, :analysis_started_at => nil}
       +      end
       +    when 'job'
       +      if @conf[:force]
       +        query = {:job_id => @conf[:target_id], :answered => true, :busy => false}
       +      else
       +        query = {:job_id => @conf[:target_id], :answered => true, :busy => false, :analysis_started_at => nil}
       +      end
       +    when 'project'
       +      if @conf[:force]
       +        query = {:project_id => @conf[:target_id], :answered => true, :busy => false}
       +      else
       +        query = {:project_id => @conf[:target_id], :answered => true, :busy => false, :analysis_started_at => nil}
       +      end
       +    when 'global'
       +      if @conf[:force]
       +        query = {:answered => true, :busy => false}
       +      else
       +        query = {:answered => true, :busy => false, :analysis_started_at => nil}
       +      end
       +    else
       +      # Bail if we don't have a valid scope
       +      return
       +    end
       +
       +    # Build a list of call IDs, as find_each() gets confused if the DB changes mid-iteration
       +    calls = Call.where(query).map{|c| c.id }
       +
       +    @total_calls     = calls.length
       +    @completed_calls = 0
       +
       +    max_threads = WarVOX::Config.analysis_threads
       +    last_update = Time.now
       +
       +    while(calls.length > 0)
       +      if @tasks.length < max_threads
       +        @tasks << Thread.new(calls.shift, job.id) { |c,j| ::ActiveRecord::Base.connection_pool.with_connection { run_analyze_call(c,j) }}
       +      else
       +        clear_stale_tasks
       +
       +        # Update progress every 10 seconds or so
       +        if Time.now.to_f - last_update.to_f > 10
       +          update_progress((@completed_calls / @total_calls.to_f) * 100)
       +          last_update = Time.now
       +        end
       +
       +        clear_zombies
       +      end
       +    end
       +
       +    @tasks.map {|t| t.join }
       +    clear_stale_tasks
       +    clear_zombies
       +
       +    rescue ::Exception => e
       +      WarVOX::Log.error("Exception: #{e.class} #{e} #{e.backtrace}")
       +    end
       +
       +    }
       +  end
       +
       +  def clear_stale_tasks
       +    @tasks = @tasks.select{ |x| x.status }
       +    IO.select(nil, nil, nil, 0.25)
       +  end
       +
       +  def update_progress(pct)
       +    ::ActiveRecord::Base.connection_pool.with_connection {
       +      Job.where(id: @job_id).update_all(progress: pct)
       +    }
       +  end
       +
       +  def run_analyze_call(cid, jid)
       +
       +    dr = Call.includes(:job).where(id: cid).first
       +    dr.analysis_started_at = Time.now
       +    dr.analysis_job_id = jid
       +    dr.save!
       +
       +    WarVOX::Log.debug("Worker processing audio for #{dr.number}...")
       +
       +    bin = File.join(WarVOX::Base, 'bin', 'analyze_result.rb')
       +    tmp = Tempfile.new("Analysis")
       +    begin
       +
       +    mr = dr.media
       +    ::File.open(tmp.path, "wb") do |fd|
       +      fd.write(mr.audio)
       +    end
       +
       +    pfd = IO.popen("nice #{bin} '#{tmp.path}' '#{ dr.number.gsub(/[^0-9a-zA-Z\-\+]+/, '') }'")
       +    out = Marshal.load(pfd.read) rescue nil
       +    pfd.close
       +
       +    return if not out
       +
       +    mf = dr.media_fields
       +    out.each_key do |k|
       +      if mf.include?(k.to_s)
       +        mr[k] = out[k]
       +      else
       +        dr[k] = out[k]
       +      end
       +    end
       +
       +    dr.analysis_completed_at = Time.now
       +
       +    rescue ::Interrupt
       +    ensure
       +      tmp.close
       +      tmp.unlink
       +    end
       +
       +    mr.save
       +    dr.save
       +
       +    @completed_calls += 1
       +  end
       +
       +  # Takes the raw file path as an argument, returns a hash
       +  def self.analyze_call(input, num=nil)
       +
       +    return if not input
       +    return if not File.exist?(input)
       +
       +    bname   = File.expand_path(File.dirname(input))
       +    num   ||= File.basename(input)
       +    res     = {}
       +
       +    #
       +    # Create the signature database
       +    #
       +    raw  = WarVOX::Audio::Raw.from_file(input)
       +    fft  = KissFFT.fftr(8192, 8000, 1, raw.samples) || []
       +
       +    freq = raw.to_freq_sig_arr()
       +
       +    # Save the signature data
       +    res[:fprint] = freq
       +
       +    #
       +    # Create a raw decompressed file
       +    #
       +
       +    # Decompress the audio file
       +    rawfile = Tempfile.new("rawfile")
       +    datfile = Tempfile.new("datfile")
       +
       +    # Data files for audio processing and signal graph
       +    cnt = 0
       +    rawfile.write(raw.samples.pack('v*'))
       +    datfile.write(raw.samples.map{|val| cnt +=1; "#{cnt/8000.0} #{val}"}.join("\n"))
       +    rawfile.flush
       +    datfile.flush
       +
       +    # Data files for spectrum plotting
       +    frefile = Tempfile.new("frefile")
       +
       +    # Calculate the peak frequencies for the sample
       +    maxf = 0
       +    maxp = 0
       +    tones = {}
       +    fft.each do |x|
       +      rank = x.sort{|a,b| a[1].to_i <=> b[1].to_i }.reverse
       +      rank[0..10].each do |t|
       +        f = t[0].round
       +        p = t[1].round
       +        next if f == 0
       +        next if p < 1
       +        tones[ f ] ||= []
       +        tones[ f ] << t
       +        if(t[1] > maxp)
       +          maxf = t[0]
       +          maxp = t[1]
       +        end
       +      end
       +    end
       +
       +    # Save the peak frequency
       +    res[:peak_freq] = maxf
       +
       +    # Calculate average frequency and peaks over time
       +    avg = {}
       +    pks = []
       +    pkz = []
       +    fft.each do |slot|
       +      pks << slot.sort{|a,b| a[1] <=> b[1] }.reverse[0]
       +      pkz << slot.sort{|a,b| a[1] <=> b[1] }.reverse[0..9]
       +      slot.each do |f|
       +        avg[ f[0] ] ||= 0
       +        avg[ f[0] ] +=  f[1]
       +      end
       +    end
       +
       +    # Save the peak frequencies over time
       +    res[:peak_freq_data] = pks.map{|f| "#{f[0]}-#{f[1]}" }.join(" ")
       +
       +    # Generate the frequency file
       +    avg.keys.sort.each do |k|
       +      avg[k] = avg[k] / fft.length
       +      frefile.write("#{k} #{avg[k]}\n")
       +    end
       +    frefile.flush
       +
       +    # Count significant frequencies across the sample
       +    fcnt = {}
       +    0.step(4000, 5) {|f| fcnt[f] = 0 }
       +    pkz.each do |fb|
       +      fb.each do |f|
       +        fdx = ((f[0] / 5.0).round * 5.0).to_i
       +        fcnt[fdx]  += 0.1
       +      end
       +    end
       +
       +    #
       +    # Classifier processing
       +    #
       +
       +    sproc = Classifier.new
       +    sproc.data =
       +    {
       +      :raw  => raw,
       +      :freq => freq,
       +      :fcnt => fcnt,
       +      :fft  => fft,
       +      :pks  => pks,
       +      :pkz  => pkz,
       +      :maxf => maxf,
       +      :maxp => maxp
       +    }
       +
       +    WarVOX::Config.classifiers_load.each do |sigfile|
       +      begin
       +        str = File.read(sigfile, File.size(sigfile))
       +        sproc.proc(str)
       +      rescue ::Exception => e
       +        $stderr.puts "DEBUG: Caught exception in #{sigfile}: #{e} #{e.backtrace}"
       +      end
       +      break if sproc.line_type
       +    end
       +
       +    # Save the guessed line type
       +    res[:line_type] = sproc.line_type
       +
       +    png_big       = Tempfile.new("big")
       +    png_big_dots  = Tempfile.new("bigdots")
       +    png_big_freq  = Tempfile.new("bigfreq")
       +    png_sig       = Tempfile.new("signal")
       +    png_sig_freq  = Tempfile.new("sigfreq")
       +
       +    # Plot samples to a graph
       +    plotter = Tempfile.new("gnuplot")
       +
       +
       +    plotter.puts("set autoscale")
       +    plotter.puts("set yrange [-15000:15000]")
       +    plotter.puts("set ylabel \"Signal\"")
       +    plotter.puts("set xlabel \"Seconds\"")
       +    plotter.puts("set terminal png medium size 640,480 transparent")
       +    plotter.puts("set output \"#{png_big.path}\"")
       +    plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with lines")
       +    plotter.puts("set output \"#{png_big_dots.path}\"")
       +    plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with dots")
       +
       +
       +    plotter.puts("unset yrange")
       +    plotter.puts("set autoscale")
       +    plotter.puts("set xrange [0:4000]")
       +    plotter.puts("set terminal png medium size 640,480 transparent")
       +    plotter.puts("set ylabel \"Power\"")
       +    plotter.puts("set xlabel \"Frequency\"")
       +    plotter.puts("set output \"#{png_big_freq.path}\"")
       +    plotter.puts("plot \"#{frefile.path}\" using 1:2 title \"#{num} - Peak #{maxf.round}hz\" with lines")
       +
       +
       +    plotter.puts("unset xrange")
       +    plotter.puts("set autoscale")
       +    plotter.puts("set yrange [-15000:15000]")
       +    plotter.puts("unset border")
       +    plotter.puts("unset xtics")
       +    plotter.puts("unset ytics")
       +    plotter.puts("set ylabel \"\"")
       +    plotter.puts("set xlabel \"\"")
       +    plotter.puts("set terminal png small size 80,60 transparent")
       +    plotter.puts("set format x ''")
       +    plotter.puts("set format y ''")
       +    plotter.puts("set output \"#{png_sig.path}\"")
       +    plotter.puts("plot \"#{datfile.path}\" using 1:2 notitle with lines")
       +
       +    plotter.puts("unset yrange")
       +    plotter.puts("set autoscale")
       +    plotter.puts("set xrange [0:4000]")
       +    plotter.puts("unset border")
       +    plotter.puts("unset xtics")
       +    plotter.puts("unset ytics")
       +    plotter.puts("set ylabel \"\"")
       +    plotter.puts("set xlabel \"\"")
       +    plotter.puts("set terminal png small size 80,60 transparent")
       +    plotter.puts("set format x ''")
       +    plotter.puts("set format y ''")
       +    plotter.puts("set output \"#{png_sig_freq.path}\"")
       +    plotter.puts("plot \"#{frefile.path}\" using 1:2 notitle with lines")
       +    plotter.flush
       +
       +    system("#{WarVOX::Config.tool_path('gnuplot')} #{plotter.path}")
       +    File.unlink(plotter.path)
       +    File.unlink(datfile.path)
       +    File.unlink(frefile.path)
       +    plotter.close
       +    datfile.close
       +    frefile.path
       +
       +    ::File.open(png_big.path, 'rb')      { |fd| res[:png_big]      = fd.read }
       +    ::File.open(png_big_dots.path, 'rb') { |fd| res[:png_big_dots] = fd.read }
       +    ::File.open(png_big_freq.path, 'rb') { |fd| res[:png_big_freq] = fd.read }
       +    ::File.open(png_sig.path, 'rb')      { |fd| res[:png_sig]      = fd.read }
       +    ::File.open(png_sig_freq.path, 'rb') { |fd| res[:png_sig_freq] = fd.read }
       +
       +    [png_big, png_big_dots, png_big_freq, png_sig, png_sig_freq ].map {|x| x.unlink; x.close }
       +
       +    tmp_wav = Tempfile.new("wav")
       +    tmp_mp3 = Tempfile.new("mp3")
       +
       +    # Generate a WAV file from raw linear PCM
       +    ::File.open(tmp_wav.path, "wb") do |fd|
       +      fd.write(raw.to_wav)
       +    end
       +
       +    # Default samples at 8k, bump it to 32k to get better quality
       +    system("#{WarVOX::Config.tool_path('lame')} -b 32 #{tmp_wav.path} #{tmp_mp3.path} >/dev/null 2>&1")
       +
       +    File.unlink(rawfile.path)
       +    rawfile.close
       +
       +    ::File.open(tmp_mp3.path, "rb") { |fd| res[:mp3] = fd.read }
       +
       +    [tmp_wav, tmp_mp3].map {|x| x.unlink; x.close }
       +
       +    clear_zombies()
       +
       +    res
       +  end
        end
        
        
 (DIR) diff --git a/lib/warvox/jobs/base.rb b/lib/warvox/jobs/base.rb
       @@ -1,38 +1,38 @@
        module WarVOX
        module Jobs
        class Base
       -        attr_accessor :name, :status
       +  attr_accessor :name, :status
        
       -        def type
       -                'base'
       -        end
       +  def type
       +    'base'
       +  end
        
       -        def stop
       -                @status = 'active'
       -        end
       +  def stop
       +    @status = 'active'
       +  end
        
       -        def start
       -                @status = 'completed'
       -        end
       +  def start
       +    @status = 'completed'
       +  end
        
       -        def db_save(obj)
       -                max_tries = 100
       -                cur_tries = 0
       -                obj.save!
       -        end
       +  def db_save(obj)
       +    max_tries = 100
       +    cur_tries = 0
       +    obj.save!
       +  end
        
       -        def clear_zombies
       -                self.class.clear_zombies
       -        end
       +  def clear_zombies
       +    self.class.clear_zombies
       +  end
        
       -        def self.clear_zombies
       -                begin
       -                        # Clear zombies just in case...
       -                        while(Process.waitpid(-1, Process::WNOHANG))
       -                        end
       -                rescue ::Exception
       -                end
       -        end
       +  def self.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
       @@ -2,225 +2,225 @@ module WarVOX
        module Jobs
        class Dialer < Base
        
       -        require 'fileutils'
       -
       -        def type
       -                'dialer'
       -        end
       -
       -        def initialize(job_id, conf)
       -                @job_id  = job_id
       -                @conf    = conf
       -                @range   = @conf[:range]
       -                @seconds = @conf[:seconds]
       -                @lines   = @conf[:lines]
       -                @nums    = shuffle_a(WarVOX::Phone.crack_mask(@range))
       -
       -                @tasks   = []
       -                @provs   = get_providers
       -
       -                # CallerID modes (SELF or a mask)
       -                @cid_self = @conf[:cid_mask] == 'SELF'
       -                if(not @cid_self)
       -                        @cid_range = WarVOX::Phone.crack_mask(@conf[:cid_mask])
       -                end
       -        end
       -
       -        #
       -        # Performs a Fisher-Yates shuffle on an array
       -        #
       -        def shuffle_a(arr)
       -                len = arr.length
       -                max = len - 1
       -                cyc = [* (0..max) ]
       -                for d in cyc
       -                        e = rand(d+1)
       -                        next if e == d
       -                        f = arr[d];
       -                        g = arr[e];
       -                        arr[d] = g;
       -                        arr[e] = f;
       -                end
       -                return arr
       -        end
       -
       -        def get_providers
       -                res = []
       -
       -                ::ActiveRecord::Base.connection_pool.with_connection {
       -                        ::Provider.where(:enabled => true).all.each do |prov|
       -                                info = {
       -                                        :name  => prov.name,
       -                                        :id    => prov.id,
       -                                        :port  => prov.port,
       -                                        :host  => prov.host,
       -                                        :user  => prov.user,
       -                                        :pass  => prov.pass,
       -                                        :lines => prov.lines
       -                                }
       -                                1.upto(prov.lines) {|i| res.push(info) }
       -                        end
       -                }
       -
       -                shuffle_a(res)
       -        end
       -
       -
       -        def stop
       -                @nums = []
       -                @tasks.each do |t|
       -                        t.kill rescue nil
       -                end
       -                @tasks = []
       -        end
       -
       -        def start
       -                # Scrub all numbers matching the blacklist
       -                list = WarVOX::Config.blacklist_load
       -                list.each do |b|
       -                        lno,reg = b
       -                        @nums.each do |num|
       -                                if(num =~ /#{reg}/)
       -                                        $stderr.puts "DEBUG: Skipping #{num} due to blacklist (line: #{lno})"
       -                                        @nums.delete(num)
       -                                end
       -                        end
       -                end
       -
       -                last_update = Time.now
       -                @nums_total = @nums.length
       -
       -                max_tasks = [@provs.length, @lines].min
       -
       -                while(@nums.length > 0)
       -                        while( @tasks.length < max_tasks ) do
       -                                tnum  = @nums.shift
       -                                break unless tnum
       -
       -                                tprov = allocate_provider
       -
       -                                @tasks << Thread.new(tnum,tprov) do |num,prov|
       -
       -                                        out_fd = Tempfile.new("rawfile")
       -                                        out    = out_fd.path
       -
       -                                        begin
       -                                        # Execute and read the output
       -                                        busy = 0
       -                                        ring = 0
       -                                        fail = 1
       -                                        byte = 0
       -                                        path = ''
       -                                        cid  = @cid_self ? num : @cid_range[ rand(@cid_range.length) ]
       -
       -                                        IO.popen(
       -                                                [
       -                                                        WarVOX::Config.tool_path('iaxrecord'),
       -                                                        "-s",
       -                                                        prov[:host],
       -                                                        "-u",
       -                                                        prov[:user],
       -                                                        "-p",
       -                                                        prov[:pass],
       -                                                        "-c",
       -                                                        cid,
       -                                                        "-o",
       -                                                        out,
       -                                                        "-n",
       -                                                        num,
       -                                                        "-l",
       -                                                        @seconds
       -                                                ].map{|i|
       -                                                        "'" + i.to_s.gsub("'",'') +"'"
       -                                        }.join(" ")).each_line do |line|
       -                                                $stderr.puts "DEBUG: #{line.strip}"
       -                                                if(line =~ /^COMPLETED/)
       -                                                        line.split(/\s+/).map{|b| b.split('=', 2) }.each do |info|
       -                                                                busy = info[1].to_i if info[0] == 'BUSY'
       -                                                                fail = info[1].to_i if info[0] == 'FAIL'
       -                                                                ring = info[1].to_i if info[0] == 'RINGTIME'
       -                                                                byte = info[1].to_i if info[0] == 'BYTES'
       -                                                                path = info[1]      if info[0] == 'FILE'
       -                                                        end
       -                                                end
       -                                        end
       -
       -                                        ::ActiveRecord::Base.connection_pool.with_connection do
       -                                                job = Job.find(@job_id)
       -                                                if not job
       -                                                        raise RuntimeError, "The parent job is not available"
       -                                                end
       -
       -                                                res = ::Call.new
       -                                                res.number        = num
       -                                                res.job_id        = job.id
       -                                                res.project_id    = job.project_id
       -                                                res.provider_id   = prov[:id]
       -                                                res.answered      = (fail == 0) ? true : false
       -                                                res.busy          = (busy == 1) ? true : false
       -                                                res.audio_length = (byte / 16000)  # 8khz @ 16-bit
       -                                                res.ring_length  = ring
       -                                                res.caller_id     = cid
       -
       -                                                res.save
       -
       -                                                if(File.exists?(out))
       -                                                        File.open(out, "rb") do |fd|
       -                                                                med = res.media
       -                                                                med.audio = fd.read(fd.stat.size)
       -                                                                med.save
       -                                                        end
       -                                                end
       -
       -                                                out_fd.close
       -                                                ::FileUtils.rm_f(out)
       -                                        end
       -
       -                                        rescue ::Exception => e
       -                                                $stderr.puts "ERROR: #{e.class} #{e} #{e.backtrace} #{num} #{prov.inspect}"
       -                                        end
       -                                end
       -
       -                                # END NEW THREAD
       -                        end
       -                        # END SPAWN THREADS
       -
       -                        clear_stale_tasks
       -
       -                        # Update progress every 10 seconds or so
       -                        if Time.now.to_f - last_update.to_f > 10
       -                                update_progress(((@nums_total - @nums.length) / @nums_total.to_f) * 100)
       -                                last_update = Time.now.to_f
       -                        end
       -
       -                        clear_zombies()
       -                end
       -
       -                while @tasks.length > 0
       -                        clear_stale_tasks
       -                end
       -
       -                # ALL DONE
       -        end
       -
       -        def clear_stale_tasks
       -                # Remove dead threads from the task list
       -                @tasks = @tasks.select{ |x| x.status }
       -                IO.select(nil, nil, nil, 0.25)
       -        end
       -
       -        def update_progress(pct)
       -                ::ActiveRecord::Base.connection_pool.with_connection {
       -                        Job.where(id: @job_id).update_all(progress: pct)
       -                }
       -        end
       -
       -        def allocate_provider
       -                @prov_idx ||= 0
       -                prov = @provs[ @prov_idx % @provs.length ]
       -                @prov_idx  += 1
       -                prov
       -        end
       +  require 'fileutils'
       +
       +  def type
       +    'dialer'
       +  end
       +
       +  def initialize(job_id, conf)
       +    @job_id  = job_id
       +    @conf    = conf
       +    @range   = @conf[:range]
       +    @seconds = @conf[:seconds]
       +    @lines   = @conf[:lines]
       +    @nums    = shuffle_a(WarVOX::Phone.crack_mask(@range))
       +
       +    @tasks   = []
       +    @provs   = get_providers
       +
       +    # CallerID modes (SELF or a mask)
       +    @cid_self = @conf[:cid_mask] == 'SELF'
       +    if(not @cid_self)
       +      @cid_range = WarVOX::Phone.crack_mask(@conf[:cid_mask])
       +    end
       +  end
       +
       +  #
       +  # Performs a Fisher-Yates shuffle on an array
       +  #
       +  def shuffle_a(arr)
       +    len = arr.length
       +    max = len - 1
       +    cyc = [* (0..max) ]
       +    for d in cyc
       +      e = rand(d+1)
       +      next if e == d
       +      f = arr[d];
       +      g = arr[e];
       +      arr[d] = g;
       +      arr[e] = f;
       +    end
       +    return arr
       +  end
       +
       +  def get_providers
       +    res = []
       +
       +    ::ActiveRecord::Base.connection_pool.with_connection {
       +      ::Provider.where(:enabled => true).all.each do |prov|
       +        info = {
       +          :name  => prov.name,
       +          :id    => prov.id,
       +          :port  => prov.port,
       +          :host  => prov.host,
       +          :user  => prov.user,
       +          :pass  => prov.pass,
       +          :lines => prov.lines
       +        }
       +        1.upto(prov.lines) {|i| res.push(info) }
       +      end
       +    }
       +
       +    shuffle_a(res)
       +  end
       +
       +
       +  def stop
       +    @nums = []
       +    @tasks.each do |t|
       +      t.kill rescue nil
       +    end
       +    @tasks = []
       +  end
       +
       +  def start
       +    # Scrub all numbers matching the blacklist
       +    list = WarVOX::Config.blacklist_load
       +    list.each do |b|
       +      lno,reg = b
       +      @nums.each do |num|
       +        if(num =~ /#{reg}/)
       +          $stderr.puts "DEBUG: Skipping #{num} due to blacklist (line: #{lno})"
       +          @nums.delete(num)
       +        end
       +      end
       +    end
       +
       +    last_update = Time.now
       +    @nums_total = @nums.length
       +
       +    max_tasks = [@provs.length, @lines].min
       +
       +    while(@nums.length > 0)
       +      while( @tasks.length < max_tasks ) do
       +        tnum  = @nums.shift
       +        break unless tnum
       +
       +        tprov = allocate_provider
       +
       +        @tasks << Thread.new(tnum,tprov) do |num,prov|
       +
       +          out_fd = Tempfile.new("rawfile")
       +          out    = out_fd.path
       +
       +          begin
       +          # Execute and read the output
       +          busy = 0
       +          ring = 0
       +          fail = 1
       +          byte = 0
       +          path = ''
       +          cid  = @cid_self ? num : @cid_range[ rand(@cid_range.length) ]
       +
       +          IO.popen(
       +            [
       +              WarVOX::Config.tool_path('iaxrecord'),
       +              "-s",
       +              prov[:host],
       +              "-u",
       +              prov[:user],
       +              "-p",
       +              prov[:pass],
       +              "-c",
       +              cid,
       +              "-o",
       +              out,
       +              "-n",
       +              num,
       +              "-l",
       +              @seconds
       +            ].map{|i|
       +              "'" + i.to_s.gsub("'",'') +"'"
       +          }.join(" ")).each_line do |line|
       +            $stderr.puts "DEBUG: #{line.strip}"
       +            if(line =~ /^COMPLETED/)
       +              line.split(/\s+/).map{|b| b.split('=', 2) }.each do |info|
       +                busy = info[1].to_i if info[0] == 'BUSY'
       +                fail = info[1].to_i if info[0] == 'FAIL'
       +                ring = info[1].to_i if info[0] == 'RINGTIME'
       +                byte = info[1].to_i if info[0] == 'BYTES'
       +                path = info[1]      if info[0] == 'FILE'
       +              end
       +            end
       +          end
       +
       +          ::ActiveRecord::Base.connection_pool.with_connection do
       +            job = Job.find(@job_id)
       +            if not job
       +              raise RuntimeError, "The parent job is not available"
       +            end
       +
       +            res = ::Call.new
       +            res.number        = num
       +            res.job_id        = job.id
       +            res.project_id    = job.project_id
       +            res.provider_id   = prov[:id]
       +            res.answered      = (fail == 0) ? true : false
       +            res.busy          = (busy == 1) ? true : false
       +            res.audio_length = (byte / 16000)  # 8khz @ 16-bit
       +            res.ring_length  = ring
       +            res.caller_id     = cid
       +
       +            res.save
       +
       +            if(File.exists?(out))
       +              File.open(out, "rb") do |fd|
       +                med = res.media
       +                med.audio = fd.read(fd.stat.size)
       +                med.save
       +              end
       +            end
       +
       +            out_fd.close
       +            ::FileUtils.rm_f(out)
       +          end
       +
       +          rescue ::Exception => e
       +            $stderr.puts "ERROR: #{e.class} #{e} #{e.backtrace} #{num} #{prov.inspect}"
       +          end
       +        end
       +
       +        # END NEW THREAD
       +      end
       +      # END SPAWN THREADS
       +
       +      clear_stale_tasks
       +
       +      # Update progress every 10 seconds or so
       +      if Time.now.to_f - last_update.to_f > 10
       +        update_progress(((@nums_total - @nums.length) / @nums_total.to_f) * 100)
       +        last_update = Time.now.to_f
       +      end
       +
       +      clear_zombies()
       +    end
       +
       +    while @tasks.length > 0
       +      clear_stale_tasks
       +    end
       +
       +    # ALL DONE
       +  end
       +
       +  def clear_stale_tasks
       +    # Remove dead threads from the task list
       +    @tasks = @tasks.select{ |x| x.status }
       +    IO.select(nil, nil, nil, 0.25)
       +  end
       +
       +  def update_progress(pct)
       +    ::ActiveRecord::Base.connection_pool.with_connection {
       +      Job.where(id: @job_id).update_all(progress: pct)
       +    }
       +  end
       +
       +  def allocate_provider
       +    @prov_idx ||= 0
       +    prov = @provs[ @prov_idx % @provs.length ]
       +    @prov_idx  += 1
       +    prov
       +  end
        
        end
        end
 (DIR) diff --git a/lib/warvox/phone.rb b/lib/warvox/phone.rb
       @@ -1,58 +1,58 @@
        module WarVOX
        class Phone
        
       -        # Convert 123456XXXX to an array of expanded numbers
       -        def self.crack_mask(mask)
       -                masks = mask.split(/[\s,]+/) || [ ]
       -                masks.delete(nil)
       -                self.crack_masks(masks)
       -        end
       -        
       -        def self.crack_masks(masks)
       -                res = {}
       -                masks.each do |mask|
       -                        mask = mask.strip
       -                        
       -                        if(mask.index(':'))
       -                                next if mask.index('X')
       +  # Convert 123456XXXX to an array of expanded numbers
       +  def self.crack_mask(mask)
       +    masks = mask.split(/[\s,]+/) || [ ]
       +    masks.delete(nil)
       +    self.crack_masks(masks)
       +  end
       +  
       +  def self.crack_masks(masks)
       +    res = {}
       +    masks.each do |mask|
       +      mask = mask.strip
       +      
       +      if(mask.index(':'))
       +        next if mask.index('X')
        
       -                                # Quick hack to fix leading 0s
       -                                prefix = ""
       -                                if mask =~ /^(0+)/
       -                                        prefix = $1
       +        # Quick hack to fix leading 0s
       +        prefix = ""
       +        if mask =~ /^(0+)/
       +          prefix = $1
                                        end
        
       -                                rbeg,rend = mask.split(':').map{|c| c.gsub(/[^\d]/, '').to_i }
       -                                rbeg.upto(rend) do |n|
       -                                        res[prefix + n.to_s] = {}
       -                                end
       -                                next
       -                        end
       -                        
       -                        incdigits = 0
       -                        mask.each_char do |c|
       -                                incdigits += 1 if c =~ /^[X#]$/i
       -                        end
       -        
       -                        max = (10**incdigits)-1
       -        
       -                        (0..max).each do |num|
       -                                number = mask.dup # copy the mask
       -                                numstr = sprintf("%0#{incdigits}d", num) # stringify our incrementing number
       -                                j = 0 # index for numstr
       -                                for i in 0..number.length-1 do # step through the number (mask)
       -                                        if number[i].chr =~ /^[X#]$/i
       -                                                number[i] = numstr[j] # replaced masked indexes with digits from incrementing number
       -                                                j += 1
       -                                        end
       -                                end
       -                                res[number] = {}
       -                        end
       -        
       -                end
       +        rbeg,rend = mask.split(':').map{|c| c.gsub(/[^\d]/, '').to_i }
       +        rbeg.upto(rend) do |n|
       +          res[prefix + n.to_s] = {}
       +        end
       +        next
       +      end
       +      
       +      incdigits = 0
       +      mask.each_char do |c|
       +        incdigits += 1 if c =~ /^[X#]$/i
       +      end
       +  
       +      max = (10**incdigits)-1
       +  
       +      (0..max).each do |num|
       +        number = mask.dup # copy the mask
       +        numstr = sprintf("%0#{incdigits}d", num) # stringify our incrementing number
       +        j = 0 # index for numstr
       +        for i in 0..number.length-1 do # step through the number (mask)
       +          if number[i].chr =~ /^[X#]$/i
       +            number[i] = numstr[j] # replaced masked indexes with digits from incrementing number
       +            j += 1
       +          end
       +        end
       +        res[number] = {}
       +      end
       +  
       +    end
        
       -                return res.keys.sort
       -        end
       +    return res.keys.sort
       +  end
        
        end
        end
 (DIR) diff --git a/lib/warvox/proto/iax2/client.rb b/lib/warvox/proto/iax2/client.rb
       @@ -162,27 +162,27 @@ class Client
          end
        
          def send_ack(call)
       -    data =        [ IAX_SUBTYPE_ACK ].pack('C')
       +    data =  [ IAX_SUBTYPE_ACK ].pack('C')
            send_data( call, create_pkt( call.scall, call.dcall, call.timestamp, call.oseq, call.iseq, IAX_TYPE_IAX, data ), false )
          end
        
          def send_pong(call, stamp)
       -    data =        [ IAX_SUBTYPE_PONG ].pack('C')
       +    data =  [ IAX_SUBTYPE_PONG ].pack('C')
            send_data( call, create_pkt( call.scall, call.dcall, stamp, call.oseq, call.iseq, IAX_TYPE_IAX, data ) )
          end
        
          def send_lagrp(call, stamp)
       -    data =        [ IAX_SUBTYPE_LAGRP ].pack('C')
       +    data =  [ IAX_SUBTYPE_LAGRP ].pack('C')
            send_data( call, create_pkt( call.scall, call.dcall, stamp, call.oseq, call.iseq, IAX_TYPE_IAX, data ) )
          end
        
          def send_invalid(call)
       -    data =        [ IAX_SUBTYPE_INVAL ].pack('C')
       +    data =  [ IAX_SUBTYPE_INVAL ].pack('C')
            send_data( call, create_pkt( call.scall, call.dcall, call.timestamp, call.oseq, call.iseq, IAX_TYPE_IAX, data ) )
          end
        
          def send_hangup(call)
       -    data =        [ IAX_SUBTYPE_HANGUP ].pack('C')
       +    data =  [ IAX_SUBTYPE_HANGUP ].pack('C')
            send_data( call, create_pkt( call.scall, call.dcall, call.timestamp, call.oseq, call.iseq, IAX_TYPE_IAX, data ) )
          end
        
 (DIR) diff --git a/spec/factories/call_media.rb b/spec/factories/call_media.rb
       @@ -15,9 +15,9 @@
        #
        
        FactoryGirl.define do
       -        factory :call_medium do
       -                call
       -                project
       -        end
       +  factory :call_medium do
       +    call
       +    project
       +  end
        
        end
 (DIR) diff --git a/spec/factories/calls.rb b/spec/factories/calls.rb
       @@ -25,11 +25,11 @@
        #
        
        FactoryGirl.define do
       -        factory :call do
       -                project
       -                job
       -                provider
       -                number { Faker::PhoneNumber.phone_number }
       -        end
       +  factory :call do
       +    project
       +    job
       +    provider
       +    number { Faker::PhoneNumber.phone_number }
       +  end
        
        end
 (DIR) diff --git a/spec/factories/jobs.rb b/spec/factories/jobs.rb
       @@ -19,16 +19,16 @@
        #
        
        FactoryGirl.define do
       -        factory :job do
       -                project
       -                task 'dialer'
       -                args "\x04\b{\t:\nrangeI\"\x0F7632458942\x06:\x06ET:\nlinesi\x0F:\fsecondsi::\rcid_maskI\"\tSELF\x06;\x06T"
       -                status 'submitted'
       -                error nil
       -                range { Faker::PhoneNumber.phone_number }
       -                cid_mask { Faker::PhoneNumber.phone_number }
       -                seconds { Faker::Number.between(1, 299) }
       -                lines { Faker::Number.between(1, 10000) }
       -        end
       +  factory :job do
       +    project
       +    task 'dialer'
       +    args "\x04\b{\t:\nrangeI\"\x0F7632458942\x06:\x06ET:\nlinesi\x0F:\fsecondsi::\rcid_maskI\"\tSELF\x06;\x06T"
       +    status 'submitted'
       +    error nil
       +    range { Faker::PhoneNumber.phone_number }
       +    cid_mask { Faker::PhoneNumber.phone_number }
       +    seconds { Faker::Number.between(1, 299) }
       +    lines { Faker::Number.between(1, 10000) }
       +  end
        
        end
 (DIR) diff --git a/spec/factories/lines.rb b/spec/factories/lines.rb
       @@ -12,9 +12,9 @@
        #
        
        FactoryGirl.define do
       -        factory :line do
       -                project
       -                number { Faker::PhoneNumber.phone_number }
       -        end
       +  factory :line do
       +    project
       +    number { Faker::PhoneNumber.phone_number }
       +  end
        
        end
 (DIR) diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
       @@ -13,9 +13,9 @@
        #
        
        FactoryGirl.define do
       -        factory :project do
       -                name { Faker::Lorem.sentence }
       -                description { Faker::Lorem.sentence }
       -        end
       +  factory :project do
       +    name { Faker::Lorem.sentence }
       +    description { Faker::Lorem.sentence }
       +  end
        
        end
 (DIR) diff --git a/spec/factories/providers.rb b/spec/factories/providers.rb
       @@ -15,14 +15,14 @@
        #
        
        FactoryGirl.define do
       -        factory :provider do
       -                name { Faker::Company.name }
       -                host { Faker::Internet.ip_v4_address }
       -                port { Faker::Number.between(1, 65535) }
       -                user { Faker::Internet.user_name }
       -                pass { Faker::Internet.password(10, 20) }
       -                lines { Faker::Number.between(1, 254) }
       -                enabled true
       -        end
       +  factory :provider do
       +    name { Faker::Company.name }
       +    host { Faker::Internet.ip_v4_address }
       +    port { Faker::Number.between(1, 65535) }
       +    user { Faker::Internet.user_name }
       +    pass { Faker::Internet.password(10, 20) }
       +    lines { Faker::Number.between(1, 254) }
       +    enabled true
       +  end
        
        end
 (DIR) diff --git a/spec/factories/settings.rb b/spec/factories/settings.rb
       @@ -12,8 +12,8 @@
        #
        
        FactoryGirl.define do
       -        factory :setting, :class => 'Settings' do
       -                var "CachedStuff"
       -        end
       +  factory :setting, :class => 'Settings' do
       +    var "CachedStuff"
       +  end
        
        end
 (DIR) diff --git a/spec/factories/signature_fps.rb b/spec/factories/signature_fps.rb
       @@ -1,6 +1,6 @@
        FactoryGirl.define do
       -        factory :signature_fp do
       -                
       -        end
       +  factory :signature_fp do
       +    
       +  end
        
        end
 (DIR) diff --git a/spec/factories/signatures.rb b/spec/factories/signatures.rb
       @@ -14,13 +14,13 @@
        #
        
        FactoryGirl.define do
       -        factory :signature do
       -                name { Faker::Commerce.product_name }
       -                source { Faker::PhoneNumber.cell_phone }
       -                description { Faker::Lorem.sentence }
       -                category { Faker::Lorem.word }
       -                line_type { Faker::Lorem.word }
       -                risk { Faker::Lorem.word }
       -        end
       +  factory :signature do
       +    name { Faker::Commerce.product_name }
       +    source { Faker::PhoneNumber.cell_phone }
       +    description { Faker::Lorem.sentence }
       +    category { Faker::Lorem.word }
       +    line_type { Faker::Lorem.word }
       +    risk { Faker::Lorem.word }
       +  end
        
        end
 (DIR) diff --git a/spec/factories/users.rb b/spec/factories/users.rb
       @@ -24,12 +24,12 @@
        #
        
        FactoryGirl.define do
       -        factory :user do
       -                login { Faker::Internet.user_name }
       -                password 'RandomPass'
       -                password_confirmation 'RandomPass'
       -                enabled true
       -                admin true
       -        end
       +  factory :user do
       +    login { Faker::Internet.user_name }
       +    password 'RandomPass'
       +    password_confirmation 'RandomPass'
       +    enabled true
       +    admin true
       +  end
        
        end
 (DIR) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
       @@ -2,24 +2,24 @@ require 'rails_helper'
        
        RSpec.feature "Projects", type: :feature do
        
       -        before(:each) do
       -                @user = create(:user)
       -                create_user_session(@user)
       -        end
       +  before(:each) do
       +    @user = create(:user)
       +    create_user_session(@user)
       +  end
        
       -        it "list all existing projects" do
       -                project = create(:project)
       -                visit projects_path
       -                expect(page).to have_content "WarVOX Projects"
       -                within "#projects-table" do
       -                        expect(page).to have_content "Name"
       -                        expect(page).to have_content "Description"
       -                        expect(page).to have_content "Jobs"
       -                        expect(page).to have_content "Calls"
       -                        expect(page).to have_content "Analyzed"
       -                        expect(page).to have_content "Created"
       -                        expect(page).to have_content "Actions"
       -                        expect(page).to have_content project.name
       -                end
       -        end
       +  it "list all existing projects" do
       +    project = create(:project)
       +    visit projects_path
       +    expect(page).to have_content "WarVOX Projects"
       +    within "#projects-table" do
       +      expect(page).to have_content "Name"
       +      expect(page).to have_content "Description"
       +      expect(page).to have_content "Jobs"
       +      expect(page).to have_content "Calls"
       +      expect(page).to have_content "Analyzed"
       +      expect(page).to have_content "Created"
       +      expect(page).to have_content "Actions"
       +      expect(page).to have_content project.name
       +    end
       +  end
        end
 (DIR) diff --git a/spec/features/visitor/logins_spec.rb b/spec/features/visitor/logins_spec.rb
       @@ -1,58 +1,58 @@
        require 'rails_helper'
        
        RSpec.feature "Logins", type: :feature do
       -        it "login with valid credentials" do
       -                user = create(:user)
       -                visit login_path
       -                within "#new_user_session" do
       -                        expect(page).to have_content "Username"
       -                        expect(page).to have_content "Password"
       -                        fill_in "user_session_login", with: user.login
       -                        fill_in "user_session_password", with: 'RandomPass'
       -                        click_button "Sign in"
       -                end
       -                within "div.content" do
       -                        expect(page).to have_content "WarVOX Projects"
       -                end
       -        end
       +  it "login with valid credentials" do
       +    user = create(:user)
       +    visit login_path
       +    within "#new_user_session" do
       +      expect(page).to have_content "Username"
       +      expect(page).to have_content "Password"
       +      fill_in "user_session_login", with: user.login
       +      fill_in "user_session_password", with: 'RandomPass'
       +      click_button "Sign in"
       +    end
       +    within "div.content" do
       +      expect(page).to have_content "WarVOX Projects"
       +    end
       +  end
        
       -        it "failed login with invalid password valid username" do
       -                user = create(:user)
       -                visit login_path
       -                within "#new_user_session" do
       -                        fill_in "user_session_login", with: user.login
       -                        fill_in "user_session_password", with: 'WrongPassword'
       -                        click_button "Sign in"
       -                end
       -                expect(page).to have_content "Password is not valid"
       -        end
       +  it "failed login with invalid password valid username" do
       +    user = create(:user)
       +    visit login_path
       +    within "#new_user_session" do
       +      fill_in "user_session_login", with: user.login
       +      fill_in "user_session_password", with: 'WrongPassword'
       +      click_button "Sign in"
       +    end
       +    expect(page).to have_content "Password is not valid"
       +  end
        
       -        it "failed login with invalid username valid password" do
       -                user = create(:user)
       -                visit login_path
       -                within "#new_user_session" do
       -                        fill_in "user_session_login", with: user.login + "Wrong"
       -                        fill_in "user_session_password", with: 'RandomPass'
       -                        click_button "Sign in"
       -                end
       -                expect(page).to have_content "Login is not valid"
       -        end
       +  it "failed login with invalid username valid password" do
       +    user = create(:user)
       +    visit login_path
       +    within "#new_user_session" do
       +      fill_in "user_session_login", with: user.login + "Wrong"
       +      fill_in "user_session_password", with: 'RandomPass'
       +      click_button "Sign in"
       +    end
       +    expect(page).to have_content "Login is not valid"
       +  end
        
       -        it "failed login with no input entered" do
       -                visit login_path
       -                within "#new_user_session" do
       -                        click_button "Sign in"
       -                end
       -                expect(page).to have_content "You did not provide any details for authentication."
       -        end
       +  it "failed login with no input entered" do
       +    visit login_path
       +    within "#new_user_session" do
       +      click_button "Sign in"
       +    end
       +    expect(page).to have_content "You did not provide any details for authentication."
       +  end
        
       -        it "failed login with no password entered" do
       -                user = create(:user)
       -                visit login_path
       -                within "#new_user_session" do
       -                        fill_in "user_session_login", with: user.login
       -                        click_button "Sign in"
       -                end
       -                expect(page).to have_content "Password cannot be blank"
       -        end
       +  it "failed login with no password entered" do
       +    user = create(:user)
       +    visit login_path
       +    within "#new_user_session" do
       +      fill_in "user_session_login", with: user.login
       +      click_button "Sign in"
       +    end
       +    expect(page).to have_content "Password cannot be blank"
       +  end
        end
 (DIR) diff --git a/spec/models/call_medium_spec.rb b/spec/models/call_medium_spec.rb
       @@ -17,10 +17,10 @@
        require 'rails_helper'
        
        RSpec.describe CallMedium, type: :model do
       -        it { should belong_to(:call) }
       -        it { should belong_to(:project) }
       +  it { should belong_to(:call) }
       +  it { should belong_to(:project) }
        
       -        it "valid record" do
       -                expect(build(:call_medium)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:call_medium)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/call_spec.rb b/spec/models/call_spec.rb
       @@ -27,12 +27,12 @@
        require 'rails_helper'
        
        RSpec.describe Call, type: :model do
       -        it { should belong_to(:project) }
       -        it { should belong_to(:provider) }
       -        it { should belong_to(:job) }
       -        it { should have_one(:call_medium).dependent(:delete) }
       +  it { should belong_to(:project) }
       +  it { should belong_to(:provider) }
       +  it { should belong_to(:job) }
       +  it { should have_one(:call_medium).dependent(:delete) }
        
       -        it "valid record" do
       -                expect(build(:call)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:call)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/job_spec.rb b/spec/models/job_spec.rb
       @@ -21,12 +21,12 @@
        require 'rails_helper'
        
        RSpec.describe Job, type: :model do
       -        it { should belong_to(:project) }
       -        it { should have_many(:calls) }
       +  it { should belong_to(:project) }
       +  it { should have_many(:calls) }
        
       -        it { should validate_presence_of(:project_id) }
       +  it { should validate_presence_of(:project_id) }
        
       -        it "valid record" do
       -                expect(build(:job)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:job)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/line_spec.rb b/spec/models/line_spec.rb
       @@ -14,10 +14,10 @@
        require 'rails_helper'
        
        RSpec.describe Line, type: :model do
       -        it { should belong_to(:project) }
       -        it { should have_many(:line_attributes).dependent(:delete_all) }
       +  it { should belong_to(:project) }
       +  it { should have_many(:line_attributes).dependent(:delete_all) }
        
       -        it "valid record" do
       -                expect(build(:line)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:line)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
       @@ -15,16 +15,16 @@
        require 'rails_helper'
        
        RSpec.describe Project, type: :model do
       -        it { should have_many(:lines).dependent(:delete_all) }
       -        it { should have_many(:line_attributes).dependent(:delete_all) }
       -        it { should have_many(:calls).dependent(:delete_all) }
       -        it { should have_many(:call_media).dependent(:delete_all) }
       -        it { should have_many(:jobs).dependent(:delete_all) }
       +  it { should have_many(:lines).dependent(:delete_all) }
       +  it { should have_many(:line_attributes).dependent(:delete_all) }
       +  it { should have_many(:calls).dependent(:delete_all) }
       +  it { should have_many(:call_media).dependent(:delete_all) }
       +  it { should have_many(:jobs).dependent(:delete_all) }
        
       -        it { should validate_presence_of(:name) }
       -        it { should validate_uniqueness_of(:name) }
       +  it { should validate_presence_of(:name) }
       +  it { should validate_uniqueness_of(:name) }
        
       -        it "valid record" do
       -                expect(build(:project)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:project)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/provider_spec.rb b/spec/models/provider_spec.rb
       @@ -17,20 +17,20 @@
        require 'rails_helper'
        
        RSpec.describe Provider, type: :model do
       -        ## TODO determine if association is unecessary
       -        # the DialResult model does not exist
       -        #it { should have_many(:dial_results) }
       +  ## TODO determine if association is unecessary
       +  # the DialResult model does not exist
       +  #it { should have_many(:dial_results) }
        
       -        it { should validate_presence_of(:name) }
       -        it { should validate_presence_of(:host) }
       -        it { should validate_presence_of(:port) }
       -        it { should validate_presence_of(:user) }
       -        it { should validate_presence_of(:pass) }
       -        it { should validate_presence_of(:lines) }
       -        it { should validate_numericality_of(:port).is_less_than(65536).is_greater_than(0) }
       -        it { should validate_numericality_of(:lines).is_less_than(255).is_greater_than(0) }
       +  it { should validate_presence_of(:name) }
       +  it { should validate_presence_of(:host) }
       +  it { should validate_presence_of(:port) }
       +  it { should validate_presence_of(:user) }
       +  it { should validate_presence_of(:pass) }
       +  it { should validate_presence_of(:lines) }
       +  it { should validate_numericality_of(:port).is_less_than(65536).is_greater_than(0) }
       +  it { should validate_numericality_of(:lines).is_less_than(255).is_greater_than(0) }
        
       -        it "valid record" do
       -                expect(build(:provider)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:provider)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/settings_spec.rb b/spec/models/settings_spec.rb
       @@ -14,7 +14,7 @@
        require 'rails_helper'
        
        RSpec.describe Settings, type: :model do
       -        it "valid record" do
       -                expect(build(:setting)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:setting)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/signature_spec.rb b/spec/models/signature_spec.rb
       @@ -16,11 +16,11 @@
        require 'rails_helper'
        
        RSpec.describe Signature, type: :model do
       -        ## TODO association may not be needed
       -        # causes crash:  PG::UndefinedTable: ERROR:  relation "signature_fps" does not exist
       -        #it { should have_many(:signature_fps) }
       +  ## TODO association may not be needed
       +  # causes crash:  PG::UndefinedTable: ERROR:  relation "signature_fps" does not exist
       +  #it { should have_many(:signature_fps) }
        
       -        it "valid record" do
       -                expect(build(:signature)).to be_valid
       -        end
       +  it "valid record" do
       +    expect(build(:signature)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
       @@ -26,10 +26,10 @@
        require 'rails_helper'
        
        RSpec.describe User, type: :model do
       -        it { should validate_length_of(:password).is_at_least(8) }
       -        it { should validate_length_of(:password_confirmation).is_at_least(8) }
       +  it { should validate_length_of(:password).is_at_least(8) }
       +  it { should validate_length_of(:password_confirmation).is_at_least(8) }
        
       -        it 'valid record' do
       -                expect(build(:user)).to be_valid
       -        end
       +  it 'valid record' do
       +    expect(build(:user)).to be_valid
       +  end
        end
 (DIR) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
       @@ -27,29 +27,29 @@ Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
        ActiveRecord::Migration.maintain_test_schema!
        
        RSpec.configure do |config|
       -        # FactoryGirl Syntax
       -        config.include FactoryGirl::Syntax::Methods
       +  # FactoryGirl Syntax
       +  config.include FactoryGirl::Syntax::Methods
        
       -        # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
       -        config.fixture_path = "#{::Rails.root}/spec/fixtures"
       +  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
       +  config.fixture_path = "#{::Rails.root}/spec/fixtures"
        
       -        # If you're not using ActiveRecord, or you'd prefer not to run each of your
       -        # examples within a transaction, remove the following line or assign false
       -        # instead of true.
       -        config.use_transactional_fixtures = true
       +  # If you're not using ActiveRecord, or you'd prefer not to run each of your
       +  # examples within a transaction, remove the following line or assign false
       +  # instead of true.
       +  config.use_transactional_fixtures = true
        
       -        # RSpec Rails can automatically mix in different behaviours to your tests
       -        # based on their file location, for example enabling you to call `get` and
       -        # `post` in specs under `spec/controllers`.
       -        #
       -        # You can disable this behaviour by removing the line below, and instead
       -        # explicitly tag your specs with their type, e.g.:
       -        #
       -        #     RSpec.describe UsersController, :type => :controller do
       -        #       # ...
       -        #     end
       -        #
       -        # The different available types are documented in the features, such as in
       -        # https://relishapp.com/rspec/rspec-rails/docs
       -        config.infer_spec_type_from_file_location!
       +  # RSpec Rails can automatically mix in different behaviours to your tests
       +  # based on their file location, for example enabling you to call `get` and
       +  # `post` in specs under `spec/controllers`.
       +  #
       +  # You can disable this behaviour by removing the line below, and instead
       +  # explicitly tag your specs with their type, e.g.:
       +  #
       +  #     RSpec.describe UsersController, :type => :controller do
       +  #       # ...
       +  #     end
       +  #
       +  # The different available types are documented in the features, such as in
       +  # https://relishapp.com/rspec/rspec-rails/docs
       +  config.infer_spec_type_from_file_location!
        end
 (DIR) diff --git a/spec/support/auth_logic_helpers.rb b/spec/support/auth_logic_helpers.rb
       @@ -1,20 +1,20 @@
        module Authlogic
       -        module TestHelper
       -                def create_user_session(user)
       -                        visit login_path
       -                        within "#new_user_session" do
       -                                expect(page).to have_content "Username"
       -                                expect(page).to have_content "Password"
       -                                fill_in "user_session_login", with: user.login
       -                                fill_in "user_session_password", with: user.password
       -                                click_button "Sign in"
       -                        end
       -                end
       -        end
       +  module TestHelper
       +    def create_user_session(user)
       +      visit login_path
       +      within "#new_user_session" do
       +        expect(page).to have_content "Username"
       +        expect(page).to have_content "Password"
       +        fill_in "user_session_login", with: user.login
       +        fill_in "user_session_password", with: user.password
       +        click_button "Sign in"
       +      end
       +    end
       +  end
        end
        
        # Make this available to just the request and feature specs
        RSpec.configure do |config|
       -        config.include Authlogic::TestHelper, type: :request
       -        config.include Authlogic::TestHelper, type: :feature
       +  config.include Authlogic::TestHelper, type: :request
       +  config.include Authlogic::TestHelper, type: :feature
        end
        \ No newline at end of file