punchblock 1.8.0 → 1.8.1

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +1 -1
  4. data/lib/punchblock/component/component_node.rb +1 -1
  5. data/lib/punchblock/component/stop.rb +2 -2
  6. data/lib/punchblock/connection/asterisk.rb +3 -3
  7. data/lib/punchblock/connection/freeswitch.rb +2 -2
  8. data/lib/punchblock/connection/xmpp.rb +1 -1
  9. data/lib/punchblock/translator.rb +1 -0
  10. data/lib/punchblock/translator/asterisk.rb +10 -9
  11. data/lib/punchblock/translator/asterisk/call.rb +15 -7
  12. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +1 -1
  13. data/lib/punchblock/translator/asterisk/component/input.rb +3 -4
  14. data/lib/punchblock/translator/asterisk/component/output.rb +6 -6
  15. data/lib/punchblock/translator/asterisk/component/record.rb +7 -7
  16. data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +3 -3
  17. data/lib/punchblock/translator/dtmf_recognizer.rb +87 -0
  18. data/lib/punchblock/translator/freeswitch.rb +9 -7
  19. data/lib/punchblock/translator/freeswitch/call.rb +6 -4
  20. data/lib/punchblock/translator/freeswitch/component/input.rb +1 -2
  21. data/lib/punchblock/translator/freeswitch/component/output.rb +1 -2
  22. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +1 -1
  23. data/lib/punchblock/translator/input_component.rb +17 -64
  24. data/lib/punchblock/version.rb +1 -1
  25. data/punchblock.gemspec +1 -1
  26. data/spec/punchblock/component/input_spec.rb +1 -1
  27. data/spec/punchblock/component/output_spec.rb +33 -3
  28. data/spec/punchblock/component/record_spec.rb +1 -1
  29. data/spec/punchblock/connection/asterisk_spec.rb +3 -3
  30. data/spec/punchblock/connection/freeswitch_spec.rb +3 -2
  31. data/spec/punchblock/connection/xmpp_spec.rb +18 -6
  32. data/spec/punchblock/translator/asterisk/call_spec.rb +83 -31
  33. data/spec/punchblock/translator/asterisk/component/input_spec.rb +1 -1
  34. data/spec/punchblock/translator/asterisk/component/output_spec.rb +32 -27
  35. data/spec/punchblock/translator/asterisk/component/record_spec.rb +47 -43
  36. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  37. data/spec/punchblock/translator/asterisk_spec.rb +28 -26
  38. data/spec/punchblock/translator/freeswitch/call_spec.rb +24 -15
  39. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +1 -1
  40. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +2 -3
  41. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +1 -1
  42. data/spec/punchblock/translator/freeswitch_spec.rb +75 -18
  43. data/spec/spec_helper.rb +1 -1
  44. metadata +24 -74
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 73b10610c29750367e41a8f99669d79fd5b8bcb8
4
+ data.tar.gz: 66d4bd7dea9d5c33ca580ef361fbf20bfe147015
5
+ SHA512:
6
+ metadata.gz: 6bdf955c26d3ebea75ce690c7feafd969c3542403f0e2c392a8eab91d05e5981514b1bc95bfe025eab8894f864e0638fae051221c7ec382f867baa9db5de2901
7
+ data.tar.gz: 1c9d7bb96f8112ce3e55dc83d5a847a27e73e11ac8d1c20c6907bfe9c7d22956d91de608f65d6f915cdb8de021562029ea84b1cd4956a5d974a42474567bd4b3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # [develop](https://github.com/adhearsion/punchblock)
2
2
 
3
+ # [v1.8.1](https://github.com/adhearsion/punchblock/compare/v1.8.0...v1.8.1) - [2013-03-25](https://rubygems.org/gems/punchblock/versions/1.8.1)
4
+ * Bugfix: FreeSWITCH was requiring a from attribute on a dial command
5
+ * Bugfix: Asterisk translator now properly checks for existence of the recordings directory
6
+ * Bugfix: Components should transition state before unblocking
7
+ * Bugfix: Asterisk joins are now more robustly responded to when the join begins
8
+ * Bugfix: On FreeSWITCH, only events relating to bridge start/end should be delivered to bridged calls
9
+ * Bugfix: On FreeSWITCH, a voice value on an audio-only output component should not prevent execution
10
+ * Bugfix: XMPP Ping should be an IQ get, not set
11
+ * Bugfix: Stop command should be in Rayo ext namespace
12
+ * Bugfix: XMPP specs were mistakenly resetting the logger object for other tests.
13
+ * CS: Avoid Celluloid deprecation warnings
14
+
3
15
  # [v1.8.0](https://github.com/adhearsion/punchblock/compare/v1.7.1...v1.8.0) - [2013-01-10](https://rubygems.org/gems/punchblock/versions/1.8.0)
