punchblock 0.9.2 → 0.10.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.
Files changed (113) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +34 -21
  3. data/Rakefile +20 -0
  4. data/lib/punchblock/client/component_registry.rb +2 -0
  5. data/lib/punchblock/client.rb +2 -2
  6. data/lib/punchblock/command/accept.rb +2 -0
  7. data/lib/punchblock/command/answer.rb +2 -0
  8. data/lib/punchblock/command/dial.rb +2 -0
  9. data/lib/punchblock/command/hangup.rb +2 -0
  10. data/lib/punchblock/command/join.rb +2 -0
  11. data/lib/punchblock/command/mute.rb +2 -0
  12. data/lib/punchblock/command/redirect.rb +2 -0
  13. data/lib/punchblock/command/reject.rb +3 -1
  14. data/lib/punchblock/command/unjoin.rb +2 -0
  15. data/lib/punchblock/command/unmute.rb +2 -0
  16. data/lib/punchblock/command.rb +2 -0
  17. data/lib/punchblock/command_node.rb +2 -0
  18. data/lib/punchblock/component/asterisk/agi/command.rb +4 -2
  19. data/lib/punchblock/component/asterisk/agi.rb +2 -0
  20. data/lib/punchblock/component/asterisk/ami/action.rb +4 -2
  21. data/lib/punchblock/component/asterisk/ami.rb +2 -0
  22. data/lib/punchblock/component/asterisk.rb +2 -0
  23. data/lib/punchblock/component/component_node.rb +2 -0
  24. data/lib/punchblock/component/input.rb +2 -0
  25. data/lib/punchblock/component/output.rb +2 -0
  26. data/lib/punchblock/component/record.rb +2 -0
  27. data/lib/punchblock/component/stop.rb +2 -0
  28. data/lib/punchblock/component.rb +2 -0
  29. data/lib/punchblock/connection/asterisk.rb +4 -1
  30. data/lib/punchblock/connection/connected.rb +2 -0
  31. data/lib/punchblock/connection/generic_connection.rb +5 -0
  32. data/lib/punchblock/connection/xmpp.rb +5 -6
  33. data/lib/punchblock/connection.rb +2 -0
  34. data/lib/punchblock/core_ext/blather/stanza/presence.rb +2 -0
  35. data/lib/punchblock/core_ext/blather/stanza.rb +2 -0
  36. data/lib/punchblock/core_ext/ruby.rb +1 -12
  37. data/lib/punchblock/disconnected_error.rb +2 -0
  38. data/lib/punchblock/event/answered.rb +2 -0
  39. data/lib/punchblock/event/asterisk/ami/event.rb +3 -1
  40. data/lib/punchblock/event/asterisk/ami.rb +2 -0
  41. data/lib/punchblock/event/asterisk.rb +2 -0
  42. data/lib/punchblock/event/complete.rb +3 -1
  43. data/lib/punchblock/event/dtmf.rb +2 -0
  44. data/lib/punchblock/event/end.rb +2 -0
  45. data/lib/punchblock/event/joined.rb +2 -0
  46. data/lib/punchblock/event/offer.rb +6 -0
  47. data/lib/punchblock/event/ringing.rb +2 -0
  48. data/lib/punchblock/event/unjoined.rb +2 -0
  49. data/lib/punchblock/event.rb +2 -0
  50. data/lib/punchblock/has_headers.rb +3 -1
  51. data/lib/punchblock/header.rb +2 -0
  52. data/lib/punchblock/key_value_pair_node.rb +2 -0
  53. data/lib/punchblock/media_container.rb +2 -0
  54. data/lib/punchblock/media_node.rb +2 -0
  55. data/lib/punchblock/protocol_error.rb +2 -0
  56. data/lib/punchblock/rayo_node.rb +4 -3
  57. data/lib/punchblock/ref.rb +2 -0
  58. data/lib/punchblock/translator/asterisk/call.rb +80 -26
  59. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +3 -1
  60. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +3 -1
  61. data/lib/punchblock/translator/asterisk/component/asterisk.rb +2 -0
  62. data/lib/punchblock/translator/asterisk/component/input.rb +4 -2
  63. data/lib/punchblock/translator/asterisk/component/output.rb +21 -3
  64. data/lib/punchblock/translator/asterisk/component.rb +2 -0
  65. data/lib/punchblock/translator/asterisk.rb +50 -20
  66. data/lib/punchblock/translator.rb +2 -0
  67. data/lib/punchblock/version.rb +3 -1
  68. data/lib/punchblock.rb +2 -0
  69. data/punchblock.gemspec +2 -2
  70. data/spec/capture_warnings.rb +32 -0
  71. data/spec/punchblock/client/component_registry_spec.rb +2 -0
  72. data/spec/punchblock/client_spec.rb +3 -1
  73. data/spec/punchblock/command/accept_spec.rb +3 -1
  74. data/spec/punchblock/command/answer_spec.rb +3 -1
  75. data/spec/punchblock/command/dial_spec.rb +12 -10
  76. data/spec/punchblock/command/hangup_spec.rb +3 -1
  77. data/spec/punchblock/command/join_spec.rb +11 -9
  78. data/spec/punchblock/command/mute_spec.rb +3 -1
  79. data/spec/punchblock/command/redirect_spec.rb +5 -3
  80. data/spec/punchblock/command/reject_spec.rb +7 -5
  81. data/spec/punchblock/command/unjoin_spec.rb +7 -5
  82. data/spec/punchblock/command/unmute_spec.rb +3 -1
  83. data/spec/punchblock/command_node_spec.rb +9 -7
  84. data/spec/punchblock/component/asterisk/agi/command_spec.rb +21 -19
  85. data/spec/punchblock/component/asterisk/ami/action_spec.rb +19 -17
  86. data/spec/punchblock/component/component_node_spec.rb +5 -3
  87. data/spec/punchblock/component/input_spec.rb +51 -49
  88. data/spec/punchblock/component/output_spec.rb +60 -58
  89. data/spec/punchblock/component/record_spec.rb +36 -34
  90. data/spec/punchblock/connection/asterisk_spec.rb +9 -4
  91. data/spec/punchblock/connection/xmpp_spec.rb +40 -39
  92. data/spec/punchblock/event/answered_spec.rb +4 -2
  93. data/spec/punchblock/event/asterisk/ami/event_spec.rb +9 -7
  94. data/spec/punchblock/event/complete_spec.rb +12 -10
  95. data/spec/punchblock/event/dtmf_spec.rb +6 -4
  96. data/spec/punchblock/event/end_spec.rb +6 -4
  97. data/spec/punchblock/event/joined_spec.rb +8 -6
  98. data/spec/punchblock/event/offer_spec.rb +7 -5
  99. data/spec/punchblock/event/ringing_spec.rb +4 -2
  100. data/spec/punchblock/event/unjoined_spec.rb +8 -6
  101. data/spec/punchblock/header_spec.rb +13 -11
  102. data/spec/punchblock/protocol_error_spec.rb +8 -6
  103. data/spec/punchblock/ref_spec.rb +5 -3
  104. data/spec/punchblock/translator/asterisk/call_spec.rb +261 -14
  105. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +13 -11
  106. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +4 -2
  107. data/spec/punchblock/translator/asterisk/component/input_spec.rb +10 -8
  108. data/spec/punchblock/translator/asterisk/component/output_spec.rb +111 -20
  109. data/spec/punchblock/translator/asterisk/component_spec.rb +3 -1
  110. data/spec/punchblock/translator/asterisk_spec.rb +107 -10
  111. data/spec/spec_helper.rb +23 -17
  112. data/spec/support/mock_connection_with_event_handler.rb +2 -0
  113. metadata +43 -41
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  class Event
3
5
  class Unjoined < Event
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  class Event < RayoNode
3
5
  def self.new(options = {})
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module HasHeaders
3
5
  ##
