require 'md5' require 'thread' module AGI class Error < Exception; end class Hangup < Exception; end class Digit < Exception; end class Timeout < Exception; end ENV = {} @sequence = 0 @digit_queue = [] @stdin_mutex = Mutex.new @stdin_queue = Queue.new @stdout_mutex = Mutex.new def self.flush_digits @digit_queue = [] end def self.queue_digit(digit) digit=digit[0] if String === digit @digit_queue << digit unless digit == 0 end def self.cmd_digit(command) if @digit_queue.empty? cmd(command) else $stderr.puts "Unqueued digit" ["",@digit_queue.shift] end end def self.wait_for_digits_thread Thread.new do loop do @stdout_mutex.synchronize do $stdout.puts("WAIT FOR DIGIT -1") end @stdin_mutex.synchronize do begin response = $stdin.gets rescue Errno::EPIPE response = nil end @stdin_queue << response end end end end def self.read_input if @stdin_queue.length > 0 then result = @stdin_queue.pop(true) else result = $stdin.gets end #$stderr.puts "Read #{result.inspect}" result end def self.cmd(command) $stderr.puts("Command #{command.inspect}") begin @sequence += 1 @stdout_mutex.synchronize do # This will wake up the thread, and the response # will get queued $stdout.puts("SET VARIABLE ruby_agi_seq #{@sequence}") $stdout.puts("GET VARIABLE ruby_agi_seq") @stdin_mutex.synchronize do # Read until we get the response for our sequence number while response = read_input case response when /^HANGUP/, nil raise Hangup when /^200 result=1 \(#{@sequence}\)/ break when /^200 result=(-?\d+)/ # May be a potential digit digit = $1.to_i case digit when (?0..?9), ?#, ?*, (?A..?D) $stderr.puts("Queued digit #{digit}") @digit_queue << digit end end end raise Hangup unless response $stdout.puts(command) response = $stdin.gets case response when /^HANGUP/, nil raise Hangup when /^200 result=(-?\d+)/ return [response, $1.to_i] else raise Error, response.inspect + " " + command.inspect end end end rescue Errno::EPIPE raise Hangup end end def self.say(text,digits=nil,options={}) if @digit_queue.length > 0 then return ["",@digit_queue.shift] end name = "ruby-agi-say-#{MD5.new(text)}" tf = "/sw/asterisk/var/lib/asterisk/sounds/#{name}.wav" unless File.exists?(tf) IO.popen("text2wave -F 8000 -scale 2 -o #{tf} /dev/stdin >/dev/null 2>&1","w") do |fp| fp.puts(text) end end begin digits = "0123456789" if digits.nil? digits = '""' if digits == "" response, digit = cmd_digit("STREAM FILE #{name} #{digits}") if options[:raise_digit] then raise Digit,digit end return [response, digit] ensure #File.unlink(tf) unless options[:static] end end def self.queue_say(text,digits=nil,options={}) @say_queue << [text,digits,options] end def self.map_digit(digit) case digit when 0 raise Timeout when -1 "failure" else digit.chr end end def self.goto(obj,selector) @obj = obj @selector = selector end def self.hangup self.cmd("HANGUP") end def self.wait_for_digit(where="unspec") while args = @say_queue.shift response,digit = self.say(*args) $stderr.puts "Response is #{digit.inspect} to #{where}" if digit > 0 then return digit end end response, digit = self.cmd_digit("WAIT FOR DIGIT #{(@obj.timeout*1000).to_i}") raise Timeout if digit == 0 $stderr.puts "Response is #{digit.inspect} to #{where}" return digit end def self.run(obj,selector=:start) @obj = obj @selector = "exten_#{selector}" while @selector @say_queue = [] $stderr.puts "Entering selector #{@selector}" selector = @selector @selector = nil @obj.__send__(selector) next if @selector begin digit = wait_for_digit("run") $stderr.puts "Got digit #{digit.inspect}" target = "exten_#{map_digit(digit)}" rescue Timeout target = "exten_timeout" end if @obj.respond_to?(target) then @selector = target else @selector = "exten_invalid" end $stderr.puts "Selector is now #{@selector}" end end class Context def initialize @timeout = 4 end attr_accessor :timeout def exten_invalid exten_hangup end def exten_timeout exten_hangup end def exten_hangup AGI.hangup exit end def goto(exten) AGI.goto(self,"exten_#{exten}") end end def self.initialize #$stderr.puts "Reading env lines" while line = $stdin.gets break if line.length<=1 if line =~ /^agi_(\w+):\s+(.*)$/ then AGI::ENV[$1] = $2 end end $stdout.sync = true end end