4
16
  * Feature: Join command now enforces a list of valid direction attribute values
5
17
  * Feature: Added support for media direction to the Record component
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  gem 'bluecloth' unless RUBY_PLATFORM =~ /java/
@@ -50,8 +50,8 @@ module Punchblock
50
50
  def complete_event=(other)
51
51
  return if @complete_event_resource.set_yet?
52
52
  client.delete_component_registration self if client
53
- @complete_event_resource.resource = other
54
53
  complete!
54
+ @complete_event_resource.resource = other
55
55
  end
56
56
 
57
57
  ##
@@ -3,7 +3,7 @@
3
3
  module Punchblock
4
4
  module Component
5
5
  class Stop < CommandNode # :nodoc:
6
- register :stop, :core
6
+ register :stop, :ext
7
7
  end
8
8
  end
9
- end
9
+ end
@@ -9,7 +9,7 @@ module Punchblock
9
9
  attr_accessor :event_handler
10
10
 
11
11
  def initialize(options = {})
12
- @ami_client = RubyAMI::Client.new options.merge(:event_handler => lambda { |event| translator.handle_ami_event! event }, :logger => pb_logger)
12
+ @ami_client = RubyAMI::Client.new options.merge(:event_handler => lambda { |event| translator.async.handle_ami_event event }, :logger => pb_logger)
13
13
  @translator = Translator::Asterisk.new @ami_client, self, options[:media_engine]
14
14
  super()
15
15
  end
@@ -20,12 +20,12 @@ module Punchblock
20
20
  end
21
21
 
22
22
  def stop
23
- translator.shutdown!
23
+ translator.async.shutdown
24
24
  ami_client.stop
25
25
  end
26
26
 
27
27
  def write(command, options)
28
- translator.execute_command! command, options
28
+ translator.async.execute_command command, options
29
29
  end
30
30
 
31
31
  def handle_event(event)
@@ -27,7 +27,7 @@ module Punchblock
27
27
  end
28
28
 
29
29
  def write(command, options)
30
- translator.execute_command! command, options
30
+ translator.async.execute_command command, options
31
31
  end
32
32
 
33
33
  def handle_event(event)
@@ -37,7 +37,7 @@ module Punchblock
37
37
  private
38
38
 
39
39
  def new_fs_stream
40
- RubyFS::Stream.new(*@stream_options, lambda { |e| translator.handle_es_event! e })
40
+ RubyFS::Stream.new(*@stream_options, lambda { |e| translator.async.handle_es_event e })
41
41
  end
42
42
 
43
43
  def start_stream
@@ -175,7 +175,7 @@ module Punchblock
175
175
  end
176
176
 
177
177
  def ping_rayo
178
- client.write_with_handler Blather::Stanza::Iq::Ping.new(:set, root_domain) do |response|
178
+ client.write_with_handler Blather::Stanza::Iq::Ping.new(:get, root_domain) do |response|
179
179
  begin
180
180
  handle_error response if response.is_a? Blather::BlatherError
181
181
  rescue ProtocolError => e
@@ -9,6 +9,7 @@ module Punchblock
9
9
  autoload :Asterisk
10
10
  autoload :Freeswitch
11
11
 
12
+ autoload :DTMFRecognizer
12
13
  autoload :InputComponent
13
14
  end
14
15
  end
@@ -57,7 +57,7 @@ module Punchblock
57
57
  end
58
58
 
59
59
  def shutdown
60
- @calls.values.each(&:shutdown!)
60
+ @calls.values.each { |call| call.async.shutdown }
61
61
  terminate
62
62
  end
63
63
 
@@ -105,7 +105,7 @@ module Punchblock
105
105
 
106
106
  def execute_call_command(command)
107
107
  if call = call_with_id(command.target_call_id)
108
- call.execute_command! command
108
+ call.async.execute_command command
109
109
  else
110
110
  command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{command.target_call_id}", command.target_call_id
111
111
  end
@@ -113,7 +113,7 @@ module Punchblock
113
113
 
114
114
  def execute_component_command(command)
115
115
  if (component = component_with_id(command.component_id))
116
- component.execute_command! command
116
+ component.async.execute_command command
117
117
  else
118
118
  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
119
119
  end
@@ -124,11 +124,11 @@ module Punchblock
124
124
  when Punchblock::Component::Asterisk::AMI::Action
