| First push of the new code base - warvox - VoIP based wardialing tool, forked f… | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit 1d11bb69a4d816c751af34dc79b60a4245afd3dc | |
| parent b99f2cf35da070c8d767310bf30a24808bf788e4 | |
| Author: HD Moore <[email protected]> | |
| Date: Sun, 1 Mar 2009 21:31:55 +0000 | |
| First push of the new code base | |
| Diffstat: | |
| A Makefile | 18 ++++++++++++++++++ | |
| A README | 21 +++++++++++++++++++++ | |
| M bin/automatch.rb | 3 +++ | |
| A bin/warvox.rb | 46 +++++++++++++++++++++++++++++… | |
| A etc/warvox.conf | 28 ++++++++++++++++++++++++++++ | |
| M lib/warvox.rb | 15 ++++++++++++--- | |
| A lib/warvox/config.rb | 68 +++++++++++++++++++++++++++++… | |
| M lib/warvox/db.rb | 2 +- | |
| A lib/warvox/jobs.rb | 60 +++++++++++++++++++++++++++++… | |
| A lib/warvox/jobs/analysis.rb | 178 +++++++++++++++++++++++++++++… | |
| A lib/warvox/jobs/base.rb | 24 ++++++++++++++++++++++++ | |
| A lib/warvox/jobs/dialer.rb | 206 +++++++++++++++++++++++++++++… | |
| A lib/warvox/phone.rb | 32 +++++++++++++++++++++++++++++… | |
| M src/iaxrecord/iaxrecord.c | 10 +++++++++- | |
| 14 files changed, 706 insertions(+), 5 deletions(-) | |
| --- | |
| diff --git a/Makefile b/Makefile | |
| @@ -0,0 +1,18 @@ | |
| +all: install | |
| + | |
| +install: iaxrecord ruby-kissfft | |
| + cp -a src/iaxrecord/iaxrecord bin/ | |
| + cp -a src/ruby-kissfft/kissfft.so lib/ | |
| + | |
| +iaxrecord: | |
| + make -C src/iaxrecord/ | |
| + | |
| +ruby-kissfft: | |
| + ( cd src/ruby-kissfft/; ruby extconf.rb ) | |
| + make -C src/ruby-kissfft/ | |
| + | |
| +clean: | |
| + ( cd src/ruby-kissfft/; ruby extconf.rb ) | |
| + make -C src/ruby-kissfft/ clean | |
| + make -C src/iaxrecord/ clean | |
| + rm -f bin/iaxrecord lib/kissfft.so | |
| diff --git a/README b/README | |
| @@ -0,0 +1,21 @@ | |
| +WarVOX Quick Start Guide | |
| +======================== | |
| + | |
| +1. Install all pre-requisites | |
| + Ubuntu: | |
| + $ sudo apt-get install libiaxclient-dev sox lame ruby gnuplot | |
| + | |
| +2. Build the WarVOX tools and modules | |
| + $ make install | |
| + | |
| +3. Change the admin user/pass in etc/warvox.conf | |
| + | |
| +4. Start the WarVOX interface | |
| + $ bin/warvox.rb | |
| + | |
| +5. Access the interface in a web browser | |
| + $ firefox http://127.0.0.1:7777/ | |
| + | |
| +6. Configure an IAX-capable VoIP provider | |
| + | |
| +7. Create a a new job | |
| diff --git a/bin/automatch.rb b/bin/automatch.rb | |
| @@ -42,6 +42,9 @@ oset = wdb.keys.sort | |
| iset = oset.dup | |
| +$stdout.puts car.keys.map{|x| "#{x}-100" }.join(" ") | |
| +$stdout.flush | |
| + | |
| while(not oset.empty?) | |
| s = Time.now | |
| diff --git a/bin/warvox.rb b/bin/warvox.rb | |
| @@ -0,0 +1,46 @@ | |
| +#!/usr/bin/env ruby | |
| +################### | |
| + | |
| +# | |
| +# Load the library path | |
| +# | |
| +base = __FILE__ | |
| +while File.symlink?(base) | |
| + base = File.expand_path(File.readlink(base), File.dirname(base)) | |
| +end | |
| + | |
| +voxroot = File.join(File.dirname(base), '..', 'web') | |
| +Dir.chdir(voxroot) | |
| + | |
| +voxserv = File.join('script', 'server') | |
| + | |
| +opts = | |
| +{ | |
| + 'ServerPort' => 7777, | |
| + 'ServerHost' => '127.0.0.1', | |
| + 'Background' => false, | |
| +} | |
| + | |
| + | |
| +# Clear ARGV | |
| +while(ARGV.length > 0) | |
| + ARGV.shift | |
| +end | |
| + | |
| +# Rebuild ARGV | |
| +[ | |
| + '-p', opts['ServerPort'].to_s, | |
| + '-b', opts['ServerHost'], | |
| + '-e', 'production', | |
| + (opts['Background'] ? '-d' : '') | |
| +].each do |arg| | |
| + ARGV.push arg | |
| +end | |
| + | |
| +$browser_url = "http://#{opts['ServerHost']}:#{opts['ServerPort']}/" | |
| + | |
| +$stderr.puts "" | |
| +$stderr.puts "[*] Starting WarVOX on #{$browser_url}" | |
| +$stderr.puts "" | |
| + | |
| +load(voxserv) | |
| diff --git a/etc/warvox.conf b/etc/warvox.conf | |
| @@ -0,0 +1,28 @@ | |
| +# | |
| +# WarVOX Configuration | |
| +# | |
| + | |
| + | |
| +# | |
| +# Configure the username and password for the WarVOX | |
| +# web interface. This password is sent in clear text | |
| +# | |
| +authentication: | |
| + user: admin | |
| + pass: warvox | |
| + | |
| +# | |
| +# Configure the path to all saved data files | |
| +# This requires ~500M of space per prefix | |
| +# | |
| +data_path: %BASE%/data/ | |
| + | |
| +# | |
| +# Configure filesystem paths to each required tool | |
| +# | |
| +tools: | |
| + gnuplot: gnuplot | |
| + sox: sox | |
| + lame: lame | |
| + iaxrecord: %BASE%/bin/iaxrecord | |
| + | |
| diff --git a/lib/warvox.rb b/lib/warvox.rb | |
| @@ -2,8 +2,17 @@ | |
| # top level include file for warvox libaries | |
| ## | |
| -module WarVOX | |
| -end | |
| - | |
| +# Load components | |
| +require 'warvox/config' | |
| +require 'warvox/jobs' | |
| +require 'warvox/phone' | |
| require 'warvox/audio' | |
| require 'warvox/db' | |
| + | |
| +# Global configuration | |
| +module WarVOX | |
| + VERSION = '1.0.0' | |
| + Base = File.expand_path(File.join(File.dirname(__FILE__), '..')) | |
| + Conf = File.expand_path(File.join(Base, 'etc', 'warvox.conf')) | |
| + JobManager = WarVOX::JobQueue.new | |
| +end | |
| diff --git a/lib/warvox/config.rb b/lib/warvox/config.rb | |
| @@ -0,0 +1,68 @@ | |
| +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.data_path | |
| + info = YAML.load_file(WarVOX::Conf) | |
| + return nil if not info | |
| + return nil if not info['data_path'] | |
| + File.expand_path(info['data_path'].gsub('%BASE%', WarVOX::Base… | |
| + 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… | |
| + if (::File::Stat.new(path)) | |
| + return path | |
| + end | |
| + rescue | |
| + end | |
| + } | |
| + end | |
| + return nil | |
| + end | |
| + | |
| +end | |
| +end | |
| diff --git a/lib/warvox/db.rb b/lib/warvox/db.rb | |
| @@ -137,7 +137,7 @@ class DB < ::Hash | |
| tone << rec | |
| end | |
| - (tone.empty? or tone.length == 1) ? false : tone | |
| + (tone.empty? or (data.length > 5 and tone.length == 1)) ? fals… | |
| end | |
| def find_carriers | |
| diff --git a/lib/warvox/jobs.rb b/lib/warvox/jobs.rb | |
| @@ -0,0 +1,60 @@ | |
| +module WarVOX | |
| +class JobQueue | |
| + attr_accessor :active_job, :active_thread, :queue, :queue_thread | |
| + | |
| + def initialize | |
| + @queue = [] | |
| + @queue_thread = Thread.new{ manage_queue } | |
| + super | |
| + end | |
| + | |
| + # XXX synchronize | |
| + def deschedule(job_id) | |
| + | |
| + if(@active_job and @active_job.name == job_id) | |
| + @active_thread.kill | |
| + @active_job = @active_thread = nil | |
| + end | |
| + | |
| + res = [] | |
| + @queue.each do |j| | |
| + res << j if j.name == job_id | |
| + end | |
| + | |
| + if(res.length > 0) | |
| + res.each {|j| @queue.delete(j) } | |
| + end | |
| + end | |
| + | |
| + def schedule(job) | |
| + @queue.push(job) | |
| + end | |
| + | |
| + def manage_queue | |
| + begin | |
| + while(true) | |
| + if(@active_job and @active_job.status == 'completed') | |
| + @active_job = nil | |
| + @active_thread = nil | |
| + end | |
| + | |
| + if(not @active_job and @queue.length > 0) | |
| + @active_job = @queue.shift | |
| + @active_thread = Thread.new { @active_job.star… | |
| + end | |
| + | |
| + Kernel.select(nil, nil, nil, 1) | |
| + end | |
| + rescue ::Exception | |
| + $stderr.puts "QUEUE MANAGER:#{$!.class} #{$!}" | |
| + $stderr.flush | |
| + end | |
| + end | |
| + | |
| +end | |
| +end | |
| + | |
| + | |
| +require 'warvox/jobs/base' | |
| +require 'warvox/jobs/dialer' | |
| +require 'warvox/jobs/analysis' | |
| diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb | |
| @@ -0,0 +1,178 @@ | |
| +module WarVOX | |
| +module Jobs | |
| +class Analysis < Base | |
| + | |
| + require 'fileutils' | |
| + require 'kissfft' | |
| + | |
| + def type | |
| + 'analysis' | |
| + end | |
| + | |
| + def initialize(job_id) | |
| + @name = job_id | |
| + end | |
| + | |
| + def get_job | |
| + ::DialJob.find(@name) | |
| + end | |
| + | |
| + def start | |
| + @status = 'active' | |
| + | |
| + begin | |
| + start_processing() | |
| + | |
| + model = get_job | |
| + model.processed = true | |
| + model.save | |
| + | |
| + 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) | |
| + todo.each do |r| | |
| + next if r.processed | |
| + next if not r.completed | |
| + next if r.busy | |
| + next if not File.exist?(r.rawfile) | |
| + | |
| + bname = r.rawfile.gsub(/\..*/, '') | |
| + num = r.number | |
| + | |
| + # | |
| + # Create the signature database | |
| + # | |
| + raw = WarVOX::Audio::Raw.from_file(r.rawfile) | |
| + fd = File.new("#{bname}.sig", "wb") | |
| + fd.write raw.to_flow | |
| + fd.close | |
| + | |
| + # | |
| + # 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/80… | |
| + rawfile.flush | |
| + datfile.flush | |
| + | |
| + # Data files for spectrum plotting | |
| + frefile = Tempfile.new("frefile") | |
| + | |
| + # Perform a DFT on the samples | |
| + res = KissFFT.fftr(8192, 8000, 1, raw.samples) | |
| + | |
| + # Calculate the peak frequencies for the sample | |
| + maxf = 0 | |
| + maxp = 0 | |
| + tones = {} | |
| + res.each do |x| | |
| + rank = x.sort{|a,b| a[1].to_i <=> b[1].to_i }.… | |
| + 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 | |
| + | |
| + # Calculate average frequency and peaks over time | |
| + avg = {} | |
| + pks = [] | |
| + res.each do |slot| | |
| + pks << slot.sort{|a,b| a[1] <=> b[1] }.reverse… | |
| + slot.each do |freq| | |
| + avg[ freq[0] ] ||= 0 | |
| + avg[ freq[0] ] += freq[1] | |
| + end | |
| + end | |
| + avg.keys.sort.each do |k| | |
| + avg[k] = avg[k] / res.length | |
| + frefile.write("#{k} #{avg[k]}\n") | |
| + end | |
| + frefile.flush | |
| + | |
| + # XXX: Store all frequency information | |
| + # maxf == peak frequency | |
| + # avg == averages over whole sample | |
| + # pks == peaks over time | |
| + # tones == significant frequencies | |
| + | |
| + # Plot samples to a graph | |
| + plotter = Tempfile.new("gnuplot") | |
| + | |
| + plotter.puts("set ylabel \"Signal\"") | |
| + plotter.puts("set xlabel \"Time\"") | |
| + | |
| + plotter.puts("set terminal png medium size 640,480 tra… | |
| + plotter.puts("set output \"#{bname}_big.png\"") | |
| + plotter.puts("plot \"#{datfile.path}\" using 1:2 title… | |
| + plotter.puts("set output \"#{bname}_big_dots.png\"") | |
| + plotter.puts("plot \"#{datfile.path}\" using 1:2 title… | |
| + | |
| + plotter.puts("set terminal png medium size 640,480 tra… | |
| + plotter.puts("set ylabel \"Power\"") | |
| + plotter.puts("set xlabel \"Frequency\"") | |
| + plotter.puts("set output \"#{bname}_freq_big.png\"") | |
| + plotter.puts("plot \"#{frefile.path}\" using 1:2 title… | |
| + | |
| + plotter.puts("set terminal png small size 160,120 tran… | |
| + plotter.puts("set format x ''") | |
| + plotter.puts("set format y ''") | |
| + plotter.puts("set output \"#{bname}.png\"") | |
| + plotter.puts("plot \"#{datfile.path}\" using 1:2 notit… | |
| + | |
| + plotter.puts("set terminal png small size 160,120 tran… | |
| + 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 notit… | |
| + plotter.flush | |
| + | |
| + system("gnuplot #{plotter.path}") | |
| + File.unlink(plotter.path) | |
| + File.unlink(datfile.path) | |
| + plotter.close | |
| + datfile.close | |
| + | |
| + # Generate a MP3 audio file | |
| + system("sox -s -w -r 8000 -t raw -c 1 #{rawfile.path} … | |
| + system("lame #{bname}.wav #{bname}.mp3 >/dev/null 2>&1… | |
| + File.unlink("#{bname}.wav") | |
| + File.unlink(rawfile.path) | |
| + rawfile.close | |
| + | |
| + # XXX: Dump the frequencies | |
| + | |
| + # Save the changes | |
| + r.processed = true | |
| + r.processed_at = Time.now | |
| + r.save | |
| + end | |
| + end | |
| + | |
| +end | |
| +end | |
| +end | |
| diff --git a/lib/warvox/jobs/base.rb b/lib/warvox/jobs/base.rb | |
| @@ -0,0 +1,24 @@ | |
| +module WarVOX | |
| +module Jobs | |
| +class Base | |
| + attr_accessor :name, :status | |
| + | |
| + def type | |
| + 'base' | |
| + end | |
| + | |
| + def name | |
| + 'noname' | |
| + end | |
| + | |
| + def stop | |
| + @status = 'active' | |
| + end | |
| + | |
| + def start | |
| + @status = 'completed' | |
| + end | |
| +end | |
| +end | |
| +end | |
| + | |
| diff --git a/lib/warvox/jobs/dialer.rb b/lib/warvox/jobs/dialer.rb | |
| @@ -0,0 +1,206 @@ | |
| +module WarVOX | |
| +module Jobs | |
| +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 | |
| + @nums = shuffle_a(WarVOX::Phone.crack_mask(@range)) | |
| + @cid = '8005551212' # XXX: Read from job | |
| + 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 = [] | |
| + | |
| + ::Provider.find(: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 get_job | |
| + ::DialJob.find(@name) | |
| + end | |
| + | |
| + def start | |
| + begin | |
| + | |
| + model = get_job | |
| + model.status = 'active' | |
| + model.started_at = Time.now | |
| + model.save | |
| + | |
| + start_dialing() | |
| + | |
| + stop() | |
| + | |
| + rescue ::Exception => e | |
| + $stderr.puts "Exception in the job queue: #{$e.class} … | |
| + end | |
| + end | |
| + | |
| + def stop | |
| + @status = 'completed' | |
| + model = get_job | |
| + model.status = 'completed' | |
| + model.completed_at = Time.now | |
| + model.save | |
| + end | |
| + | |
| + def start_dialing | |
| + dest = File.join(WarVOX::Config.data_path, "#{@name}-#{@range}… | |
| + FileUtils.mkdir_p(dest) | |
| + | |
| + @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 | |
| + # Execute and read the output | |
| + busy = 0 | |
| + ring = 0 | |
| + fail = 1 | |
| + byte = 0 | |
| + path = '' | |
| + | |
| + IO.popen( | |
| + [ | |
| + WarVOX::Config.tool_pa… | |
| + prov[:host], | |
| + prov[:user], | |
| + prov[:pass], | |
| + @cid, | |
| + out, | |
| + num, | |
| + @seconds | |
| + ].map{|i| | |
| + "'" + i.to_s.gsub("'",… | |
| + }.join(" ")).each_line do |line| | |
| + $stderr.puts "DEBUG: #{line.st… | |
| + if(line =~ /^COMPLETED/) | |
| + line.split(/\s+/).map{… | |
| + busy = info[1]… | |
| + fail = info[1]… | |
| + ring = info[1]… | |
| + byte = info[1]… | |
| + path = info[1]… | |
| + end | |
| + end | |
| + end | |
| + | |
| + res = ::DialResult.new | |
| + res.number = num | |
| + res.dial_job_id = @name | |
| + res.provider_id = prov[:id] | |
| + res.completed = (fail == 0) ? true : f… | |
| + res.busy = (busy == 1) ? true : false | |
| + 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" | |
| + end | |
| + | |
| + @calls << res | |
| + | |
| + rescue ::Exception => e | |
| + $stderr.puts "ERROR: #{e.class… | |
| + end | |
| + end | |
| + | |
| + # END NEW THREAD | |
| + end | |
| + # END SPAWN THREADS | |
| + tasks.map{|t| t.join if t} | |
| + | |
| + # Save data to the database | |
| + begin | |
| + | |
| + # Iterate through the results | |
| + @calls.each do |r| | |
| + tries = 0 | |
| + begin | |
| + r.save | |
| + rescue ::Exception => e | |
| + $stderr.puts "ERROR: #{r.inspe… | |
| + tries += 1 | |
| + Kernel.select(nil, nil, nil, 0… | |
| + retry if tries < 5 | |
| + end | |
| + end | |
| + | |
| + # Update the progress bar | |
| + model = get_job | |
| + model.progress = ((@nums_total - @nums.length)… | |
| + model.save | |
| + | |
| + rescue ::SQLite3::BusyException => e | |
| + $stderr.puts "ERROR: Database lock hit trying … | |
| + retry | |
| + end | |
| + end | |
| + | |
| + # ALL DONE | |
| + end | |
| + | |
| +end | |
| +end | |
| +end | |
| diff --git a/lib/warvox/phone.rb b/lib/warvox/phone.rb | |
| @@ -0,0 +1,32 @@ | |
| +module WarVOX | |
| +class Phone | |
| + | |
| + # Convert 123456XXXX to an array of expanded numbers | |
| + def self.crack_mask(mask) | |
| + res = {} | |
| + | |
| + 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 o… | |
| + j = 0 # index for numstr | |
| + for i in 0..number.length-1 do # step through the numb… | |
| + if number[i].chr =~ /^[X#]$/i | |
| + number[i] = numstr[j] # replaced maske… | |
| + j += 1 | |
| + end | |
| + end | |
| + res[number] = {} | |
| + end | |
| + | |
| + return res.keys.sort | |
| + end | |
| + | |
| +end | |
| +end | |
| diff --git a/src/iaxrecord/iaxrecord.c b/src/iaxrecord/iaxrecord.c | |
| @@ -26,6 +26,7 @@ | |
| int initialized = 0; | |
| int debug = 0; | |
| int busy = 0; | |
| +int fail = 1; | |
| float silence_threshold = 0.0f; | |
| int call_state = 0; | |
| @@ -61,7 +62,9 @@ void usage(char **argv) { | |
| int state_event_callback(struct iaxc_ev_call_state call) { | |
| if(call.state & IAXC_CALL_STATE_BUSY) busy = 1; | |
| + if(call.state & IAXC_CALL_STATE_COMPLETE) fail = 0; | |
| call_state = call.state; | |
| + | |
| /* | |
| fprintf(stdout, "STATE: "); | |
| if(call.state & IAXC_CALL_STATE_FREE) | |
| @@ -109,6 +112,7 @@ int audio_event_callback( struct iaxc_ev_audio audio) { | |
| int iaxc_callback(iaxc_event e) { | |
| switch(e.type) { | |
| case IAXC_EVENT_TEXT: | |
| + // fprintf(stdout, "TEXT: %s\n", e.ev.text.message); | |
| return ( debug ? 0 : 1 ); | |
| break; | |
| case IAXC_EVENT_STATE: | |
| @@ -200,13 +204,17 @@ int main(int argc, char **argv) { | |
| if(iaxc_first_free_call() == call_id) break; | |
| iaxc_millisleep(250); | |
| } | |
| + } else { | |
| + fail = 1; | |
| } | |
| + | |
| if(! etime) time(&etime); | |
| - fprintf(stdout, "COMPLETED %s BYTES=%d FILE=%s BUSY=%d RINGTIME=%d\n", | |
| + fprintf(stdout, "COMPLETED %s BYTES=%d FILE=%s FAIL=%d BUSY=%d RINGTIM… | |
| iax_num, | |
| call_bytes, | |
| iax_out, | |
| + fail, | |
| busy, | |
| (unsigned int)(etime) - (unsigned int)(stime) | |
| ); |