punchblock 0.9.2 → 0.10.0

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