125
125
  component = Component::Asterisk::AMIAction.new command, current_actor
126
126
  register_component component
127
- component.execute!
127
+ component.async.execute
128
128
  when Punchblock::Command::Dial
129
129
  call = Call.new_link command.to, current_actor
130
130
  register_call call
131
- call.dial! command
131
+ call.async.dial command
132
132
  else
133
133
  command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command"
134
134
  end
@@ -149,10 +149,11 @@ module Punchblock
149
149
  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."
150
150
  end
151
151
  end
152
+ check_recording_directory
152
153
  end
153
154
 
154
155
  def check_recording_directory
155
- 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)
156
+ pb_logger.warn "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)
156
157
  end
157
158
 
158
159
  def actor_died(actor, reason)
@@ -181,9 +182,9 @@ module Punchblock
181
182
  call = call_for_channel channel
182
183
  if call
183
184
  if channel_is_bridged?(channel)
184
- call.process_ami_event! event if EVENTS_ALLOWED_BRIDGED.include?(event.name.downcase)
185
+ call.async.process_ami_event event if EVENTS_ALLOWED_BRIDGED.include?(event.name.downcase)
185
186
  else
186
- call.process_ami_event! event
187
+ call.async.process_ami_event event
187
188
  end
188
189
  end
189
190
  end
@@ -215,7 +216,7 @@ module Punchblock
215
216
  call = Call.new event['Channel'], current_actor, env
216
217
  link call
217
218
  register_call call
218
- call.send_offer!
219
+ call.async.send_offer
219
220
  end
220
221
  end
221
222
  end
@@ -8,7 +8,7 @@ module Punchblock
8
8
  include Celluloid
9
9
  include DeadActorSafety
10
10
 
11
- attr_reader :id, :channel, :translator, :agi_env, :direction, :pending_joins
11
+ attr_reader :id, :channel, :translator, :agi_env, :direction
12
12
 
13
13
  HANGUP_CAUSE_TO_END_REASON = Hash.new { :error }
14
14
  HANGUP_CAUSE_TO_END_REASON[0] = :hangup
@@ -29,6 +29,7 @@ module Punchblock
29
29
  @answered = false
30
30
  @pending_joins = {}
31
31
  @progress_sent = false
32
+ @block_commands = false
32
33
  end
33
34
 
34
35
  def register_component(component)
@@ -69,7 +70,7 @@ module Punchblock
69
70
  originate_action = Punchblock::Component::Asterisk::AMI::Action.new :name => 'Originate',
70
71
  :params => params
71
72
  originate_action.request!
72
- translator.execute_global_command! originate_action
73
+ translator.async.execute_global_command originate_action
73
74
  dial_command.response = Ref.new :id => id
74
75
  end
75
76
 
@@ -100,6 +101,7 @@ module Punchblock
100
101
 
101
102
  case ami_event.name
102
103
  when 'Hangup'
104
+ @block_commands = true
103
105
  @components.dup.each_pair do |id, component|
104
106
  safe_from_dead_actors do
105
107
  component.call_ended if component.alive?
@@ -123,9 +125,9 @@ module Punchblock
123
125
  send_end_event :error
124
126
  end
125
127
  when 'BridgeExec'
126
- if join_command = pending_joins[ami_event['Channel2']]
127
- join_command.response = true
128
- end
128
+ join_command = @pending_joins.delete ami_event['Channel1']
129
+ join_command ||= @pending_joins.delete ami_event['Channel2']
130
+ join_command.response = true if join_command
129
131
  when 'Bridge'
130
132
  other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
131
133
  if other_call = translator.call_for_channel(other_call_channel)
@@ -154,6 +156,10 @@ module Punchblock
154
156
  end
155
157
 
156
158
  def execute_command(command)
159
+ if @block_commands
160
+ command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
161
+ return
162
+ end
157
163
  if command.component_id
158
164
  if component = component_with_id(command.component_id)
159
165
  component.execute_command command
@@ -180,7 +186,7 @@ module Punchblock
180
186
  end
181
187
  when Command::Join
182
188
  other_call = translator.call_with_id command.call_id
183
- pending_joins[other_call.channel] = command
189
+ @pending_joins[other_call.channel] = command
184
190
  send_agi_action 'EXEC Bridge', other_call.channel
185
191
  when Command::Unjoin
186
192
  other_call = translator.call_with_id command.call_id
@@ -217,6 +223,8 @@ module Punchblock
217
223
  else
218
224
  command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
219
225
  end
226
+ rescue Celluloid::DeadActorError
227
+ 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
220
228
  end
