| Slightly more progress - warvox - VoIP based wardialing tool, forked from rapid… | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit 08a2e4abad16cf7bfd22d010c513ce3429a3342b | |
| parent 9cb1016e0f3aa69356bea154884e6f4f16a7041b | |
| Author: HD Moore <[email protected]> | |
| 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(-) | |
| --- | |
| 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. | |
| # | |
| # | |
| + | |
| 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' | |
| + | |
| 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.len… | |
| @@ -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 availab… | |
| - 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 sa… | |
| - 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 < … | |
| res << [maxf, maxp] | |
| maxf,maxp = [0,0] | |
| end | |
| end | |
| - | |
| - sig << res.sort{ |a,b| # … | |
| - a[1] <=> b[1] | |
| + | |
| + sig << res.sort{ |a,b| # s… | |
| + a[1] <=> b[1] | |
| }.reverse[0,freq_cnt].sort { |a,b| # t… | |
| - a[0] <=> b[0] … | |
| - }.map {|a| [a[0].round, a[1].round ] } # … | |
| + a[0] <=> b[0] | |
| + }.map {|a| [a[0].round, a[1].round ] } # r… | |
| 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.le… | |
| end | |
| - | |
| + | |
| final | |
| end | |
| - | |
| + | |
| end | |
| end | |
| end | |
| + | |
| 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 availab… | |
| + raise RuntimeError, "The KissFFT module is not availab… | |
| 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} #… | |
| 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]}:#… | |
| - | |
| + | |
| + 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… | |
| - 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… | |
| 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… | |
| 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 … | |
| 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 … | |
| + plotter.puts("set format y ''") | |
| + plotter.puts("set output \"#{png_sig_freq.path}\"") | |
| + plotter.puts("plot \"#{frefile.path}\" using 1:2 notitle with … | |
| 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] … | |
| + ::File.open(png_big_dots.path, 'rb') { |fd| res[:png_big_dots]… | |
| + ::File.open(png_big_freq.path, 'rb') { |fd| res[:png_big_freq]… | |
| + ::File.open(png_sig.path, 'rb') { |fd| res[:png_sig] … | |
| + ::File.open(png_sig_freq.path, 'rb') { |fd| res[:png_sig_freq]… | |
| + | |
| + [png_big, png_big_dots, png_big_freq, png_sig, png_sig_freq ].… | |
| + | |
| # 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 ra… | |
| - | |
| + system("#{WarVOX::Config.tool_path('sox')} -s -2 -r 8000 -t ra… | |
| + | |
| # Default samples at 8k, bump it to 32k to get better quality | |
| - system("#{WarVOX::Config.tool_path('lame')} -b 32 #{bname}.wav… | |
| - | |
| - File.unlink("#{bname}.wav") | |
| + system("#{WarVOX::Config.tool_path('lame')} -b 32 #{tmp_wav.pa… | |
| + | |
| 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 availab… | |
| 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} #… | |
| 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 | |
| + | |
| 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} … | |
| 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 ==… | |
| Thread.current.kill if @provs.length =… | |
| - | |
| + | |
| 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[ r… | |
| - | |
| + | |
| IO.popen( | |
| [ | |
| WarVOX::Config.tool_pa… | |
| @@ -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.st… | |
| if(line =~ /^COMPLETED/) | |
| @@ -161,12 +161,12 @@ class Dialer < Base | |
| busy = info[1]… | |
| fail = info[1]… | |
| ring = info[1]… | |
| - byte = info[1]… | |
| + byte = info[1]… | |
| path = info[1]… | |
| end | |
| end | |
| end | |
| - | |
| + | |
| res = ::DialResult.new | |
| res.number = num | |
| res.cid = cid | |
| @@ -177,21 +177,21 @@ class Dialer < Base | |
| res.seconds = (byte / 16000) # 8khz @… | |
| 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… | |
| + end | |
| + File.unlink(out) | |
| end | |
| - | |
| + | |
| @calls << res | |
| - | |
| + | |
| rescue ::Exception => e | |
| $stderr.puts "ERROR: #{e.class… | |
| 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… | |
| db_save(model) | |
| - | |
| + | |
| clear_zombies() | |
| end | |
| - | |
| + | |
| # ALL DONE | |
| end | |
| end | |
| end | |
| end | |
| + | |
| diff --git a/web/db/migrate/20090228195925_create_providers.rb b/web/db/migrate… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090228200035_create_dial_jobs.rb b/web/db/migrate… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090228200141_create_dial_results.rb b/web/db/migr… | |
| @@ -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 | |
| + | |
| diff --git a/web/db/migrate/20090303204859_add_cid_mask_to_dial_jobs.rb b/web/d… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090303204917_add_cid_to_dial_results.rb b/web/db/… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090304013815_add_peak_freq_to_dial_results.rb b/w… | |
| @@ -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 | |
| + | |
| diff --git a/web/db/migrate/20090304013839_add_peak_freq_data_to_dial_results.r… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090304013909_add_sig_data_to_dial_results.rb b/we… | |
| @@ -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 | |
| + | |
| diff --git a/web/db/migrate/20090304014018_add_line_type_to_dial_results.rb b/w… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090304014033_add_notes_to_dial_results.rb b/web/d… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090522202032_add_signatures_to_dial_results.rb b/… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20090526031826_add_mf_and_dtmf_to_dial_results.rb b… | |
| @@ -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 | |
| diff --git a/web/db/migrate/20110801000001_add_fprint_to_dial_result.rb b/web/d… | |
| @@ -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 | |
| + | |
| diff --git a/web/db/migrate/20110801000002_add_audio_to_dial_result.rb b/web/db… | |
| @@ -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 | |
| + | |
| diff --git a/web/db/migrate/20110801000003_add_media_to_dial_result.rb b/web/db… | |
| @@ -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 | |
| + | |
| 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 syste… | |
| -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 |