punchblock 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +3 -3
- data/CHANGELOG.md +23 -0
- data/lib/punchblock.rb +24 -0
- data/lib/punchblock/command/reject.rb +10 -2
- data/lib/punchblock/component/record.rb +16 -0
- data/lib/punchblock/core_ext/blather/stanza.rb +3 -1
- data/lib/punchblock/dead_actor_safety.rb +9 -0
- data/lib/punchblock/event/complete.rb +9 -11
- data/lib/punchblock/rayo_node.rb +4 -0
- data/lib/punchblock/translator/asterisk.rb +65 -22
- data/lib/punchblock/translator/asterisk/call.rb +49 -30
- data/lib/punchblock/translator/asterisk/component.rb +6 -8
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +13 -20
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/input.rb +3 -6
- data/lib/punchblock/translator/asterisk/component/output.rb +40 -45
- data/lib/punchblock/translator/asterisk/component/record.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +5 -2
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +5 -5
- data/spec/punchblock/command/reject_spec.rb +7 -1
- data/spec/punchblock/command_node_spec.rb +5 -2
- data/spec/punchblock/component/component_node_spec.rb +4 -0
- data/spec/punchblock/component/output_spec.rb +1 -1
- data/spec/punchblock/component/record_spec.rb +30 -0
- data/spec/punchblock/event/complete_spec.rb +10 -0
- data/spec/punchblock/translator/asterisk/call_spec.rb +191 -48
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +6 -39
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +3 -3
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +8 -3
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +153 -46
- data/spec/punchblock/translator/asterisk/component/record_spec.rb +6 -5
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +1 -2
- data/spec/punchblock/translator/asterisk/component_spec.rb +1 -0
- data/spec/punchblock/translator/asterisk_spec.rb +147 -12
- data/spec/punchblock_spec.rb +34 -0
- data/spec/spec_helper.rb +5 -1
- metadata +30 -20
data/.travis.yml
CHANGED
|
@@ -2,8 +2,8 @@ language: ruby
|
|
|
2
2
|
rvm:
|
|
3
3
|
- 1.9.2
|
|
4
4
|
- 1.9.3
|
|
5
|
-
- jruby-19mode
|
|
6
|
-
- rbx-19mode
|
|
7
|
-
- ruby-head
|
|
5
|
+
# - jruby-19mode
|
|
6
|
+
# - rbx-19mode
|
|
7
|
+
# - ruby-head
|
|
8
8
|
notifications:
|
|
9
9
|
irc: "irc.freenode.org#adhearsion"
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# [develop](https://github.com/adhearsion/punchblock)
|
|
2
2
|
|
|
3
|
+
# [v1.3.0](https://github.com/adhearsion/punchblock/compare/v1.2.0...v1.3.0) - [2012-07-22](https://rubygems.org/gems/punchblock/versions/1.3.0)
|
|
4
|
+
* Change: Asterisk output now uses Playback rather than STREAM FILE
|
|
5
|
+
* Feature: The recordings dir is now checked for existence on startup, and logs an error if it is not there. Asterisk only.
|
|
6
|
+
* Feature: Punchblock now logs an error if it was unable to add the redirect context on Asterisk on startup.
|
|
7
|
+
* Feature: Output component now exposes #recording and #recording_uri for easy access to record results.
|
|
8
|
+
* Feature: Early media support for Asterisk, using Progress to start an early media session
|
|
9
|
+
* Feature: Output component on Asterisk now supports early media. If the line is not answered, it runs Progress followed by Playback with noanswer.
|
|
10
|
+
* Feature: Record component on Asterisk now raises if called on an unanswered call
|
|
11
|
+
* Feature: Input component on Asterisk works the same whether the call is answered or unanswered
|
|
12
|
+
* Feature: AMI events are emitted to the relevant calls
|
|
13
|
+
* Feature: Simpler method of getting hold of a new client/connection
|
|
14
|
+
* Bugfix: AMI events are processed in order by the translator
|
|
15
|
+
* Bugfix: Asterisk calls and components are removed from registries when they die
|
|
16
|
+
* Bugfix: Commands for unknown calls/components respond with the correct `:item_not_found` name
|
|
17
|
+
* Bugfix: AMI events relevant to a particular call are emitted by that call to the client
|
|
18
|
+
* Bugfix: Asterisk calls send an error complete event for their dying components
|
|
19
|
+
* Bugfix: Asterisk translator sends an error end event for its dying calls
|
|
20
|
+
* Bugfix: Use the primitive version of AGI ANSWER, rather than an app
|
|
21
|
+
* Bugfix: Outbound calls which never begin progress on Asterisk end with an error
|
|
22
|
+
* Bugfix: Asterisk now responds correctly to unjoin commands
|
|
23
|
+
* Bugfix: Allow nil reject reasons
|
|
24
|
+
* Bugfix: Asterisk translator now does NOT answer the call automatically when Output, Input or Record are used.
|
|
25
|
+
|
|
3
26
|
# [v1.2.0](https://github.com/adhearsion/punchblock/compare/v1.1.0...v1.2.0) - [2012-04-29](https://rubygems.org/gems/punchblock/versions/1.2.0)
|
|
4
27
|
* Feature: Basic support for record component on Asterisk, using MixMonitor. Currently unsupported options include: start_paused, initial_timeout, final_timeout. Hints are additionally not supported, and recordings are stored on the * machine's local filesystem.
|
|
5
28
|
|
data/lib/punchblock.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Punchblock
|
|
|
18
18
|
autoload :CommandNode
|
|
19
19
|
autoload :Component
|
|
20
20
|
autoload :Connection
|
|
21
|
+
autoload :DeadActorSafety
|
|
21
22
|
autoload :DisconnectedError
|
|
22
23
|
autoload :HasHeaders
|
|
23
24
|
autoload :Header
|
|
@@ -39,6 +40,29 @@ module Punchblock
|
|
|
39
40
|
def reset_logger
|
|
40
41
|
@logger = NullObject.new
|
|
41
42
|
end
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# Get a new Punchblock client with a connection attached
|
|
46
|
+
#
|
|
47
|
+
# @param [Symbol] type the connection type (eg :XMPP, :asterisk)
|
|
48
|
+
# @param [Hash] options the options to pass to the connection (credentials, etc
|
|
49
|
+
#
|
|
50
|
+
# @return [Punchblock::Client] a punchblock client object
|
|
51
|
+
#
|
|
52
|
+
def client_with_connection(type, options)
|
|
53
|
+
connection = Connection.const_get(type.to_s.classify).new options
|
|
54
|
+
Client.new :connection => connection
|
|
55
|
+
rescue NameError
|
|
56
|
+
raise ArgumentError, "Connection type #{type.inspect} is not valid."
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def new_uuid
|
|
60
|
+
SecureRandom.uuid
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def jruby?
|
|
64
|
+
@jruby ||= !!(RUBY_PLATFORM =~ /java/)
|
|
65
|
+
end
|
|
42
66
|
end
|
|
43
67
|
|
|
44
68
|
##
|
|
@@ -41,7 +41,8 @@ module Punchblock
|
|
|
41
41
|
# @return [Symbol] the reason type for rejecting a call
|
|
42
42
|
#
|
|
43
43
|
def reason
|
|
44
|
-
|
|
44
|
+
node = reason_node
|
|
45
|
+
node ? node.name.to_sym : nil
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
##
|
|
@@ -56,12 +57,19 @@ module Punchblock
|
|
|
56
57
|
raise ArgumentError, "Invalid Reason (#{reject_reason}), use: #{VALID_REASONS*' '}"
|
|
57
58
|
end
|
|
58
59
|
children.each(&:remove)
|
|
59
|
-
self << RayoNode.new(reject_reason)
|
|
60
|
+
self << RayoNode.new(reject_reason) if reject_reason
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
def inspect_attributes # :nodoc:
|
|
63
64
|
[:reason] + super
|
|
64
65
|
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def reason_node
|
|
70
|
+
node_children = children.select { |c| [Nokogiri::XML::Element, Niceogiri::XML::Node].any? { |k| c.is_a?(k) } }
|
|
71
|
+
node_children.first
|
|
72
|
+
end
|
|
65
73
|
end # Reject
|
|
66
74
|
end # Command
|
|
67
75
|
end # Punchblock
|
|
@@ -188,6 +188,22 @@ module Punchblock
|
|
|
188
188
|
end
|
|
189
189
|
end
|
|
190
190
|
|
|
191
|
+
##
|
|
192
|
+
# Directly returns the recording for the component
|
|
193
|
+
# @return [Punchblock::Component::Record::Recording] The recording object
|
|
194
|
+
#
|
|
195
|
+
def recording
|
|
196
|
+
complete_event.recording
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
##
|
|
200
|
+
# Directly returns the recording URI for the component
|
|
201
|
+
# @return [String] The recording URI
|
|
202
|
+
#
|
|
203
|
+
def recording_uri
|
|
204
|
+
recording.uri
|
|
205
|
+
end
|
|
206
|
+
|
|
191
207
|
class Pause < CommandNode # :nodoc:
|
|
192
208
|
register :pause, :record
|
|
193
209
|
end
|
|
@@ -7,8 +7,10 @@ module Blather
|
|
|
7
7
|
# representing the Rayo command/event contained within the stanza
|
|
8
8
|
#
|
|
9
9
|
def rayo_node
|
|
10
|
-
first_child =
|
|
10
|
+
first_child = at_xpath '*'
|
|
11
11
|
Punchblock::RayoNode.import first_child, nil, component_id if first_child
|
|
12
|
+
rescue Punchblock::RayoNode::InvalidNodeError
|
|
13
|
+
nil
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
##
|
|
@@ -10,12 +10,11 @@ module Punchblock
|
|
|
10
10
|
register :complete, :ext
|
|
11
11
|
|
|
12
12
|
def reason
|
|
13
|
-
element = find_first
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
13
|
+
element = find_first '*'
|
|
14
|
+
return unless element
|
|
15
|
+
RayoNode.import(element).tap do |reason|
|
|
16
|
+
reason.target_call_id = target_call_id
|
|
17
|
+
reason.component_id = component_id
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
|
|
@@ -26,11 +25,10 @@ module Punchblock
|
|
|
26
25
|
|
|
27
26
|
def recording
|
|
28
27
|
element = find_first('//ns:recording', :ns => RAYO_NAMESPACES[:record_complete])
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
28
|
+
return unless element
|
|
29
|
+
RayoNode.import(element).tap do |recording|
|
|
30
|
+
recording.target_call_id = target_call_id
|
|
31
|
+
recording.component_id = component_id
|
|
34
32
|
end
|
|
35
33
|
end
|
|
36
34
|
|
data/lib/punchblock/rayo_node.rb
CHANGED
|
@@ -5,6 +5,8 @@ require 'niceogiri'
|
|
|
5
5
|
|
|
6
6
|
module Punchblock
|
|
7
7
|
class RayoNode < Niceogiri::XML::Node
|
|
8
|
+
InvalidNodeError = Class.new Punchblock::Error
|
|
9
|
+
|
|
8
10
|
@@registrations = {}
|
|
9
11
|
|
|
10
12
|
class_attribute :registered_ns, :registered_name
|
|
@@ -41,6 +43,7 @@ module Punchblock
|
|
|
41
43
|
# @param [XML::Node] node the node to import
|
|
42
44
|
# @return the appropriate object based on the node name and namespace
|
|
43
45
|
def self.import(node, call_id = nil, component_id = nil)
|
|
46
|
+
node = Nokogiri::XML(node.to_xml).root if Punchblock.jruby?
|
|
44
47
|
ns = (node.namespace.href if node.namespace)
|
|
45
48
|
klass = class_from_registration(node.element_name, ns)
|
|
46
49
|
if klass && klass != self
|
|
@@ -60,6 +63,7 @@ module Punchblock
|
|
|
60
63
|
# not provided one will be created
|
|
61
64
|
# @return a new object with the registered name and namespace
|
|
62
65
|
def self.new(name = registered_name, doc = nil)
|
|
66
|
+
raise InvalidNodeError, "Trying to create a new #{self} with no name" unless name
|
|
63
67
|
super name, doc, registered_ns
|
|
64
68
|
end
|
|
65
69
|
|
|
@@ -19,6 +19,8 @@ module Punchblock
|
|
|
19
19
|
REDIRECT_EXTENSION = '1'
|
|
20
20
|
REDIRECT_PRIORITY = '1'
|
|
21
21
|
|
|
22
|
+
trap_exit :actor_died
|
|
23
|
+
|
|
22
24
|
def initialize(ami_client, connection, media_engine = nil)
|
|
23
25
|
pb_logger.debug "Starting up..."
|
|
24
26
|
@ami_client, @connection, @media_engine = ami_client, connection, media_engine
|
|
@@ -31,6 +33,11 @@ module Punchblock
|
|
|
31
33
|
@calls[call.id] ||= call
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
def deregister_call(call)
|
|
37
|
+
@channel_to_call_id.delete call.channel
|
|
38
|
+
@calls.delete call.id
|
|
39
|
+
end
|
|
40
|
+
|
|
34
41
|
def call_with_id(call_id)
|
|
35
42
|
@calls[call_id]
|
|
36
43
|
end
|
|
@@ -54,24 +61,28 @@ module Punchblock
|
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
def handle_ami_event(event)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
exclusive do
|
|
65
|
+
return unless event.is_a? RubyAMI::Event
|
|
66
|
+
|
|
67
|
+
if event.name.downcase == "fullybooted"
|
|
68
|
+
pb_logger.trace "Counting FullyBooted event"
|
|
69
|
+
@fully_booted_count += 1
|
|
70
|
+
if @fully_booted_count >= 2
|
|
71
|
+
handle_pb_event Connection::Connected.new
|
|
72
|
+
@fully_booted_count = 0
|
|
73
|
+
run_at_fully_booted
|
|
74
|
+
end
|
|
75
|
+
return
|
|
66
76
|
end
|
|
67
|
-
return
|
|
68
|
-
end
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
handle_varset_ami_event event
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
ami_dispatch_to_or_create_call event
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
unless ami_event_known_call?(event)
|
|
83
|
+
handle_pb_event Event::Asterisk::AMI::Event.new(:name => event.name, :attributes => event.headers)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
75
86
|
end
|
|
76
87
|
|
|
77
88
|
def handle_pb_event(event)
|
|
@@ -98,7 +109,7 @@ module Punchblock
|
|
|
98
109
|
if call = call_with_id(command.target_call_id)
|
|
99
110
|
call.execute_command! command
|
|
100
111
|
else
|
|
101
|
-
command.response = ProtocolError.new.setup
|
|
112
|
+
command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{command.target_call_id}", command.target_call_id
|
|
102
113
|
end
|
|
103
114
|
end
|
|
104
115
|
|
|
@@ -106,7 +117,7 @@ module Punchblock
|
|
|
106
117
|
if (component = component_with_id(command.component_id))
|
|
107
118
|
component.execute_command! command
|
|
108
119
|
else
|
|
109
|
-
command.response = ProtocolError.new.setup
|
|
120
|
+
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
|
|
110
121
|
end
|
|
111
122
|
end
|
|
112
123
|
|
|
@@ -117,7 +128,7 @@ module Punchblock
|
|
|
117
128
|
register_component component
|
|
118
129
|
component.execute!
|
|
119
130
|
when Punchblock::Command::Dial
|
|
120
|
-
call = Call.
|
|
131
|
+
call = Call.new_link command.to, current_actor
|
|
121
132
|
register_call call
|
|
122
133
|
call.dial! command
|
|
123
134
|
else
|
|
@@ -134,6 +145,29 @@ module Punchblock
|
|
|
134
145
|
'Command' => "dialplan add extension #{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY},AGI,agi:async into #{REDIRECT_CONTEXT}"
|
|
135
146
|
})
|
|
136
147
|
pb_logger.trace "Added extension extension #{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY},AGI,agi:async into #{REDIRECT_CONTEXT}"
|
|
148
|
+
send_ami_action('Command', {
|
|
149
|
+
'Command' => "dialplan show #{REDIRECT_CONTEXT}"
|
|
150
|
+
}) do |result|
|
|
151
|
+
if result.text_body =~ /failed/
|
|
152
|
+
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."
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def check_recording_directory
|
|
158
|
+
pb_logger.warning "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)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def actor_died(actor, reason)
|
|
162
|
+
return unless reason
|
|
163
|
+
pb_logger.error "A linked actor (#{actor.inspect}) died due to #{reason.inspect}"
|
|
164
|
+
if id = @calls.key(actor)
|
|
165
|
+
pb_logger.info "Dead actor was a call we know about, with ID #{id}. Removing it from the registry..."
|
|
166
|
+
@calls.delete id
|
|
167
|
+
end_event = Punchblock::Event::End.new :target_call_id => id,
|
|
168
|
+
:reason => :error
|
|
169
|
+
handle_pb_event end_event
|
|
170
|
+
end
|
|
137
171
|
end
|
|
138
172
|
|
|
139
173
|
private
|
|
@@ -149,10 +183,8 @@ module Punchblock
|
|
|
149
183
|
end
|
|
150
184
|
|
|
151
185
|
def ami_dispatch_to_or_create_call(event)
|
|
152
|
-
if (event
|
|
153
|
-
|
|
154
|
-
(event['Channel2'] && call_for_channel(event['Channel2']))
|
|
155
|
-
[event['Channel'], event['Channel1'], event['Channel2']].compact.each do |channel|
|
|
186
|
+
if ami_event_known_call?(event)
|
|
187
|
+
channels_for_ami_event(event).each do |channel|
|
|
156
188
|
call = call_for_channel channel
|
|
157
189
|
call.process_ami_event! event if call
|
|
158
190
|
end
|
|
@@ -161,14 +193,25 @@ module Punchblock
|
|
|
161
193
|
end
|
|
162
194
|
end
|
|
163
195
|
|
|
196
|
+
def channels_for_ami_event(event)
|
|
197
|
+
[event['Channel'], event['Channel1'], event['Channel2']].compact
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def ami_event_known_call?(event)
|
|
201
|
+
(event['Channel'] && call_for_channel(event['Channel'])) ||
|
|
202
|
+
(event['Channel1'] && call_for_channel(event['Channel1'])) ||
|
|
203
|
+
(event['Channel2'] && call_for_channel(event['Channel2']))
|
|
204
|
+
end
|
|
205
|
+
|
|
164
206
|
def handle_async_agi_start_event(event)
|
|
165
|
-
env =
|
|
207
|
+
env = RubyAMI::AsyncAGIEnvironmentParser.new(event['Env']).to_hash
|
|
166
208
|
|
|
167
209
|
return pb_logger.warn "Ignoring AsyncAGI Start event because it is for an 'h' extension" if env[:agi_extension] == 'h'
|
|
168
210
|
return pb_logger.warn "Ignoring AsyncAGI Start event because it is for an 'Kill' type" if env[:agi_type] == 'Kill'
|
|
169
211
|
|
|
170
212
|
pb_logger.trace "Handling AsyncAGI Start event by creating a new call"
|
|
171
213
|
call = Call.new event['Channel'], current_actor, env
|
|
214
|
+
link call
|
|
172
215
|
register_call call
|
|
173
216
|
call.send_offer!
|
|
174
217
|
end
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
2
|
|
|
3
|
-
require 'uri'
|
|
4
|
-
|
|
5
3
|
module Punchblock
|
|
6
4
|
module Translator
|
|
7
5
|
class Asterisk
|
|
8
6
|
class Call
|
|
9
7
|
include HasGuardedHandlers
|
|
10
8
|
include Celluloid
|
|
9
|
+
include DeadActorSafety
|
|
11
10
|
|
|
12
11
|
attr_reader :id, :channel, :translator, :agi_env, :direction, :pending_joins
|
|
13
12
|
|
|
@@ -21,26 +20,16 @@ module Punchblock
|
|
|
21
20
|
HANGUP_CAUSE_TO_END_REASON[22] = :reject
|
|
22
21
|
HANGUP_CAUSE_TO_END_REASON[102] = :timeout
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
def parse_environment(agi_env)
|
|
26
|
-
agi_env_as_array(agi_env).inject({}) do |accumulator, element|
|
|
27
|
-
accumulator[element[0].to_sym] = element[1] || ''
|
|
28
|
-
accumulator
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def agi_env_as_array(agi_env)
|
|
33
|
-
URI::Parser.new.unescape(agi_env).encode.split("\n").map { |p| p.split ': ' }
|
|
34
|
-
end
|
|
35
|
-
end
|
|
23
|
+
trap_exit :actor_died
|
|
36
24
|
|
|
37
25
|
def initialize(channel, translator, agi_env = nil)
|
|
38
26
|
@channel, @translator = channel, translator
|
|
39
27
|
@agi_env = agi_env || {}
|
|
40
|
-
@id, @components =
|
|
28
|
+
@id, @components = Punchblock.new_uuid, {}
|
|
41
29
|
@answered = false
|
|
42
30
|
@pending_joins = {}
|
|
43
31
|
pb_logger.debug "Starting up call with channel #{channel}, id #{@id}"
|
|
32
|
+
@progress_sent = false
|
|
44
33
|
end
|
|
45
34
|
|
|
46
35
|
def register_component(component)
|
|
@@ -96,9 +85,11 @@ module Punchblock
|
|
|
96
85
|
@answered
|
|
97
86
|
end
|
|
98
87
|
|
|
99
|
-
def
|
|
100
|
-
return if answered? || outbound?
|
|
101
|
-
|
|
88
|
+
def send_progress
|
|
89
|
+
return if answered? || outbound? || @progress_sent
|
|
90
|
+
pb_logger.debug "Sending Progress to start early media"
|
|
91
|
+
@progress_sent = true
|
|
92
|
+
send_agi_action "EXEC Progress"
|
|
102
93
|
end
|
|
103
94
|
|
|
104
95
|
def channel=(other)
|
|
@@ -107,19 +98,23 @@ module Punchblock
|
|
|
107
98
|
end
|
|
108
99
|
|
|
109
100
|
def process_ami_event(ami_event)
|
|
101
|
+
send_pb_event Event::Asterisk::AMI::Event.new(:name => ami_event.name, :attributes => ami_event.headers)
|
|
102
|
+
|
|
110
103
|
case ami_event.name
|
|
111
104
|
when 'Hangup'
|
|
112
105
|
pb_logger.trace "Received a Hangup AMI event. Sending End event."
|
|
113
106
|
@components.dup.each_pair do |id, component|
|
|
114
|
-
|
|
107
|
+
safe_from_dead_actors do
|
|
108
|
+
component.call_ended if component.alive?
|
|
109
|
+
end
|
|
115
110
|
end
|
|
116
111
|
send_end_event HANGUP_CAUSE_TO_END_REASON[ami_event['Cause'].to_i]
|
|
117
112
|
when 'AsyncAGI'
|
|
118
113
|
pb_logger.trace "Received an AsyncAGI event. Looking for matching AGICommand component."
|
|
119
114
|
if component = component_with_id(ami_event['CommandID'])
|
|
120
|
-
component.handle_ami_event
|
|
115
|
+
component.handle_ami_event ami_event
|
|
121
116
|
else
|
|
122
|
-
pb_logger.
|
|
117
|
+
pb_logger.trace "Could not find component for AMI event: #{ami_event.inspect}"
|
|
123
118
|
end
|
|
124
119
|
when 'Newstate'
|
|
125
120
|
pb_logger.trace "Received a Newstate AMI event with state #{ami_event['ChannelState']}: #{ami_event['ChannelStateDesc']}"
|
|
@@ -130,6 +125,11 @@ module Punchblock
|
|
|
130
125
|
@answered = true
|
|
131
126
|
send_pb_event Event::Answered.new
|
|
132
127
|
end
|
|
128
|
+
when 'OriginateResponse'
|
|
129
|
+
if ami_event['Response'] == 'Failure' && ami_event['Uniqueid'] == '<null>'
|
|
130
|
+
pb_logger.info "Outbound call could not be established!"
|
|
131
|
+
send_end_event :error
|
|
132
|
+
end
|
|
133
133
|
when 'BridgeExec'
|
|
134
134
|
if join_command = pending_joins[ami_event['Channel2']]
|
|
135
135
|
join_command.response = true
|
|
@@ -165,9 +165,9 @@ module Punchblock
|
|
|
165
165
|
pb_logger.debug "Executing command: #{command.inspect}"
|
|
166
166
|
if command.component_id
|
|
167
167
|
if component = component_with_id(command.component_id)
|
|
168
|
-
component.execute_command
|
|
168
|
+
component.execute_command command
|
|
169
169
|
else
|
|
170
|
-
command.response = ProtocolError.new.setup
|
|
170
|
+
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
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
case command
|
|
@@ -182,7 +182,7 @@ module Punchblock
|
|
|
182
182
|
end
|
|
183
183
|
end
|
|
184
184
|
when Command::Answer
|
|
185
|
-
send_agi_action '
|
|
185
|
+
send_agi_action 'ANSWER' do |response|
|
|
186
186
|
command.response = true
|
|
187
187
|
end
|
|
188
188
|
when Command::Hangup
|
|
@@ -195,7 +195,14 @@ module Punchblock
|
|
|
195
195
|
send_agi_action 'EXEC Bridge', other_call.channel
|
|
196
196
|
when Command::Unjoin
|
|
197
197
|
other_call = translator.call_with_id command.call_id
|
|
198
|
-
redirect_back other_call
|
|
198
|
+
redirect_back other_call do |response|
|
|
199
|
+
case response
|
|
200
|
+
when RubyAMI::Error
|
|
201
|
+
command.response = ProtocolError.new.setup 'error', response.message
|
|
202
|
+
else
|
|
203
|
+
command.response = true
|
|
204
|
+
end
|
|
205
|
+
end
|
|
199
206
|
when Command::Reject
|
|
200
207
|
rejection = case command.reason
|
|
201
208
|
when :busy
|
|
@@ -238,7 +245,7 @@ module Punchblock
|
|
|
238
245
|
(name.is_a?(RubyAMI::Action) ? name : RubyAMI::Action.new(name, headers, &block)).tap do |action|
|
|
239
246
|
@current_ami_action = action
|
|
240
247
|
pb_logger.trace "Sending AMI action #{action.inspect}"
|
|
241
|
-
translator.send_ami_action
|
|
248
|
+
translator.send_ami_action action
|
|
242
249
|
end
|
|
243
250
|
end
|
|
244
251
|
|
|
@@ -246,7 +253,7 @@ module Punchblock
|
|
|
246
253
|
"#{self.class}: #{id}"
|
|
247
254
|
end
|
|
248
255
|
|
|
249
|
-
def redirect_back(other_call = nil)
|
|
256
|
+
def redirect_back(other_call = nil, &block)
|
|
250
257
|
redirect_options = {
|
|
251
258
|
'Channel' => channel,
|
|
252
259
|
'Exten' => Asterisk::REDIRECT_EXTENSION,
|
|
@@ -259,18 +266,30 @@ module Punchblock
|
|
|
259
266
|
'ExtraPriority' => Asterisk::REDIRECT_PRIORITY,
|
|
260
267
|
'ExtraContext' => Asterisk::REDIRECT_CONTEXT
|
|
261
268
|
}) if other_call
|
|
262
|
-
send_ami_action 'Redirect', redirect_options
|
|
269
|
+
send_ami_action 'Redirect', redirect_options, &block
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def actor_died(actor, reason)
|
|
273
|
+
return unless reason
|
|
274
|
+
pb_logger.error "A linked actor (#{actor.inspect}) died due to #{reason.inspect}"
|
|
275
|
+
if id = @components.key(actor)
|
|
276
|
+
pb_logger.info "Dead actor was a component we know about, with ID #{id}. Removing it from the registry..."
|
|
277
|
+
@components.delete id
|
|
278
|
+
complete_event = Punchblock::Event::Complete.new :component_id => id, :reason => Punchblock::Event::Complete::Error.new
|
|
279
|
+
send_pb_event complete_event
|
|
280
|
+
end
|
|
263
281
|
end
|
|
264
282
|
|
|
265
283
|
private
|
|
266
284
|
|
|
267
285
|
def send_end_event(reason)
|
|
268
286
|
send_pb_event Event::End.new(:reason => reason)
|
|
287
|
+
translator.deregister_call current_actor
|
|
269
288
|
after(5) { shutdown }
|
|
270
289
|
end
|
|
271
290
|
|
|
272
291
|
def execute_component(type, command, options = {})
|
|
273
|
-
type.
|
|
292
|
+
type.new_link(command, current_actor).tap do |component|
|
|
274
293
|
register_component component
|
|
275
294
|
component.internal = true if options[:internal]
|
|
276
295
|
component.execute!
|
|
@@ -280,7 +299,7 @@ module Punchblock
|
|
|
280
299
|
def send_pb_event(event)
|
|
281
300
|
event.target_call_id = id
|
|
282
301
|
pb_logger.trace "Sending Punchblock event: #{event.inspect}"
|
|
283
|
-
translator.handle_pb_event
|
|
302
|
+
translator.handle_pb_event event
|
|
284
303
|
end
|
|
285
304
|
|
|
286
305
|
def offer_event
|