class Punchblock::Translator::Asterisk

Constants

ChannelGoneError

Indicates that a command was executed against a channel which no longer exists

EVENTS_ALLOWED_BRIDGED
REDIRECT_CONTEXT
REDIRECT_EXTENSION
REDIRECT_PRIORITY

Attributes

ami_client[R]
calls[R]
connection[R]

Public Class Methods

event_filter=(filter) click to toggle source

Set the AMI event filter to be applied to incoming AMI events. A truthy return value will send the event via Rayo to the client (Adhearsion).

@param [#[<RubyAMI::Event>]] filter

@example A lambda

Punchblock::Translator::Asterisk.event_filter = ->(event) { event.name == 'AsyncAGI' }
# File lib/punchblock/translator/asterisk.rb, line 38
def self.event_filter=(filter)
  @event_filter = filter
end
event_passes_filter?(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 42
def self.event_passes_filter?(event)
  @event_filter ? !!@event_filter[event] : true
end
new(ami_client, connection) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 48
def initialize(ami_client, connection)
  @ami_client, @connection = ami_client, connection
  @calls, @components, @channel_to_call_id, @bridges = {}, {}, {}, {}
end

Public Instance Methods

actor_died(actor, reason) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 196
def actor_died(actor, reason)
  return unless reason
  if id = @calls.key(actor)
    @calls.delete id
    end_event = Punchblock::Event::End.new :target_call_id  => id,
                                           :reason          => :error
    handle_pb_event end_event
  end
end
call_for_channel(channel) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 67
def call_for_channel(channel)
  call_with_id @channel_to_call_id[Channel.new(channel).name]
end
call_with_id(call_id) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 63
def call_with_id(call_id)
  @calls[call_id]
end
check_recording_directory() click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 192
def check_recording_directory
  pb_logger.warn "Recordings directory #{Component::Record::RECORDING_BASE_PATH} does not exist. Recording might not work. This warning can be ignored if Adhearsion is running on a separate machine than Asterisk. See http://adhearsion.com/docs/call-controllers#recording" unless File.exists?(Component::Record::RECORDING_BASE_PATH)
end
component_with_id(component_id) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 79
def component_with_id(component_id)
  @components[component_id]
end
deregister_call(id, channel) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 58
def deregister_call(id, channel)
  @channel_to_call_id.delete channel
  @calls.delete id
end
deregister_component(id) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 75
def deregister_component(id)
  @components.delete id
end
execute_call_command(command) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 142
def execute_call_command(command)
  if call = call_with_id(command.target_call_id)
    begin
      call.execute_command command
    rescue => e
      deregister_call call.id, call.channel
    end
  else
    command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{command.target_call_id}", command.target_call_id
  end
end
execute_command(command, options = {}) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 127
def execute_command(command, options = {})
  command.request!

  command.target_call_id ||= options[:call_id]
  command.component_id ||= options[:component_id]

  if command.target_call_id
    execute_call_command command
  elsif command.component_id
    execute_component_command command
  else
    execute_global_command command
  end
end
execute_component_command(command) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 154
def execute_component_command(command)
  if (component = component_with_id(command.component_id))
    component.execute_command command
  else
    command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id}", command.target_call_id, command.component_id
  end
end
execute_global_command(command) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 162
def execute_global_command(command)
  case command
  when Punchblock::Component::Asterisk::AMI::Action
    component = Component::Asterisk::AMIAction.new command, current_actor, ami_client
    register_component component
    component.execute
  when Punchblock::Command::Dial
    if call = call_with_id(command.uri)
      command.response = ProtocolError.new.setup(:conflict, 'Call ID already in use')
    else
      call = Call.new command.to, current_actor, ami_client, connection, nil, command.uri
      register_call call
      call.dial command
    end
  else
    command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command"
  end
end
handle_ami_event(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 83
def handle_ami_event(event)
  return unless event.is_a? RubyAMI::Event

  case event.name
  when 'FullyBooted'
    handle_pb_event Connection::Connected.new
    run_at_fully_booted
    return
  when 'BridgeEnter'
    if other_channel = @bridges.delete(event['BridgeUniqueid'])
      if event['OtherCall'] = call_for_channel(other_channel)
        join_command = event['OtherCall'].pending_joins.delete event['Channel']
        call = call_for_channel(event['Channel'])
        join_command ||= call.pending_joins.delete other_channel if call
        join_command.response = true if join_command
      end
    else
      @bridges[event['BridgeUniqueid']] = event['Channel']
    end
  when 'BridgeLeave'
    if other_channel = @bridges.delete(event['BridgeUniqueid'] + '_leave')
      event['OtherCall'] = call_for_channel(other_channel)
    else
      @bridges[event['BridgeUniqueid'] + '_leave'] = event['Channel']
    end
  end

  handle_varset_ami_event event

  ami_dispatch_to_or_create_call event
  if !ami_event_known_call?(event) && self.class.event_passes_filter?(event)
    handle_pb_event Event::Asterisk::AMI::Event.new(name: event.name, headers: event.headers)
  end
end
handle_pb_event(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 118
def handle_pb_event(event)
  connection.handle_event event
end
register_call(call) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 53
def register_call(call)
  @channel_to_call_id[call.channel] = call.id
  @calls[call.id] ||= call
end
register_component(component) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 71
def register_component(component)
  @components[component.id] ||= component
end
run_at_fully_booted() click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 181
def run_at_fully_booted
  send_ami_action 'Command', 'Command' => "dialplan add extension #{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY},AGI,agi:async into #{REDIRECT_CONTEXT}"

  result = send_ami_action 'Command', 'Command' => "dialplan show #{REDIRECT_CONTEXT}"
  if result.text_body =~ /failed/
    pb_logger.error "Punchblock failed to add the #{REDIRECT_EXTENSION} extension to the #{REDIRECT_CONTEXT} context. Please add a [#{REDIRECT_CONTEXT}] entry to your dialplan."
  end

  check_recording_directory
end
send_message(call_id, domain, body, options = {}) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 122
def send_message(call_id, domain, body, options = {})
  call = call_with_id call_id
  call.send_message body if call
end

Private Instance Methods

ami_dispatch_to_or_create_call(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 220
def ami_dispatch_to_or_create_call(event)
  calls_for_event = channels_for_ami_event(event).inject({}) do |h, channel|
    call = call_for_channel channel
    h[channel] = call if call
    h
  end

  if !calls_for_event.empty?
    calls_for_event.each_pair do |channel, call|
      next if channel.bridged? && !EVENTS_ALLOWED_BRIDGED.include?(event.name)
      call.process_ami_event event
    end
  elsif event.name == "AsyncAGIStart" || (event.name == "AsyncAGI" && event['SubEvent'] == "Start")
    handle_async_agi_start_event event
  end
end
ami_event_known_call?(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 241
def ami_event_known_call?(event)
  (event['Channel'] && call_for_channel(event['Channel'])) ||
    (event['Channel1'] && call_for_channel(event['Channel1'])) ||
    (event['Channel2'] && call_for_channel(event['Channel2']))
end
channels_for_ami_event(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 237
def channels_for_ami_event(event)
  [event['Channel'], event['Channel1'], event['Channel2']].compact.map { |channel| Channel.new(channel) }
end
handle_async_agi_start_event(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 247
def handle_async_agi_start_event(event)
  env = RubyAMI::AsyncAGIEnvironmentParser.new(event['Env']).to_hash

  return if env[:agi_extension] == 'h' || env[:agi_type] == 'Kill'

  call = Call.new event['Channel'], current_actor, ami_client, connection, env
  register_call call
  call.send_offer
end
handle_varset_ami_event(event) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 212
def handle_varset_ami_event(event)
  return unless event.name == 'VarSet' && event['Variable'] == 'punchblock_call_id' && (call = call_with_id event['Value'])

  @channel_to_call_id.delete call.channel
  call.channel = event['Channel']
  register_call call
end
send_ami_action(name, headers = {}) click to toggle source
# File lib/punchblock/translator/asterisk.rb, line 208
def send_ami_action(name, headers = {})
  ami_client.send_action name, headers
end