Slightly more progress - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit 08a2e4abad16cf7bfd22d010c513ce3429a3342b
 (DIR) parent 9cb1016e0f3aa69356bea154884e6f4f16a7041b
 (HTM) Author: HD Moore <hd_moore@rapid7.com>
       Date:   Tue,  2 Aug 2011 07:16:54 +0000
       
       Slightly more progress
       
       
       Diffstat:
         M etc/sigs/01.default.rb              |      13 ++++++++-----
         M etc/sigs/99.default.rb              |       9 +++++++--
         M lib/warvox/audio/raw.rb             |     101 ++++++++++++++++---------------
         M lib/warvox/jobs/analysis.rb         |     170 +++++++++++++++++++-------------
         M lib/warvox/jobs/dialer.rb           |      85 ++++++++++++++++---------------
         M web/db/migrate/20090228195925_crea… |       8 ++++----
         M web/db/migrate/20090228200035_crea… |       4 ++--
         M web/db/migrate/20090228200141_crea… |       5 +++--
         M web/db/migrate/20090303204859_add_… |       2 +-
         M web/db/migrate/20090303204917_add_… |       2 +-
         M web/db/migrate/20090304013815_add_… |       3 ++-
         M web/db/migrate/20090304013839_add_… |       2 +-
         M web/db/migrate/20090304013909_add_… |       3 ++-
         M web/db/migrate/20090304014018_add_… |       2 +-
         M web/db/migrate/20090304014033_add_… |       2 +-
         M web/db/migrate/20090522202032_add_… |       2 +-
         M web/db/migrate/20090526031826_add_… |       4 ++--
         A web/db/migrate/20110801000001_add_… |      10 ++++++++++
         A web/db/migrate/20110801000002_add_… |      10 ++++++++++
         A web/db/migrate/20110801000003_add_… |      20 ++++++++++++++++++++
         M web/db/schema.rb                    |      57 +++++++++++++++++++++-----------
       
       21 files changed, 310 insertions(+), 204 deletions(-)
       ---
 (DIR) diff --git a/etc/sigs/01.default.rb b/etc/sigs/01.default.rb
       @@ -23,6 +23,7 @@ maxf = data[:maxf]
        #
        scnt = 0
        ecnt = 0
       +=begin
        freq.each do |fsec|
                scnt += 1
                if(fsec.length == 0)
       @@ -34,6 +35,7 @@ freq.each do |fsec|
                savg = sump / fsec.length
                ecnt += 1 if (savg < 100)
        end
       +=end
        
        # Store these into data for use later on
        data[:scnt] = scnt
       @@ -44,7 +46,7 @@ data[:ecnt] = ecnt
        #
        if( (fcnt[2100] > 1.0 or fcnt[2230] > 1.0) and fcnt[2250] > 0.5)
                @line_type = 'modem'
       -        raise Completed                                
       +        raise Completed
        end
        
        #
       @@ -52,7 +54,7 @@ end
        #
        if(fcnt[2100] > 1.0 and (maxf > 2245.0 and maxf < 2255.0))
                @line_type = 'modem'
       -        raise Completed                                
       +        raise Completed
        end
        
        #
       @@ -60,15 +62,15 @@ end
        #
        if(fcnt[2100] > 1.0 and (maxf > 2995.0 and maxf < 3005.0))
                @line_type = 'modem'
       -        raise Completed                                
       +        raise Completed
        end
        
        #
        # Look for faxes by checking for a handful of tones (min two)
        #
        fax_sum = 0
       -[ 
       -        fcnt[1625], fcnt[1660], fcnt[1825], fcnt[2100], 
       +[
       +        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]
       @@ -92,3 +94,4 @@ end
        # 99 and greater than 01.
        #
        #
       +
 (DIR) diff --git a/etc/sigs/99.default.rb b/etc/sigs/99.default.rb
       @@ -19,7 +19,7 @@ scnt = data[:scnt]
        # is often a different frequency entirely.
        if(fcnt[1000] >= 1.0)
                @line_type = 'voicemail'
       -        raise Completed                
       +        raise Completed
        end
        
        # Look for voicemail by detecting a peak frequency of
       @@ -30,6 +30,8 @@ if(maxf > 995 and maxf < 1005)
                raise Completed
        end
        
       +=begin
       +
        #
        # Look for silence by checking the frequency signature
        #
       @@ -38,14 +40,17 @@ if(freq.map{|f| f.length}.inject(:+) == 0)
                raise Completed
        end
        
       +
        if(ecnt == scnt)
                @line_type = 'silence'
                raise Completed
        end
       +=end
        
        #
        # Fall back to 'voice' if nothing else has been matched
       -# This should be the last signature file processed 
       +# This should be the last signature file processed
        #
        
        @line_type = 'voice'
       +
 (DIR) diff --git a/lib/warvox/audio/raw.rb b/lib/warvox/audio/raw.rb
       @@ -1,14 +1,14 @@
        module WarVOX
        module Audio
        class Raw
       -        
       +
                @@kissfft_loaded = false
                begin
                        require 'kissfft'
                        @@kissfft_loaded = true
                rescue ::LoadError
                end
       -                
       +
                require 'zlib'
        
                ##
       @@ -17,50 +17,50 @@ class Raw
        
                ##
                # 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| 
       +                self.samples = data.unpack('v*').map do |s|
                                (s > 0x7fff) ? (0x10000 - s) * -1 : s
                        end
                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                
       +                lo_cnt = 0
                        hi_cnt = 0
        
                        data = self.samples.map {|c| c.abs}
       @@ -90,7 +90,7 @@ class Raw
                                        while(idx < data.length and data[idx] > lo_lim)
                                                buff << data[idx]
                                                idx += 1
       -                                end        
       +                                end
        
                                        # Ignore any sequence that is too small
                                        fprint << [:hi, buff.length, buff] if buff.length > hi_min
       @@ -123,7 +123,7 @@ class Raw
        
                        #
                        # Process results
       -                # 
       +                #
                        sig = ""
        
                        final.each do |f|
       @@ -132,7 +132,7 @@ class Raw
                                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
       @@ -141,16 +141,16 @@ class Raw
        
                        if(not @@kissfft_loaded)
                                raise RuntimeError, "The KissFFT module is not availabale, raw.to_freq() failed"
       -                end        
       -                
       +                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
        
       @@ -173,10 +173,10 @@ class Raw
                        end
        
                        # 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| 
       +                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]
       @@ -184,19 +184,19 @@ class Raw
                                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| 
       +                        [frq, pwr]}.sort{|a,b|
                                        b[1] <=> a[1]
       -                        }[0, fcnt].map{|w| 
       +                        }[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
       @@ -210,17 +210,17 @@ class Raw
        
                        # 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={})
       -                # Convert this to a text file
       -                to_freq_sig(opts).sort.join("\n")
       +                "{" + to_freq_sig(opts).sort.join(",") + "}"
                end
       -        
       +
                def self.fft_to_freq_sig(ffts, freq_cnt)
                        sig = []
                        ffts.each do |s|
       @@ -231,34 +231,34 @@ class Raw
                                        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] 
       +
       +                        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
       +                                a[0] <=> b[0]
       +                        }.map {|a| [a[0].round, a[1].round ] }             # round to whole numbers
                        end
       -                
       -                sig        
       +
       +                sig
                end
       -        
       +
                # Find pattern inside of sample
       -        def self.compare_freq_sig(pat, zam, opts)        
       -                
       +        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 = []                
       +                        res = []
                                sam = zam[si, zam.length]
       -        
       +
                                0.upto(pat.length - 1) do |pi|
                                        diff = []
                                        next if not pat[pi]
       @@ -295,14 +295,15 @@ class Raw
                                        end
                                        prev = len
                                end
       -                        
       +
                                final << [ (rsum / res.length.to_f), res.map {|x| x.length}]
                        end
       -                
       +
                        final
                end
       -        
       +
        
        end
        end
        end
       +
 (DIR) diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
       @@ -1,33 +1,33 @@
        module WarVOX
        module Jobs
       -class Analysis < Base 
       +class Analysis < Base
        
                require 'fileutils'
                require 'tempfile'
                require 'yaml'
                require 'open3'
       -        
       +
                @@kissfft_loaded = false
                begin
                        require 'kissfft'
                        @@kissfft_loaded = true
                rescue ::LoadError
                end
       -        
       +
                class SignalProcessor
       -        
       +
                        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)
       @@ -35,80 +35,89 @@ class Analysis < Base
                                end
                        end
                end
       -        
       +
                def type
                        'analysis'
                end
       -        
       +
                def initialize(job_id)
                        @name = job_id
                        if(not @@kissfft_loaded)
       -                        raise RuntimeError, "The KissFFT module is not availabale, analysis failed"
       +                        raise RuntimeError, "The KissFFT module is not available, analysis failed"
                        end
                end
       -        
       +
                def get_job
                        ::DialJob.find(@name)
                end
       -        
       +
                def start
                        @status = 'active'
       -                
       +
                        begin
                        start_processing()
       -                
       +
                        model = get_job
                        model.processed = true
                        db_save(model)
       -                
       +
                        stop()
       -                
       +
                        rescue ::Exception => e
                                $stderr.puts "Exception in the job queue: #{e.class} #{e} #{e.backtrace}"
                        end
                end
       -        
       +
                def stop
                        @status = 'completed'
                end
       -        
       +
                def start_processing
                        todo = ::DialResult.find_all_by_dial_job_id(@name)
                        jobs = []
                        todo.each do |r|
                                next if r.processed
                                next if not r.completed
       -                        next if r.busy                
       +                        next if r.busy
                                jobs << r
                        end
       -                
       +
                        max_threads = WarVOX::Config.analysis_threads
       -                
       +
                        while(not jobs.empty?)
                                threads = []
                                output  = []
       -                        1.upto(max_threads) do 
       -                                j = jobs.shift || break                
       +                        1.upto(max_threads) do
       +                                j = jobs.shift || break
                                        output  << j
                                        threads << Thread.new { run_analyze_call(j) }
                                end
        
                                # Wait for the threads to complete
                                threads.each {|t| t.join}
       -                        
       +
                                # Save the results to the database
                                output.each  {|r| db_save(r) if r.processed }
                        end
                end
       -        
       +
                def run_analyze_call(r)
                        $stderr.puts "DEBUG: Processing audio for #{r.number}..."
       -        
       +
       +
       +
                        bin = File.join(WarVOX::Base, 'bin', 'analyze_result.rb')
       -                pfd = IO.popen("#{bin} '#{r.rawfile}'")
       +                tmp = Tempfile.new("Analysis")
       +                begin
       +
       +                ::File.open(tmp, "wb") do |fd|
       +                        fd.write(r.audio)
       +                end
       +
       +                pfd = IO.popen("#{bin} '#{tmp.path}'")
                        out = YAML.load(pfd.read)
                        pfd.close
       -                
       +
                        return if not out
        
                        out.each_key do |k|
       @@ -117,35 +126,39 @@ class Analysis < Base
                                        r.send(setter, out[k])
                                end
                        end
       -                
       +
                        r.processed_at = Time.now
       -                r.processed    = true        
       +                r.processed    = true
       +
       +                rescue ::Interrupt
       +                ensure
       +                        tmp.close
       +                        tmp.unlink
       +                end
       +
                        true
                end
       -        
       +
                # Takes the raw file path as an argument, returns a hash
                def analyze_call(input)
        
                        return if not input
                        return if not File.exist?(input)
        
       -                bname = input.gsub(/\..*/, '')
       -                num   = File.basename(bname)
       +                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)                
       -                
       +                fft  = KissFFT.fftr(8192, 8000, 1, raw.samples)
       +
                        freq = raw.to_freq_sig_txt()
       -                fd   = File.new("#{bname}.sig", "wb")
       -                fd.write freq
       -                fd.close
        
                        # Save the signature data
       -                res[:sig_data] = freq
       +                res[:fprint] = freq
        
                        #
                        # Create a raw decompressed file
       @@ -153,7 +166,7 @@ class Analysis < Base
        
                        # Decompress the audio file
                        rawfile = Tempfile.new("rawfile")
       -                datfile = Tempfile.new("datfile")                        
       +                datfile = Tempfile.new("datfile")
        
                        # Data files for audio processing and signal graph
                        cnt = 0
       @@ -220,11 +233,11 @@ class Analysis < Base
                                        fcnt[fdx]  += 0.1
                                end
                        end
       -                
       +
                        #
                        # Signature processing
                        #
       -                
       +
                        sproc = SignalProcessor.new
                        sproc.data =
                        {
       @@ -253,39 +266,45 @@ class Analysis < Base
        
                        # Save any matched signatures
                        res[:signatures] = sproc.signatures.map{|s| "#{s[0]}:#{s[1]}:#{s[2]}" }.join("\n")
       -                
       +
       +                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 ylabel \"Signal\"")
                        plotter.puts("set xlabel \"Seconds\"")
                        plotter.puts("set terminal png medium size 640,480 transparent")
       -                plotter.puts("set output \"#{bname}_big.png\"")
       +                plotter.puts("set output \"#{png_big.path}\"")
                        plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with lines")
       -                plotter.puts("set output \"#{bname}_big_dots.png\"")
       +                plotter.puts("set output \"#{png_big_dots.path}\"")
                        plotter.puts("plot \"#{datfile.path}\" using 1:2 title \"#{num}\" with dots")
        
                        plotter.puts("set terminal png medium size 640,480 transparent")
                        plotter.puts("set ylabel \"Power\"")
                        plotter.puts("set xlabel \"Frequency\"")
       -                plotter.puts("set output \"#{bname}_freq_big.png\"")
       +                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("set ylabel \"Signal\"")
                        plotter.puts("set xlabel \"Seconds\"")
                        plotter.puts("set terminal png small size 160,120 transparent")
                        plotter.puts("set format x ''")
       -                plotter.puts("set format y ''")        
       -                plotter.puts("set output \"#{bname}.png\"")
       +                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("set ylabel \"Power\"")
       -                plotter.puts("set xlabel \"Frequency\"")                                        
       +                plotter.puts("set xlabel \"Frequency\"")
                        plotter.puts("set terminal png small size 160,120 transparent")
                        plotter.puts("set format x ''")
       -                plotter.puts("set format y ''")        
       -                plotter.puts("set output \"#{bname}_freq.png\"")
       -                plotter.puts("plot \"#{frefile.path}\" using 1:2 notitle with lines")                                                
       +                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}")
       @@ -296,6 +315,14 @@ class Analysis < Base
                        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 }
       +
        
                        # Detect DTMF and MF tones
                        dtmf = ''
       @@ -310,23 +337,29 @@ class Analysis < Base
                                if(line.strip =~ /^- DTMF numbers:\s+(.*)/)
                                        next if $1 == 'none'
                                        dtmf = $1
       -                        end                        
       +                        end
                        end
                        pfd.close
                        res[:dtmf] = dtmf
                        res[:mf]   = mf
        
        
       +                tmp_wav = Tempfile.new("wav")
       +                tmp_mp3 = Tempfile.new("mp3")
       +
                        # Generate a MP3 audio file
       -                system("#{WarVOX::Config.tool_path('sox')} -s -2 -r 8000 -t raw -c 1 #{rawfile.path} #{bname}.wav")
       -                
       +                system("#{WarVOX::Config.tool_path('sox')} -s -2 -r 8000 -t raw -c 1 #{rawfile.path} -t wav #{tmp_wav.path}")
       +
                        # Default samples at 8k, bump it to 32k to get better quality
       -                system("#{WarVOX::Config.tool_path('lame')} -b 32 #{bname}.wav #{bname}.mp3 >/dev/null 2>&1")
       -                
       -                File.unlink("#{bname}.wav")
       +                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_wav.path, "rb") { |fd| res[:mp3] = fd.read }
       +
       +                [tmp_wav, tmp_mp3].map {|x| x.unlink; x.close }
       +
                        clear_zombies()
        
                        res
       @@ -335,44 +368,44 @@ end
        
        
        class CallAnalysis < Analysis
       -        
       +
                @@kissfft_loaded = false
                begin
                        require 'kissfft'
                        @@kissfft_loaded = true
                rescue ::LoadError
                end
       -        
       +
                def type
                        'call_analysis'
                end
       -        
       +
                def initialize(result_id)
                        @name = result_id
                        if(not @@kissfft_loaded)
                                raise RuntimeError, "The KissFFT module is not available, analysis failed"
                        end
                end
       -        
       +
                def get_job
                        ::DialResult.find(@name)
                end
       -        
       +
                def start
                        @status = 'active'
       -                
       +
                        begin
                                start_processing()
       -                        stop()                
       +                        stop()
                        rescue ::Exception => e
                                $stderr.puts "Exception in the job queue: #{e.class} #{e} #{e.backtrace}"
                        end
                end
       -        
       +
                def stop
                        @status = 'completed'
                end
       -        
       +
                def start_processing
                        r = get_job()
                        return if not r.completed
       @@ -383,3 +416,4 @@ end
        
        end
        end
       +
 (DIR) diff --git a/lib/warvox/jobs/dialer.rb b/lib/warvox/jobs/dialer.rb
       @@ -1,25 +1,25 @@
        module WarVOX
        module Jobs
       -class Dialer < Base 
       +class Dialer < Base
        
                require 'fileutils'
       -        
       +
                def type
                        'dialer'
                end
       -        
       +
                def initialize(job_id)
                        @name    = job_id
       -                model    = get_job
       -                @range   = model.range
       -                @seconds = model.seconds
       -                @lines   = model.lines                
       +                @job     = get_job
       +                @range   = @job.range
       +                @seconds = @job.seconds
       +                @lines   = @job.lines
                        @nums    = shuffle_a(WarVOX::Phone.crack_mask(@range))
       -                
       +
                        # CallerID modes (SELF or a mask)
       -                @cid_self = model.cid_mask == 'SELF'
       +                @cid_self = @job.cid_mask == 'SELF'
                        if(not @cid_self)
       -                        @cid_range = WarVOX::Phone.crack_mask(model.cid_mask)
       +                        @cid_range = WarVOX::Phone.crack_mask(@job.cid_mask)
                        end
                end
        
       @@ -56,31 +56,31 @@ class Dialer < Base
                                }
                                1.upto(prov.lines) {|i| res.push(info) }
                        end
       -                
       +
                        shuffle_a(res)
                end
       -        
       +
                def get_job
                        ::DialJob.find(@name)
                end
       -        
       +
                def start
                        begin
       -                
       +
                        model = get_job
                        model.status = 'active'
                        model.started_at = Time.now
                        db_save(model)
       -                
       +
                        start_dialing()
       -                
       +
                        stop()
       -                
       +
                        rescue ::Exception => e
                                $stderr.puts "Exception in the job queue: #{$e.class} #{e} #{e.backtrace}"
                        end
                end
       -        
       +
                def stop
                        @status = 'completed'
                        model = get_job
       @@ -88,11 +88,11 @@ class Dialer < Base
                        model.completed_at = Time.now
                        db_save(model)
                end
       -        
       +
                def start_dialing
       -                dest = File.join(WarVOX::Config.data_path, "#{@name}")
       +                dest = File.join(WarVOX::Config.data_path, @name.to_s)
                        FileUtils.mkdir_p(dest)
       -        
       +
                        # Scrub all numbers matching the blacklist
                        list = WarVOX::Config.blacklist_load
                        list.each do |b|
       @@ -104,26 +104,26 @@ class Dialer < Base
                                        end
                                end
                        end
       -        
       +
                        @nums_total = @nums.length
                        while(@nums.length > 0)
                                @calls    = []
                                @provs    = get_providers
                                tasks     = []
                                max_tasks = [@provs.length, @lines].min
       -                        
       +
                                1.upto(max_tasks) do
                                        tasks << Thread.new do
       -                                        
       +
                                                Thread.current.kill if @nums.length == 0
                                                Thread.current.kill if @provs.length == 0
       -                
       +
                                                num  = @nums.shift
                                                prov = @provs.shift
       -                                        
       +
                                                Thread.current.kill if not num
                                                Thread.current.kill if not prov
       -                                        
       +
                                                out = File.join(dest, num+".raw")
        
                                                begin
       @@ -134,7 +134,7 @@ class Dialer < Base
                                                byte = 0
                                                path = ''
                                                cid  = @cid_self ? num : @cid_range[ rand(@cid_range.length) ]
       -                                        
       +
                                                IO.popen(
                                                        [
                                                                WarVOX::Config.tool_path('iaxrecord'),
       @@ -152,8 +152,8 @@ class Dialer < Base
                                                                num,
                                                                "-l",
                                                                @seconds
       -                                                ].map{|i| 
       -                                                        "'" + i.to_s.gsub("'",'') +"'" 
       +                                                ].map{|i|
       +                                                        "'" + i.to_s.gsub("'",'') +"'"
                                                }.join(" ")).each_line do |line|
                                                        $stderr.puts "DEBUG: #{line.strip}"
                                                        if(line =~ /^COMPLETED/)
       @@ -161,12 +161,12 @@ class Dialer < Base
                                                                        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'        
       +                                                                byte = info[1].to_i if info[0] == 'BYTES'
                                                                        path = info[1]      if info[0] == 'FILE'
                                                                end
                                                        end
                                                end
       -                                        
       +
                                                res = ::DialResult.new
                                                res.number = num
                                                res.cid = cid
       @@ -177,21 +177,21 @@ class Dialer < Base
                                                res.seconds = (byte / 16000)  # 8khz @ 16-bit
                                                res.ringtime = ring
                                                res.processed = false
       -                                        res.created_at = Time.now
       -                                        res.updated_at = Time.now
       -                                        
       +
                                                if(File.exists?(out))
       -                                                system("gzip -9 #{out}")
       -                                                res.rawfile = out + ".gz"
       +                                                File.open(out, "rb") do |fd|
       +                                                        res.audio = fd.read(fd.stat.size)
       +                                                end
       +                                                File.unlink(out)
                                                end
       -                                        
       +
                                                @calls << res
       -                                        
       +
                                                rescue ::Exception => e
                                                        $stderr.puts "ERROR: #{e.class} #{e} #{e.backtrace} #{num} #{prov.inspect}"
                                                end
                                        end
       -        
       +
                                        # END NEW THREAD
                                end
                                # END SPAWN THREADS
       @@ -206,13 +206,14 @@ class Dialer < Base
                                model = get_job
                                model.progress = ((@nums_total - @nums.length) / @nums_total.to_f) * 100
                                db_save(model)
       -                        
       +
                                clear_zombies()
                        end
       -                
       +
                        # ALL DONE
                end
        
        end
        end
        end
       +
 (DIR) diff --git a/web/db/migrate/20090228195925_create_providers.rb b/web/db/migrate/20090228195925_create_providers.rb
       @@ -1,11 +1,11 @@
        class CreateProviders < ActiveRecord::Migration
          def self.up
            create_table :providers do |t|
       -      t.string :name
       -      t.string :host
       +      t.text :name
       +      t.text :host
              t.integer :port
       -      t.string :user
       -      t.string :pass
       +      t.text :user
       +      t.text :pass
              t.integer :lines
        
              t.timestamps
 (DIR) diff --git a/web/db/migrate/20090228200035_create_dial_jobs.rb b/web/db/migrate/20090228200035_create_dial_jobs.rb
       @@ -1,10 +1,10 @@
        class CreateDialJobs < ActiveRecord::Migration
          def self.up
            create_table :dial_jobs do |t|
       -      t.string :range
       +      t.text :range
              t.integer :seconds
              t.integer :lines
       -      t.string :status
       +      t.text :status
              t.integer :progress
              t.datetime :started_at
              t.datetime :completed_at
 (DIR) diff --git a/web/db/migrate/20090228200141_create_dial_results.rb b/web/db/migrate/20090228200141_create_dial_results.rb
       @@ -1,14 +1,14 @@
        class CreateDialResults < ActiveRecord::Migration
          def self.up
            create_table :dial_results do |t|
       -      t.integer :number
       +      t.text :number
              t.integer :dial_job_id
              t.integer :provider_id
              t.boolean :completed
              t.boolean :busy
              t.integer :seconds
              t.integer :ringtime
       -      t.string :rawfile
       +      t.text :rawfile
                  t.boolean :processed
        
              t.timestamps
       @@ -19,3 +19,4 @@ class CreateDialResults < ActiveRecord::Migration
            drop_table :dial_results
          end
        end
       +
 (DIR) diff --git a/web/db/migrate/20090303204859_add_cid_mask_to_dial_jobs.rb b/web/db/migrate/20090303204859_add_cid_mask_to_dial_jobs.rb
       @@ -1,6 +1,6 @@
        class AddCidMaskToDialJobs < ActiveRecord::Migration
          def self.up
       -    add_column :dial_jobs, :cid_mask, :string
       +    add_column :dial_jobs, :cid_mask, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20090303204917_add_cid_to_dial_results.rb b/web/db/migrate/20090303204917_add_cid_to_dial_results.rb
       @@ -1,6 +1,6 @@
        class AddCidToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :cid, :string
       +    add_column :dial_results, :cid, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20090304013815_add_peak_freq_to_dial_results.rb b/web/db/migrate/20090304013815_add_peak_freq_to_dial_results.rb
       @@ -1,9 +1,10 @@
        class AddPeakFreqToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :peak_freq, :number
       +    add_column :dial_results, :peak_freq, :float
          end
        
          def self.down
            remove_column :dial_results, :peak_freq
          end
        end
       +
 (DIR) diff --git a/web/db/migrate/20090304013839_add_peak_freq_data_to_dial_results.rb b/web/db/migrate/20090304013839_add_peak_freq_data_to_dial_results.rb
       @@ -1,6 +1,6 @@
        class AddPeakFreqDataToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :peak_freq_data, :string
       +    add_column :dial_results, :peak_freq_data, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20090304013909_add_sig_data_to_dial_results.rb b/web/db/migrate/20090304013909_add_sig_data_to_dial_results.rb
       @@ -1,9 +1,10 @@
        class AddSigDataToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :sig_data, :string
       +    add_column :dial_results, :sig_data, :text
          end
        
          def self.down
            remove_column :dial_results, :sig_data
          end
        end
       +
 (DIR) diff --git a/web/db/migrate/20090304014018_add_line_type_to_dial_results.rb b/web/db/migrate/20090304014018_add_line_type_to_dial_results.rb
       @@ -1,6 +1,6 @@
        class AddLineTypeToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :line_type, :string
       +    add_column :dial_results, :line_type, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20090304014033_add_notes_to_dial_results.rb b/web/db/migrate/20090304014033_add_notes_to_dial_results.rb
       @@ -1,6 +1,6 @@
        class AddNotesToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :notes, :string
       +    add_column :dial_results, :notes, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20090522202032_add_signatures_to_dial_results.rb b/web/db/migrate/20090522202032_add_signatures_to_dial_results.rb
       @@ -1,6 +1,6 @@
        class AddSignaturesToDialResults < ActiveRecord::Migration
          def self.up
       -    add_column :dial_results, :signatures, :string
       +    add_column :dial_results, :signatures, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20090526031826_add_mf_and_dtmf_to_dial_results.rb b/web/db/migrate/20090526031826_add_mf_and_dtmf_to_dial_results.rb
       @@ -1,7 +1,7 @@
        class AddMfAndDtmfToDialResults < ActiveRecord::Migration
          def self.up
       -          add_column :dial_results, :dtmf, :string
       -          add_column :dial_results, :mf, :string
       +          add_column :dial_results, :dtmf, :text
       +          add_column :dial_results, :mf, :text
          end
        
          def self.down
 (DIR) diff --git a/web/db/migrate/20110801000001_add_fprint_to_dial_result.rb b/web/db/migrate/20110801000001_add_fprint_to_dial_result.rb
       @@ -0,0 +1,10 @@
       +class AddFprintToDialResult < ActiveRecord::Migration
       +  def self.up
       +    add_column :dial_results, :fprint, 'int[]'
       +  end
       +
       +  def self.down
       +    remove_column :dial_results, :fprint
       +  end
       +end
       +
 (DIR) diff --git a/web/db/migrate/20110801000002_add_audio_to_dial_result.rb b/web/db/migrate/20110801000002_add_audio_to_dial_result.rb
       @@ -0,0 +1,10 @@
       +class AddAudioToDialResult < ActiveRecord::Migration
       +  def self.up
       +    add_column :dial_results, :audio, :binary
       +  end
       +
       +  def self.down
       +    remove_column :dial_results, :audio
       +  end
       +end
       +
 (DIR) diff --git a/web/db/migrate/20110801000003_add_media_to_dial_result.rb b/web/db/migrate/20110801000003_add_media_to_dial_result.rb
       @@ -0,0 +1,20 @@
       +class AddMediaToDialResult < ActiveRecord::Migration
       +  def self.up
       +    add_column :dial_results, :mp3, :binary
       +    add_column :dial_results, :png_big, :binary
       +    add_column :dial_results, :png_big_dots, :binary
       +    add_column :dial_results, :png_big_freq, :binary
       +    add_column :dial_results, :png_sig, :binary
       +    add_column :dial_results, :png_sig_freq, :binary
       +  end
       +
       +  def self.down
       +    remove_column :dial_results, :mp3
       +    remove_column :dial_results, :png_big
       +    remove_column :dial_results, :png_big_dots
       +    remove_column :dial_results, :png_big_freq
       +    remove_column :dial_results, :png_sig
       +    remove_column :dial_results, :png_sig_freq
       +  end
       +end
       +
 (DIR) diff --git a/web/db/schema.rb b/web/db/schema.rb
       @@ -10,56 +10,75 @@
        #
        # It's strongly recommended to check this file into your version control system.
        
       -ActiveRecord::Schema.define(:version => 20090526031826) do
       +ActiveRecord::Schema.define(:version => 20110801000003) do
        
          create_table "dial_jobs", :force => true do |t|
       -    t.string   "range"
       +    t.text     "range"
            t.integer  "seconds"
            t.integer  "lines"
       -    t.string   "status"
       +    t.text     "status"
            t.integer  "progress"
            t.datetime "started_at"
            t.datetime "completed_at"
            t.boolean  "processed"
            t.datetime "created_at"
            t.datetime "updated_at"
       -    t.string   "cid_mask"
       +    t.text     "cid_mask"
          end
        
          create_table "dial_results", :force => true do |t|
       -    t.integer  "number"
       +    t.text     "number"
            t.integer  "dial_job_id"
            t.integer  "provider_id"
            t.boolean  "completed"
            t.boolean  "busy"
            t.integer  "seconds"
            t.integer  "ringtime"
       -    t.string   "rawfile"
       +    t.text     "rawfile"
            t.boolean  "processed"
            t.datetime "created_at"
            t.datetime "updated_at"
            t.datetime "processed_at"
       -    t.string   "cid"
       -    t.decimal  "peak_freq"
       -    t.string   "peak_freq_data"
       -    t.string   "sig_data"
       -    t.string   "line_type"
       -    t.string   "notes"
       -    t.string   "signatures"
       -    t.string   "dtmf"
       -    t.string   "mf"
       +    t.text     "cid"
       +    t.float    "peak_freq"
       +    t.text     "peak_freq_data"
       +    t.text     "sig_data"
       +    t.text     "line_type"
       +    t.text     "notes"
       +    t.text     "signatures"
       +    t.text     "dtmf"
       +    t.text     "mf"
       +    t.string   "fprint",         :limit => nil
       +    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 "providers", :force => true do |t|
       -    t.string   "name"
       -    t.string   "host"
       +    t.text     "name"
       +    t.text     "host"
            t.integer  "port"
       -    t.string   "user"
       -    t.string   "pass"
       +    t.text     "user"
       +    t.text     "pass"
            t.integer  "lines"
            t.datetime "created_at"
            t.datetime "updated_at"
            t.boolean  "enabled"
          end
        
       +  create_table "signatures", :force => true do |t|
       +    t.datetime "created_at"
       +    t.datetime "updated_at"
       +    t.string   "name"
       +    t.string   "category"
       +    t.text     "description"
       +    t.string   "mode"
       +    t.string   "print",       :limit => nil
       +    t.text     "rules"
       +  end
       +
        end