lib/punchblock/translator/freeswitch/call.rb
# encoding: utf-8
module Punchblock
module Translator
class Freeswitch
class Call
include HasGuardedHandlers
include Celluloid
include DeadActorSafety
extend ActorHasGuardedHandlers
execute_guarded_handlers_on_receiver
HANGUP_CAUSE_TO_END_REASON = Hash.new :error
HANGUP_CAUSE_TO_END_REASON['USER_BUSY'] = :busy
HANGUP_CAUSE_TO_END_REASON['MANAGER_REQUEST'] = :hangup_command
%w{
NORMAL_CLEARING ORIGINATOR_CANCEL SYSTEM_SHUTDOWN
BLIND_TRANSFER ATTENDED_TRANSFER PICKED_OFF NORMAL_UNSPECIFIED
}.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :hangup }
%w{
NO_USER_RESPONSE NO_ANSWER SUBSCRIBER_ABSENT ALLOTTED_TIMEOUT
MEDIA_TIMEOUT PROGRESS_TIMEOUT
}.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :timeout }
%w{CALL_REJECTED NUMBER_CHANGED
REDIRECTION_TO_NEW_DESTINATION FACILITY_REJECTED NORMAL_CIRCUIT_CONGESTION
SWITCH_CONGESTION USER_NOT_REGISTERED FACILITY_NOT_SUBSCRIBED
OUTGOING_CALL_BARRED INCOMING_CALL_BARRED BEARERCAPABILITY_NOTAUTH
BEARERCAPABILITY_NOTAVAIL SERVICE_UNAVAILABLE BEARERCAPABILITY_NOTIMPL
CHAN_NOT_IMPLEMENTED FACILITY_NOT_IMPLEMENTED SERVICE_NOT_IMPLEMENTED
}.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :reject }
REJECT_TO_HANGUP_REASON = Hash.new 'NORMAL_TEMPORARY_FAILURE'
REJECT_TO_HANGUP_REASON.merge! :busy => 'USER_BUSY', :decline => 'CALL_REJECTED'
attr_reader :id, :translator, :es_env, :direction, :stream
trap_exit :actor_died
def initialize(id, translator, es_env = nil, stream = nil)
@id, @translator, @stream = id, translator, stream
@es_env = es_env || {}
@components = {}
@pending_joins, @pending_unjoins = {}, {}
@answered = false
setup_handlers
end
def register_component(component)
@components[component.id] ||= component
end
def component_with_id(component_id)
@components[component_id]
end
def send_offer
@direction = :inbound
send_pb_event offer_event
end
def to_s
"#<#{self.class}:#{id}>"
end
alias :inspect :to_s
def setup_handlers
register_handler :es, :event_name => 'CHANNEL_ANSWER' do
@answered = true
send_pb_event Event::Answered.new
throw :pass
end
register_handler :es, :event_name => 'CHANNEL_STATE', [:[], :channel_call_state] => 'RINGING' do
send_pb_event Event::Ringing.new
end
register_handler :es, :event_name => 'CHANNEL_HANGUP' do |event|
@components.dup.each_pair do |id, component|
safe_from_dead_actors do
component.call_ended if component.alive?
end
end
send_end_event HANGUP_CAUSE_TO_END_REASON[event[:hangup_cause]]
end
register_handler :es, :event_name => 'CHANNEL_BRIDGE' do |event|
command = @pending_joins[event[:other_leg_unique_id]]
command.response = true if command
other_call_uri = event[:unique_id] == id ? event[:other_leg_unique_id] : event[:unique_id]
send_pb_event Event::Joined.new(:call_uri => other_call_uri)
end
register_handler :es, :event_name => 'CHANNEL_UNBRIDGE' do |event|
command = @pending_unjoins[event[:other_leg_unique_id]]
command.response = true if command
other_call_uri = event[:unique_id] == id ? event[:other_leg_unique_id] : event[:unique_id]
send_pb_event Event::Unjoined.new(:call_uri => other_call_uri)
end
register_handler :es, [:has_key?, :scope_variable_punchblock_component_id] => true do |event|
if component = component_with_id(event[:scope_variable_punchblock_component_id])
safe_from_dead_actors { component.handle_es_event event if component.alive? }
end
throw :pass
end
end
def handle_es_event(event)
trigger_handler :es, event
end
def application(*args)
stream.application id, *args
end
def sendmsg(*args)
stream.sendmsg id, *args
end
def uuid_foo(app, args = '')
stream.bgapi "uuid_#{app} #{id} #{args}"
end
def dial(dial_command)
@direction = :outbound
cid_number, cid_name = dial_command.from, nil
if dial_command.from
dial_command.from.match(/(?<cid_name>.*)<(?<cid_number>.*)>/) do |m|
cid_name = m[:cid_name].strip
cid_number = m[:cid_number]
end
end
options = {
:return_ring_ready => true,
:origination_uuid => id
}
options[:origination_caller_id_number] = "'#{cid_number}'" if cid_number.present?
options[:origination_caller_id_name] = "'#{cid_name}'" if cid_name.present?
options[:originate_timeout] = dial_command.timeout/1000 if dial_command.timeout
dial_command.headers.each do |name, value|
options["sip_h_#{name}"] = "'#{value}'"
end
opts = options.inject([]) do |a, (k, v)|
a << "#{k}=#{v}"
end.join(',')
stream.bgapi "originate {#{opts}}#{dial_command.to} &park()"
dial_command.response = Ref.new uri: id
end
def outbound?
direction == :outbound
end
def inbound?
direction == :inbound
end
def answered?
@answered
end
def execute_command(command)
if command.component_id
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} for call #{id}", id, command.component_id
end
end
case command
when Command::Accept
application 'respond', '180 Ringing'
command.response = true
when Command::Answer
if answered?
command.response = true
else
command_id = Punchblock.new_uuid
register_tmp_handler :es, :event_name => 'CHANNEL_ANSWER', [:[], :scope_variable_punchblock_command_id] => command_id do
command.response = true
end
application 'answer', "%[punchblock_command_id=#{command_id}]"
end
when Command::Hangup
hangup
command.response = true
when Command::Join
@pending_joins[command.call_uri] = command
uuid_foo :bridge, command.call_uri
when Command::Unjoin
@pending_unjoins[command.call_uri] = command
uuid_foo :transfer, '-both park inline'
when Command::Reject
hangup REJECT_TO_HANGUP_REASON[command.reason]
command.response = true
when Punchblock::Component::Output
media_renderer = command.renderer || :freeswitch
case media_renderer.to_s
when 'freeswitch', 'native'
execute_component Component::Output, command
when 'flite'
execute_component Component::FliteOutput, command
else
execute_component Component::TTSOutput, command
end
when Punchblock::Component::Input
execute_component Component::Input, command
when Punchblock::Component::Record
execute_component Component::Record, command
else
command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
end
end
def hangup(reason = 'MANAGER_REQUEST')
sendmsg :call_command => 'hangup', :hangup_cause => reason
end
def logger_id
"#{self.class}: #{id}"
end
def actor_died(actor, reason)
return unless reason
pb_logger.error "A linked actor (#{actor.inspect}) died due to #{reason.inspect}"
if id = @components.key(actor)
@components.delete id
complete_event = Punchblock::Event::Complete.new :component_id => id, source_uri: id, :reason => Punchblock::Event::Complete::Error.new
send_pb_event complete_event
end
end
private
def send_end_event(reason)
send_pb_event Event::End.new(:reason => reason)
translator.deregister_call id
terminate
end
def execute_component(type, command, *execute_args)
type.new_link(command, current_actor).tap do |component|
register_component component
component.execute(*execute_args)
end
end
def send_pb_event(event)
event.target_call_id = id
translator.handle_pb_event event
end
def offer_event
Event::Offer.new :to => es_env[:variable_sip_to_uri],
:from => "#{es_env[:variable_effective_caller_id_name]} <#{es_env[:variable_sip_from_uri]}>",
:headers => headers
end
def headers
es_env.to_a.inject({}) do |accumulator, element|
accumulator['X-' + element[0].to_s] = element[1] || ''
accumulator
end
end
end
end
end
end