punchblock 1.8.0 → 1.8.1

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