221
229
 
222
230
  def send_agi_action(command, *params, &block)
@@ -276,7 +284,7 @@ module Punchblock
276
284
  type.new_link(command, current_actor).tap do |component|
277
285
  register_component component
278
286
  component.internal = true if options[:internal]
279
- component.execute!
287
+ component.async.execute
280
288
  end
281
289
  end
282
290
 
@@ -42,7 +42,7 @@ module Punchblock
42
42
  end
43
43
  component = current_actor
44
44
  RubyAMI::Action.new @component_node.name, headers do |response|
45
- component.handle_response! response
45
+ component.async.handle_response response
46
46
  end
47
47
  end
48
48
 
@@ -16,14 +16,13 @@ module Punchblock
16
16
  private
17
17
 
18
18
  def register_dtmf_event_handler
19
- component = current_actor
20
- call.register_handler :ami, :name => 'DTMF' do |event|
21
- component.process_dtmf! event['Digit'] if event['End'] == 'Yes'
19
+ call.register_handler :ami, :name => 'DTMF', [:[], 'End'] => 'Yes' do |event|
20
+ @recognizer << event['Digit']
22
21
  end
23
22
  end
24
23
 
25
24
  def unregister_dtmf_event_handler
26
- call.unregister_handler :ami, @dtmf_handler_id if instance_variable_defined?(:@dtmf_handler_id)
25
+ call.async.unregister_handler :ami, @dtmf_handler_id if instance_variable_defined?(:@dtmf_handler_id)
27
26
  end
28
27
  end
29
28
  end
@@ -55,13 +55,13 @@ module Punchblock
55
55
  playback opts
56
56
  when :unimrcp
57
57
  send_ref
58
- @call.send_agi_action! 'EXEC MRCPSynth', escape_commas(escaped_doc), mrcpsynth_options do |complete_event|
59
- output_component.send_complete_event! success_reason
58
+ @call.async.send_agi_action 'EXEC MRCPSynth', escape_commas(escaped_doc), mrcpsynth_options do |complete_event|
59
+ output_component.async.send_complete_event success_reason
60
60
  end
61
61
  when :swift
62
62
  send_ref
63
- @call.send_agi_action! 'EXEC Swift', swift_doc do |complete_event|
64
- output_component.send_complete_event! success_reason
63
+ @call.async.send_agi_action 'EXEC Swift', swift_doc do |complete_event|
64
+ output_component.async.send_complete_event success_reason
65
65
  end
66
66
  else
67
67
  raise OptionError, 'The renderer foobar is unsupported.'
@@ -92,8 +92,8 @@ module Punchblock
92
92
 
93
93
  def playback(path)
94
94
  op = current_actor
95
- @call.send_agi_action! 'EXEC Playback', path do |complete_event|
96
- op.send_complete_event! success_reason
95
+ @call.async.send_agi_action 'EXEC Playback', path do |complete_event|
96
+ op.async.send_complete_event success_reason
97
97
  end
98
98
  end
99
99
 
@@ -31,16 +31,16 @@ module Punchblock
31
31
  send_ref
32
32
 
33
33
  if @component_node.start_beep
34
- @call.send_agi_action! 'STREAM FILE', 'beep', '""' do
35
- component.signal! :beep_finished
34
+ @call.async.send_agi_action 'STREAM FILE', 'beep', '""' do
35
+ component.async.signal :beep_finished
36
36
  end
37
37
  wait :beep_finished
38
38
  end
39
39
 
40
- call.send_ami_action! 'Monitor', 'Channel' => call.channel, 'File' => filename, 'Format' => @format, 'Mix' => true
40
+ call.async.send_ami_action 'Monitor', 'Channel' => call.channel, 'File' => filename, 'Format' => @format, 'Mix' => true
41
41
  unless max_duration == -1
42
42
  after max_duration/1000 do
43
- call.send_ami_action! 'StopMonitor', 'Channel' => call.channel
43
+ call.async.send_ami_action 'StopMonitor', 'Channel' => call.channel
44
44
  end
45
45
  end
46
46
  rescue OptionError => e
@@ -52,17 +52,17 @@ module Punchblock
52
52
  when Punchblock::Component::Stop
53
53
  command.response = true
54
54
  a = current_actor
55
- call.send_ami_action! 'StopMonitor', 'Channel' => call.channel do |complete_event|
55
+ call.async.send_ami_action 'StopMonitor', 'Channel' => call.channel do |complete_event|
56
56
  @complete_reason = stop_reason
57
57
  end