@@ -23,7 +25,7 @@ module Punchblock
23
25
  # @param [Hash, Array] headers A hash of key-value header pairs, or an array of Header objects
24
26
  #
25
27
  def headers=(headers)
26
- find('//ns:header', :ns => self.class.registered_ns).each &:remove
28
+ find('//ns:header', :ns => self.class.registered_ns).each(&:remove)
27
29
  if headers.is_a? Hash
28
30
  headers.each_pair { |k,v| self << Header.new(k, v) }
29
31
  elsif headers.is_a? Array
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'punchblock/key_value_pair_node'
2
4
 
3
5
  module Punchblock
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module KeyValuePairNode
2
4
  def self.included(klass)
3
5
  klass.class_exec do
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module MediaContainer
3
5
  ##
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  class MediaNode < RayoNode
3
5
  include MediaContainer
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  ##
3
5
  # This exception may be raised if a transport error is detected.
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'active_support/core_ext/class/attribute'
2
4
  require 'niceogiri'
3
5
 
@@ -41,12 +43,11 @@ module Punchblock
41
43
  def self.import(node, call_id = nil, component_id = nil)
42
44
  ns = (node.namespace.href if node.namespace)
43
45
  klass = class_from_registration(node.element_name, ns)
44
- event = if klass && klass != self
46
+ if klass && klass != self
45
47
  klass.import node, call_id, component_id
