/
asterisk.rb
160 lines (133 loc) · 5.37 KB
/
asterisk.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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 (event['Channel'] && call_for_channel(event['Channel'])) ||
(event['Channel1'] && call_for_channel(event['Channel1'])) ||
(event['Channel2'] && call_for_channel(event['Channel2']))
[event['Channel'], event['Channel1'], event['Channel2']].compact.each do |channel|
call = call_for_channel channel
if call
pb_logger.trace "Found call by channel matching this event. Sending to call #{call.id}"
call.process_ami_event! event
end
end
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)
env = Call.parse_environment event['Env']
return pb_logger.warn "Ignoring AsyncAGI Start event because it is for an 'h' extension" if env[:agi_extension] == 'h'
return pb_logger.warn "Ignoring AsyncAGI Start event because it is for an 'Kill' type" if env[:agi_type] == 'Kill'
pb_logger.trace "Handling AsyncAGI Start event by creating a new call"
call = Call.new event['Channel'], current_actor, env
register_call call
call.send_offer!
end
end
end
end