punchblock 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|