46
48
  else
47
49
  new.inherit node
48
- end
49
- event.tap do |event|
50
+ end.tap do |event|
50
51
  event.call_id = call_id
51
52
  event.component_id = component_id
52
53
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  ##
3
5
  # An rayo Ref message. This provides the command ID in response to execution of a command.
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'uri'
2
4
 
3
5
  module Punchblock
@@ -7,9 +9,10 @@ module Punchblock
7
9
  include HasGuardedHandlers
8
10
  include Celluloid
9
11
 
10
- attr_reader :id, :channel, :translator, :agi_env, :direction
12
+ attr_reader :id, :channel, :translator, :agi_env, :direction, :pending_joins
11
13
 
12
14
  HANGUP_CAUSE_TO_END_REASON = Hash.new { :error }
15
+ HANGUP_CAUSE_TO_END_REASON[0] = :hangup
13
16
  HANGUP_CAUSE_TO_END_REASON[16] = :hangup
14
17
  HANGUP_CAUSE_TO_END_REASON[17] = :busy
15
18
  HANGUP_CAUSE_TO_END_REASON[18] = :timeout
@@ -18,11 +21,25 @@ module Punchblock
18
21
  HANGUP_CAUSE_TO_END_REASON[22] = :reject
19
22
  HANGUP_CAUSE_TO_END_REASON[102] = :timeout
20
23
 
21
- def initialize(channel, translator, agi_env = '')
24
+ class << self
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
36
+
37
+ def initialize(channel, translator, agi_env = nil)
22
38
  @channel, @translator = channel, translator
23
- @agi_env = parse_environment agi_env
39
+ @agi_env = agi_env || {}
24
40
  @id, @components = UUIDTools::UUID.random_create.to_s, {}
25
41
  @answered = false
42
+ @pending_joins = {}
26
43
  pb_logger.debug "Starting up call with channel #{channel}, id #{@id}"
27
44
  end
28
45
 
@@ -89,21 +106,19 @@ module Punchblock
89
106
  end
90
107
 
91
108
  def process_ami_event(ami_event)
92
- pb_logger.trace "Processing AMI event #{ami_event.inspect}"
93
109
  case ami_event.name
94
110
  when 'Hangup'
95
- pb_logger.debug "Received a Hangup AMI event. Sending End event."
111
+ pb_logger.trace "Received a Hangup AMI event. Sending End event."
96
112
  send_end_event HANGUP_CAUSE_TO_END_REASON[ami_event['Cause'].to_i]
97
113
  when 'AsyncAGI'
98
- pb_logger.debug "Received an AsyncAGI event. Looking for matching AGICommand component."
114
+ pb_logger.trace "Received an AsyncAGI event. Looking for matching AGICommand component."
99
115
  if component = component_with_id(ami_event['CommandID'])
