require 'celluloid' require 'ruby_ami' module Punchblock module Translator class Asterisk include Celluloid extend ActiveSupport::Autoload autoload :Call autoload :Component attr_reader :ami_client, :connection, :media_engine, :calls def initialize(ami_client, connection, media_engine = nil) pb_logger.debug "Starting up..." @ami_client, @connection, @media_engine = ami_client, connection, media_engine @calls, @components, @channel_to_call_id = {}, {}, {} @fully_booted_count = 0 end def register_call(call) @channel_to_call_id[call.channel] = call.id @calls[call.id] ||= call end def call_with_id(call_id) @calls[call_id] end def call_for_channel(channel) call = call_with_id @channel_to_call_id[channel] pb_logger.trace "Looking up call for channel #{channel} from #{@channel_to_call_id}. Found: #{call || 'none'}" call end def register_component(component) @components[component.id] ||= component end def component_with_id(component_id) @components[component_id] end def shutdown pb_logger.debug "Shutting down" @calls.values.each &:shutdown! current_actor.terminate! end def handle_ami_event(event) return unless event.is_a? RubyAMI::Event pb_logger.trace "Handling AMI event #{event.inspect}" if event.name.downcase == "fullybooted" pb_logger.trace "Counting FullyBooted event" @fully_booted_count += 1 if @fully_booted_count >= 2 handle_pb_event Connection::Connected.new @fully_booted_count = 0 end return end if event.name == 'VarSet' && event['Variable'] == 'punchblock_call_id' && (call = call_with_id event['Value']) pb_logger.trace "Received a VarSet event indicating the full channel for call #{call}" @channel_to_call_id.delete call.channel pb_logger.trace "Removed call with old channel from channel map: #{@channel_to_call_id}" call.channel = event['Channel'] register_call call end if call = call_for_channel(event['Channel']) pb_logger.trace "Found call by channel matching this event. Sending to call #{call.id}" call.process_ami_event! event elsif event.name.downcase == "asyncagi" && event['SubEvent'] == "Start" handle_async_agi_start_event event end handle_pb_event Event::Asterisk::AMI::Event.new(:name => event.name, :attributes => event.headers) end def handle_pb_event(event) connection.handle_event event end def execute_command(command, options = {}) pb_logger.debug "Executing command #{command.inspect}" command.request! command.call_id ||= options[:call_id] command.component_id ||= options[:component_id] if command.call_id execute_call_command command elsif command.component_id execute_component_command command else execute_global_command command end end def execute_call_command(command) if call = call_with_id(command.call_id) call.execute_command! command else command.response = ProtocolError.new 'call-not-found', "Could not find a call with ID #{command.call_id}", command.call_id end end def execute_component_command(command) if (component = component_with_id(command.component_id)) component.execute_command! command else command.response = ProtocolError.new 'component-not-found', "Could not find a component with ID #{command.component_id}", command.call_id, command.component_id end end def execute_global_command(command) case command when Punchblock::Component::Asterisk::AMI::Action component = Component::Asterisk::AMIAction.new command, current_actor register_component component component.execute! when Punchblock::Command::Dial call = Call.new command.to, current_actor register_call call call.dial! command else command.response = ProtocolError.new 'command-not-acceptable', "Did not understand command" end end def send_ami_action(name, headers = {}, &block) ami_client.send_action name, headers, &block end private def handle_async_agi_start_event(event) call = Call.new event['Channel'], current_actor, event['Env'] register_call call call.send_offer! end end end end