58
58
  when Punchblock::Component::Record::Pause
59
59
  a = current_actor
60
- call.send_ami_action! 'PauseMonitor', 'Channel' => call.channel do |complete_event|
60
+ call.async.send_ami_action 'PauseMonitor', 'Channel' => call.channel do |complete_event|
61
61
  command.response = true
62
62
  end
63
63
  when Punchblock::Component::Record::Resume
64
64
  a = current_actor
65
- call.send_ami_action! 'ResumeMonitor', 'Channel' => call.channel do |complete_event|
65
+ call.async.send_ami_action 'ResumeMonitor', 'Channel' => call.channel do |complete_event|
66
66
  command.response = true
67
67
  end
68
68
  else
@@ -9,7 +9,7 @@ module Punchblock
9
9
  module StopByRedirect
10
10
  def execute_command(command)
11
11
  return super unless command.is_a?(Punchblock::Component::Stop)
12
- if @complete
12
+ if @complete
13
13
  command.response = ProtocolError.new.setup 'component-already-stopped', "Component #{id} is already stopped", call_id, id
14
14
  else
15
15
  stop_by_redirect Punchblock::Event::Complete::Stop.new
@@ -20,9 +20,9 @@ module Punchblock
20
20
  def stop_by_redirect(complete_reason)
21
21
  component_actor = current_actor
22
22
  call.register_handler :ami, lambda { |e| e['SubEvent'] == 'Start' }, :name => 'AsyncAGI' do |event|
23
- component_actor.send_complete_event! complete_reason
23
+ component_actor.async.send_complete_event complete_reason
24
24
  end
25
- call.redirect_back!
25
+ call.async.redirect_back
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class DTMFRecognizer
6
+ include Celluloid
7
+
8
+ finalizer :finalize
9
+
10
+ def initialize(responder, grammar, initial_timeout = nil, inter_digit_timeout = nil)
11
+ @responder = responder
12
+ self.grammar = grammar
13
+ self.initial_timeout = initial_timeout || -1
14
+ self.inter_digit_timeout = inter_digit_timeout || -1
15
+
16
+ @buffer = ""
17
+
18
+ begin_initial_timer @initial_timeout/1000 unless @initial_timeout == -1
19
+ end
20
+
21
+ def <<(digit)
22
+ @buffer << digit
23
+ cancel_initial_timer
24
+ case (match = @grammar.match @buffer.dup)
25
+ when RubySpeech::GRXML::Match
26
+ @responder.match match.mode, match.confidence, match.utterance, match.interpretation
27
+ when RubySpeech::GRXML::NoMatch
28
+ @responder.nomatch
29
+ when RubySpeech::GRXML::PotentialMatch
30
+ reset_inter_digit_timer
31
+ end
32
+ end
33
+
34
+ def finalize
35
+ cancel_initial_timer
36
+ cancel_inter_digit_timer
37
+ end
38
+
39
+ private
40
+
41
+ def grammar=(other)
42
+ @grammar = RubySpeech::GRXML.import other.to_s
43
+ @grammar.inline!
44
+ @grammar.tokenize!
45
+ @grammar.normalize_whitespace
46
+ end
47
+
48
+ def initial_timeout=(other)
49
+ raise OptionError, 'An initial timeout value that is negative (and not -1) is invalid.' if other < -1
50
+ @initial_timeout = other
51
+ end
52
+
53
+ def inter_digit_timeout=(other)
54
+ raise OptionError, 'An inter-digit timeout value that is negative (and not -1) is invalid.' if other < -1
55
+ @inter_digit_timeout = other
56
+ end
57
+
58
+ def begin_initial_timer(timeout)
59
+ @initial_timer = after timeout do
60
+ @responder.noinput
61
+ end
62
+ end
63
+
64
+ def cancel_initial_timer
65
+ return unless instance_variable_defined?(:@initial_timer) && @initial_timer
66
+ @initial_timer.cancel
67
+ @initial_timer = nil
68
+ end
69
+
70
+ def reset_inter_digit_timer
71
+ return if @inter_digit_timeout == -1
72
+ @inter_digit_timer ||= begin
73
+ after @inter_digit_timeout/1000 do
74
+ @responder.nomatch
75
+ end
76
+ end
77
+ @inter_digit_timer.reset
78
+ end
79
+
80
+ def cancel_inter_digit_timer
81
+ return unless instance_variable_defined?(:@inter_digit_timer) && @inter_digit_timer
82
+ @inter_digit_timer.cancel
83
+ @inter_digit_timer = nil
84
+ end
85
+ end
86
+ end
87
+ end