100
- pb_logger.debug "Found component #{component.id} for event. Forwarding event..."
101
116
  component.handle_ami_event! ami_event
102
117
  else
103
- pb_logger.debug "Could not find component for AMI event: #{ami_event}"
118
+ pb_logger.warn "Could not find component for AMI event: #{ami_event.inspect}"
104
119
  end
105
120
  when 'Newstate'
106
- pb_logger.debug "Received a Newstate AMI event with state #{ami_event['ChannelState']}: #{ami_event['ChannelStateDesc']}"
121
+ pb_logger.trace "Received a Newstate AMI event with state #{ami_event['ChannelState']}: #{ami_event['ChannelStateDesc']}"
107
122
  case ami_event['ChannelState']
108
123
  when '5'
109
124
  send_pb_event Event::Ringing.new
@@ -111,6 +126,33 @@ module Punchblock
111
126
  @answered = true
112
127
  send_pb_event Event::Answered.new
113
128
  end
129
+ when 'BridgeExec'
130
+ if join_command = pending_joins[ami_event['Channel2']]
131
+ join_command.response = true
132
+ end
133
+ when 'Bridge'
134
+ other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
135
+ if other_call = translator.call_for_channel(other_call_channel)
136
+ event = case ami_event['Bridgestate']
137
+ when 'Link'
138
+ Event::Joined.new.tap do |e|
139
+ e.other_call_id = other_call.id
140
+ end
141
+ when 'Unlink'
142
+ Event::Unjoined.new.tap do |e|
143
+ e.other_call_id = other_call.id
144
+ end
145
+ end
146
+ send_pb_event event
147
+ end
148
+ when 'Unlink'
149
+ other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
150
+ if other_call = translator.call_for_channel(other_call_channel)
151
+ event = Event::Unjoined.new.tap do |e|
152
+ e.other_call_id = other_call.id
153
+ end
154
+ send_pb_event event
155
+ end
114
156
  end
115
157
  trigger_handler :ami, ami_event
116
158
  end
@@ -143,6 +185,13 @@ module Punchblock
143
185
  send_ami_action 'Hangup', 'Channel' => channel do |response|
144
186
  command.response = true
145
187
  end
188
+ when Command::Join
189
+ other_call = translator.call_with_id command.other_call_id
190
+ pending_joins[other_call.channel] = command
191
+ send_agi_action 'EXEC Bridge', other_call.channel
192
+ when Command::Unjoin
193
+ other_call = translator.call_with_id command.other_call_id
194
+ redirect_back other_call
146
195
  when Punchblock::Component::Asterisk::AGI::Command
147
196
  execute_component Component::Asterisk::AGICommand, command
148
197
  when Punchblock::Component::Output
@@ -155,12 +204,12 @@ module Punchblock
155
204
  end
156
205
 
157
206
  def send_agi_action(command, *params, &block)
158
- pb_logger.debug "Sending AGI action #{command}"
207
+ pb_logger.trace "Sending AGI action #{command}"
159
208
  @current_agi_command = Punchblock::Component::Asterisk::AGI::Command.new :name => command, :params => params, :call_id => id
160
209
  @current_agi_command.request!
161
210
  @current_agi_command.register_handler :internal, Punchblock::Event::Complete do |e|
162
- pb_logger.debug "AGI action received complete event #{e.inspect}"
163
- block.call e
211
+ pb_logger.trace "AGI action received complete event #{e.inspect}"
212
+ block.call e if block
164
213
  end
165
214
  execute_component Component::Asterisk::AGICommand, @current_agi_command, :internal => true
166
215
  end
@@ -168,7 +217,7 @@ module Punchblock
168
217
  def send_ami_action(name, headers = {}, &block)
169
218
  (name.is_a?(RubyAMI::Action) ? name : RubyAMI::Action.new(name, headers, &block)).tap do |action|
170
219
  @current_ami_action = action
171
- pb_logger.debug "Sending AMI action #{action.inspect}"
220
+ pb_logger.trace "Sending AMI action #{action.inspect}"
172
221
  translator.send_ami_action! action
173
222
  end
174
223
  end
@@ -179,9 +228,25 @@ module Punchblock
179
228
 
180
229
  private
181
230
 
231
+ def redirect_back(other_call = nil)
232
+ redirect_options = {
233
+ 'Channel' => channel,
234
+ 'Exten' => Asterisk::REDIRECT_EXTENSION,
235
+ 'Priority' => Asterisk::REDIRECT_PRIORITY,
236
+ 'Context' => Asterisk::REDIRECT_CONTEXT
237
+ }
238
+ redirect_options.merge!({
239
+ 'ExtraChannel' => other_call.channel,
240
+ 'ExtraExten' => Asterisk::REDIRECT_EXTENSION,
241
+ 'ExtraPriority' => Asterisk::REDIRECT_PRIORITY,
242
+ 'ExtraContext' => Asterisk::REDIRECT_CONTEXT
243
+ }) if other_call
244
+ send_ami_action 'Redirect', redirect_options
245
+ end
246
+
182
247
  def send_end_event(reason)
183
248
  send_pb_event Event::End.new(:reason => reason)
184
- current_actor.terminate!
249
+ after(5) { shutdown }
185
250
  end
186
251
 
187
252
  def execute_component(type, command, options = {})
@@ -194,7 +259,7 @@ module Punchblock
194
259
 
195
260
  def send_pb_event(event)
196
261
  event.call_id = id
197
- pb_logger.debug "Sending Punchblock event: #{event.inspect}"
262
+ pb_logger.trace "Sending Punchblock event: #{event.inspect}"
198
263
  translator.handle_pb_event! event
199
264
  end
200
265
 
@@ -210,17 +275,6 @@ module Punchblock
210
275
  accumulator
211
276
  end
212
277
  end
213
-
214
- def parse_environment(agi_env)
215
- agi_env_as_array(agi_env).inject({}) do |accumulator, element|
216
- accumulator[element[0].to_sym] = element[1] || ''
217
- accumulator
218
- end
219
- end
220
-
221
- def agi_env_as_array(agi_env)
222
- URI.unescape(agi_env).encode.split("\n").map { |p| p.split ': ' }
223
- end
224
278
  end
225
279
  end
226
280
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'uri'
2
4
  require 'active_support/core_ext/string/filters'
3
5
 
@@ -28,7 +30,7 @@ module Punchblock
28
30
  end
29
31
 
30
32
  def parse_agi_result(result)
31
- match = URI.decode(result).chomp.match(/^(\d{3}) result=(-?\d*) ?(\(?.*\)?)?$/)
33
+ match = URI::Parser.new.unescape(result).chomp.match(/^(\d{3}) result=(-?\d*) ?(\(?.*\)?)?$/)
32
34
  if match
33
35
  data = match[3] ? match[3].gsub(/(^\()|(\)$)/, '') : nil
34
36
  [match[1].to_i, match[2].to_i, data]
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module Translator
3
5
  class Asterisk
@@ -55,7 +57,7 @@ module Punchblock
55
57
 
56
58
  def success_reason(response)
57
59
  headers = response.headers
58
- headers.merge! @extra_complete_attributes if @extra_complete_attributes
60
+ headers.merge! @extra_complete_attributes if instance_variable_defined?(:@extra_complete_attributes)
59
61
  headers.delete 'ActionID'
60
62
  Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :message => headers.delete('Message'), :attributes => headers
61
63
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module Translator
3
5
  class Asterisk
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module Translator
3
5
  class Asterisk
@@ -71,7 +73,7 @@ module Punchblock
71
73
  end
72
74
 
73
75
  def cancel_initial_timer
74
- return unless @initial_timer
76
+ return unless instance_variable_defined?(:@initial_timer) && @initial_timer
75
77
  @initial_timer.cancel
76
78
  @initial_timer = nil
77
79
  end
@@ -90,7 +92,7 @@ module Punchblock
90
92
  end
91
93
 
92
94
  def cancel_inter_digit_timer
93
- return unless @inter_digit_timer
95
+ return unless instance_variable_defined?(:@inter_digit_timer) && @inter_digit_timer
94
96
  @inter_digit_timer.cancel
95
97
  @inter_digit_timer = nil
96
98
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'active_support/core_ext/string/filters'
2
4
 
3
5
  module Punchblock
@@ -38,7 +40,11 @@ module Punchblock
38
40
 
39
41
  send_ref
40
42
 
41
- @interrupt_digits = '0123456789*#' if [:any, :dtmf].include? @component_node.interrupt_on
43
+ @interrupt_digits = if [:any, :dtmf].include? @component_node.interrupt_on
44
+ '0123456789*#'
45
+ else
46
+ nil
47
+ end
42
48
 
43
49
  @execution_elements.each do |element|
44
50
  element.call
@@ -46,12 +52,20 @@ module Punchblock
46
52
  process_playback_completion
47
53
  end
48
54
  when :unimrcp
49
- doc = @component_node.ssml.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }
50
55
  send_ref
51
- @call.send_agi_action! 'EXEC MRCPSynth', doc, mrcpsynth_options do |complete_event|
56
+ @call.send_agi_action! 'EXEC MRCPSynth', escaped_doc, mrcpsynth_options do |complete_event|
52
57
  pb_logger.debug "MRCPSynth completed with #{complete_event}."
53
58
  send_complete_event success_reason
54
59
  end
60
+ when :swift
61
+ doc = escaped_doc
62
+ doc << "|1|1" if [:any, :dtmf].include? @component_node.interrupt_on
63
+ doc.insert 0, "#{@component_node.voice}^" if @component_node.voice
64
+ send_ref
65
+ @call.send_agi_action! 'EXEC Swift', doc do |complete_event|
66
+ pb_logger.debug "Swift completed with #{complete_event}."
67
+ send_complete_event success_reason
68
+ end
55
69
  end
56
70
  end
57
71
 
@@ -79,6 +93,10 @@ module Punchblock
79
93
 
80
94
  private
81
95
 
96
+ def escaped_doc
97
+ @component_node.ssml.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }
98
+ end
99
+
82
100
  def mrcpsynth_options
83
101
  [].tap do |opts|
84
102
  opts << 'i=any' if [:any, :dtmf].include? @component_node.interrupt_on
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module Translator
3
5
  class Asterisk
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'celluloid'
2
4
  require 'ruby_ami'
3
5
 
@@ -13,6 +15,10 @@ module Punchblock
13
15
 
14
16
  attr_reader :ami_client, :connection, :media_engine, :calls
15
17
 
18
+ REDIRECT_CONTEXT = 'adhearsion-redirect'
19
+ REDIRECT_EXTENSION = '1'
20
+ REDIRECT_PRIORITY = '1'
21
+
16
22
  def initialize(ami_client, connection, media_engine = nil)
17
23
  pb_logger.debug "Starting up..."
18
24
  @ami_client, @connection, @media_engine = ami_client, connection, media_engine
@@ -30,9 +36,7 @@ module Punchblock
30
36
  end
31
37
 
32
38
  def call_for_channel(channel)
33
- call = call_with_id @channel_to_call_id[channel]
34
- pb_logger.trace "Looking up call for channel #{channel} from #{@channel_to_call_id}. Found: #{call || 'none'}"
35
- call
39
+ call_with_id @channel_to_call_id[channel]
36
40
  end
37
41
 
38
42
  def register_component(component)
@@ -45,37 +49,27 @@ module Punchblock
45
49
 
46
50
  def shutdown
47
51
  pb_logger.debug "Shutting down"
48
- @calls.values.each &:shutdown!
52
+ @calls.values.each(&:shutdown!)
49
53
  current_actor.terminate!
50
54
  end
51
55
 
52
56
  def handle_ami_event(event)
53
57
  return unless event.is_a? RubyAMI::Event
54
- pb_logger.trace "Handling AMI event #{event.inspect}"
58
+
55
59
  if event.name.downcase == "fullybooted"
56
60
  pb_logger.trace "Counting FullyBooted event"
57
61
  @fully_booted_count += 1
58
62
  if @fully_booted_count >= 2
59
63
  handle_pb_event Connection::Connected.new
60
64
  @fully_booted_count = 0
65
+ run_at_fully_booted
61
66
  end
62
67
  return
63
68
  end
64
69
 
65
- if event.name == 'VarSet' && event['Variable'] == 'punchblock_call_id' && (call = call_with_id event['Value'])
66
- pb_logger.trace "Received a VarSet event indicating the full channel for call #{call}"
67
- @channel_to_call_id.delete call.channel
68
- pb_logger.trace "Removed call with old channel from channel map: #{@channel_to_call_id}"
69
- call.channel = event['Channel']
70
- register_call call
71
- end
70
+ handle_varset_ami_event event
72
71
 
73
- if call = call_for_channel(event['Channel'])
74
- pb_logger.trace "Found call by channel matching this event. Sending to call #{call.id}"
75
- call.process_ami_event! event
76
- elsif event.name.downcase == "asyncagi" && event['SubEvent'] == "Start"
77
- handle_async_agi_start_event event
78
- end
72
+ ami_dispatch_to_or_create_call event
79
73
 
80
74
  handle_pb_event Event::Asterisk::AMI::Event.new(:name => event.name, :attributes => event.headers)
81
75
  end
@@ -85,7 +79,7 @@ module Punchblock
85
79
  end
86
80
 
87
81
  def execute_command(command, options = {})
88
- pb_logger.debug "Executing command #{command.inspect}"
82
+ pb_logger.trace "Executing command #{command.inspect}"
89
83
  command.request!
90
84
 
91
85
  command.call_id ||= options[:call_id]
@@ -135,10 +129,46 @@ module Punchblock
135
129
  ami_client.send_action name, headers, &block
136
130
  end
137
131
 
132
+ def run_at_fully_booted
133
+ send_ami_action('Command', {
134
+ 'Command' => "dialplan add extension #{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY},AGI,agi:async into #{REDIRECT_CONTEXT}"
135
+ })
136
+ pb_logger.trace "Added extension extension #{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY},AGI,agi:async into #{REDIRECT_CONTEXT}"
137
+ end
138
+
138
139
  private
139
140
 
141
+ def handle_varset_ami_event(event)
142
+ return unless event.name == 'VarSet' && event['Variable'] == 'punchblock_call_id' && (call = call_with_id event['Value'])
143
+
144
+ pb_logger.trace "Received a VarSet event indicating the full channel for call #{call}"
145
+ @channel_to_call_id.delete call.channel
146
+ pb_logger.trace "Removed call with old channel from channel map: #{@channel_to_call_id}"
147
+ call.channel = event['Channel']
148
+ register_call call
149
+ end
150
+
151
+ def ami_dispatch_to_or_create_call(event)
152
+ if (event['Channel'] && call_for_channel(event['Channel'])) ||
153
+ (event['Channel1'] && call_for_channel(event['Channel1'])) ||
154
+ (event['Channel2'] && call_for_channel(event['Channel2']))
155
+ [event['Channel'], event['Channel1'], event['Channel2']].compact.each do |channel|
156
+ call = call_for_channel channel
157
+ call.process_ami_event! event if call
158
+ end
159
+ elsif event.name.downcase == "asyncagi" && event['SubEvent'] == "Start"
160
+ handle_async_agi_start_event event
161
+ end
162
+ end
163
+
140
164
  def handle_async_agi_start_event(event)
141
- call = Call.new event['Channel'], current_actor, event['Env']
165
+ env = Call.parse_environment event['Env']
166
+
167
+ return pb_logger.warn "Ignoring AsyncAGI Start event because it is for an 'h' extension" if env[:agi_extension] == 'h'
168
+ return pb_logger.warn "Ignoring AsyncAGI Start event because it is for an 'Kill' type" if env[:agi_type] == 'Kill'
169
+
170
+ pb_logger.trace "Handling AsyncAGI Start event by creating a new call"
171
+ call = Call.new event['Channel'], current_actor, env
142
172
  register_call call
143
173
  call.send_offer!
144
174
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
4
  module Translator
3
5
  extend ActiveSupport::Autoload
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Punchblock
2
- VERSION = "0.9.2"
4
+ VERSION = "0.10.0"
3
5
  end
data/lib/punchblock.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  %w{
2
4
  active_support/dependencies/autoload
3
5
  active_support/core_ext/object/blank
data/punchblock.gemspec CHANGED
@@ -28,11 +28,11 @@ Gem::Specification.new do |s|
28
28
  s.add_runtime_dependency %q<state_machine>, [">= 1.0.1"]
29
29
  s.add_runtime_dependency %q<future-resource>, [">= 0.0.2"]
30
30
  s.add_runtime_dependency %q<has-guarded-handlers>, [">= 0.1.0"]
31
- s.add_runtime_dependency %q<celluloid>, [">= 0.6.0"]
31
+ s.add_runtime_dependency %q<celluloid>, [">= 0.9.0"]
32
32
  s.add_runtime_dependency %q<ruby_ami>, [">= 0.1.3"]
33
33
  s.add_runtime_dependency %q<ruby_speech>, [">= 0.5.1"]
34
34
 
35
- s.add_development_dependency %q<bundler>, ["~> 1.0.0"]
35
+ s.add_development_dependency %q<bundler>, [">= 1.0.0"]
36
36
  s.add_development_dependency %q<rspec>, ["~> 2.7.0"]
37
37
  s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
38
38
  s.add_development_dependency %q<yard>, ["~> 0.6.0"]
@@ -0,0 +1,32 @@
1
+ require 'tempfile'
2
+ stderr_file = Tempfile.new "ahn.stderr"
3
+ $stderr.reopen stderr_file.path
4
+ current_dir = Dir.pwd
5
+
6
+ at_exit do
7
+ stderr_file.rewind
8
+ lines = stderr_file.read.split("\n").uniq
9
+ stderr_file.close!
10
+
11
+ pb_warnings, other_warnings = lines.partition { |line| line.include?(current_dir) && !line.include?('vendor') && line.include?('warning') }
12
+
13
+ if pb_warnings.any?
14
+ puts
15
+ puts "-" * 30 + " PB Warnings: " + "-" * 30
16
+ puts
17
+ puts pb_warnings.join("\n")
18
+ puts
19
+ puts "-" * 75
20
+ puts
21
+ end
22
+
23
+ if other_warnings.any?
24
+ Dir.mkdir 'tmp' unless Dir.exists? 'tmp'
25
+ File.open('tmp/warnings.txt', 'w') { |f| f.write other_warnings.join("\n") }
26
+ puts
27
+ puts "Non-PB warnings written to tmp/warnings.txt"
28
+ puts
29
+ end
30
+
31
+ exit 1 if pb_warnings.any? # fail the build...
32
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Punchblock
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Punchblock
@@ -81,7 +83,7 @@ module Punchblock
81
83
  context 'if event handlers have not been set' do
82
84
  it 'should queue up the event' do
83
85
  subject.handle_event mock_event
84
- subject.event_queue.pop(true).should == mock_event
86
+ subject.event_queue.pop(true).should be == mock_event
85
87
  end
86
88
  end
87
89
  end
@@ -1,10 +1,12 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Punchblock
4
6
  module Command
5
7
  describe Accept do
6
8
  it 'registers itself' do
7
- RayoNode.class_from_registration(:accept, 'urn:xmpp:rayo:1').should == Accept
9
+ RayoNode.class_from_registration(:accept, 'urn:xmpp:rayo:1').should be == Accept
8
10
  end
9
11
 
10
12
  it_should_behave_like 'command_headers'
@@ -1,10 +1,12 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Punchblock
4
6
  module Command
5
7
  describe Answer do
6
8
  it 'registers itself' do
7
- RayoNode.class_from_registration(:answer, 'urn:xmpp:rayo:1').should == Answer
9
+ RayoNode.class_from_registration(:answer, 'urn:xmpp:rayo:1').should be == Answer
8
10
  end
9
11
 
10
12
  it_should_behave_like 'command_headers'