punchblock 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # [develop](https://github.com/adhearsion/punchblock)
2
2
 
3
+ # [v0.11.0](https://github.com/adhearsion/punchblock/compare/v0.10.0...v0.11.0) - [2012-03-29](https://rubygems.org/gems/punchblock/versions/0.11.0)
4
+ * Feature: Input & Output components on Asterisk now responds to a Stop command
5
+ * Feature: started/stopped-speaking events are now handled
6
+ * Bugfix: Asterisk output component considers an SSML doc w/ a string node w/o spaces to be a filename
7
+ * Bugfix: `ProtocolError` should behave like a normal exception, just with extra attributes
8
+
3
9
  # [v0.10.0](https://github.com/adhearsion/punchblock/compare/v0.9.2...v0.10.0) - [2012-03-19](https://rubygems.org/gems/punchblock/versions/0.10.0)
4
10
  * Feature: app_swift is now supported on Asterisk with a media_engine type of :swift
5
11
  * Feature: Asterisk calls now support the Join API
@@ -141,7 +141,7 @@ module Punchblock
141
141
 
142
142
  def handle_error(iq, command = nil)
143
143
  e = Blather::StanzaError.import iq
144
- protocol_error = ProtocolError.new e.name, e.text, iq.call_id, iq.component_id
144
+ protocol_error = ProtocolError.new.setup e.name, e.text, iq.call_id, iq.component_id
145
145
  command.response = protocol_error if command
146
146
  end
147
147
 
@@ -170,7 +170,7 @@ module Punchblock
170
170
  begin
171
171
  handle_error response if response.is_a? Blather::BlatherError
172
172
  rescue ProtocolError => e
173
- raise e unless e.name == :feature_not_implemented
173
+ raise unless e.name == :feature_not_implemented
174
174
  end
175
175
  end
176
176
  end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ class Event
5
+ module ActiveSpeaker
6
+ def other_call_id
7
+ read_attr :'call-id'
8
+ end
9
+
10
+ def other_call_id=(other)
11
+ write_attr :'call-id', other
12
+ end
13
+
14
+ def inspect_attributes # :nodoc:
15
+ [:other_call_id] + super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'punchblock/event/active_speaker'
4
+
5
+ module Punchblock
6
+ class Event
7
+ class StartedSpeaking < Event
8
+ register :'started-speaking', :core
9
+
10
+ include ActiveSpeaker
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'punchblock/event/active_speaker'
4
+
5
+ module Punchblock
6
+ class Event
7
+ class StoppedSpeaking < Event
8
+ register :'stopped-speaking', :core
9
+
10
+ include ActiveSpeaker
11
+ end
12
+ end
13
+ end
@@ -25,4 +25,6 @@ end
25
25
  offer
26
26
  ringing
27
27
  unjoined
28
+ started_speaking
29
+ stopped_speaking
28
30
  }.each { |e| require "punchblock/event/#{e}"}
@@ -6,8 +6,9 @@ module Punchblock
6
6
  class ProtocolError < StandardError
7
7
  attr_accessor :name, :text, :call_id, :component_id
8
8
 
9
- def initialize(name = nil, text = nil, call_id = nil, component_id = nil)
9
+ def setup(name = nil, text = nil, call_id = nil, component_id = nil)
10
10
  @name, @text, @call_id, @component_id = name, text, call_id, component_id
11
+ self
11
12
  end
12
13
 
13
14
  def to_s
@@ -64,6 +64,7 @@ module Punchblock
64
64
  def to_s
65
65
  "#<#{self.class}:#{id} Channel: #{channel.inspect}>"
66
66
  end
67
+ alias :inspect :to_s
67
68
 
68
69
  def dial(dial_command)
69
70
  @direction = :outbound
@@ -163,7 +164,7 @@ module Punchblock
163
164
  if component = component_with_id(command.component_id)
164
165
  component.execute_command! command
165
166
  else
166
- command.response = ProtocolError.new 'component-not-found', "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
167
+ command.response = ProtocolError.new.setup 'component-not-found', "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
167
168
  end
168
169
  end
169
170
  case command
@@ -199,7 +200,7 @@ module Punchblock
199
200
  when Punchblock::Component::Input
200
201
  execute_component Component::Input, command
201
202
  else
202
- command.response = ProtocolError.new 'command-not-acceptable', "Did not understand command for call #{id}", id
203
+ command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
203
204
  end
204
205
  end
205
206
 
@@ -226,8 +227,6 @@ module Punchblock
226
227
  "#{self.class}: #{id}"
227
228
  end
228
229
 
229
- private
230
-
231
230
  def redirect_back(other_call = nil)
232
231
  redirect_options = {
233
232
  'Channel' => channel,
@@ -244,6 +243,8 @@ module Punchblock
244
243
  send_ami_action 'Redirect', redirect_options
245
244
  end
246
245
 
246
+ private
247
+
247
248
  def send_end_event(reason)
248
249
  send_pb_event Event::End.new(:reason => reason)
249
250
  after(5) { shutdown }
@@ -62,6 +62,16 @@ module Punchblock
62
62
  end
63
63
  end
64
64
 
65
+ def execute_command(command)
66
+ case command
67
+ when Punchblock::Component::Stop
68
+ command.response = true
69
+ complete Punchblock::Event::Complete::Stop.new
70
+ else
71
+ super
72
+ end
73
+ end
74
+
65
75
  private
66
76
 
67
77
  def begin_initial_timer(timeout)
@@ -7,6 +7,7 @@ module Punchblock
7
7
  class Asterisk
8
8
  module Component
9
9
  class Output < Component
10
+ include StopByRedirect
10
11
 
11
12
  def setup
12
13
  @media_engine = @call.translator.media_engine
@@ -31,8 +32,11 @@ module Punchblock
31
32
  case node
32
33
  when RubySpeech::SSML::Audio
33
34
  lambda { current_actor.play_audio! node.src }
35
+ when String
36
+ return unrenderable_doc_error if node.include?(' ')
37
+ lambda { current_actor.play_audio! node }
34
38
  else
35
- return with_error 'unrenderable document error', 'The provided document could not be rendered.'
39
+ return unrenderable_doc_error
36
40
  end
37
41
  end.compact
38
42
 
@@ -53,18 +57,20 @@ module Punchblock
53
57
  end
54
58
  when :unimrcp
55
59
  send_ref
60
+ output_component = current_actor
56
61
  @call.send_agi_action! 'EXEC MRCPSynth', escaped_doc, mrcpsynth_options do |complete_event|
57
62
  pb_logger.debug "MRCPSynth completed with #{complete_event}."
58
- send_complete_event success_reason
63
+ output_component.send_complete_event! success_reason
59
64
  end
60
65
  when :swift
61
66
  doc = escaped_doc
62
67
  doc << "|1|1" if [:any, :dtmf].include? @component_node.interrupt_on
63
68
  doc.insert 0, "#{@component_node.voice}^" if @component_node.voice
64
69
  send_ref
70
+ output_component = current_actor
65
71
  @call.send_agi_action! 'EXEC Swift', doc do |complete_event|
66
72
  pb_logger.debug "Swift completed with #{complete_event}."
67
- send_complete_event success_reason
73
+ output_component.send_complete_event! success_reason
68
74
  end
69
75
  end
70
76
  end
@@ -93,6 +99,10 @@ module Punchblock
93
99
 
94
100
  private
95
101
 
102
+ def unrenderable_doc_error
103
+ with_error 'unrenderable document error', 'The provided document could not be rendered.'
104
+ end
105
+
96
106
  def escaped_doc
97
107
  @component_node.ssml.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }
98
108
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_support/core_ext/string/filters'
4
+
5
+ module Punchblock
6
+ module Translator
7
+ class Asterisk
8
+ module Component
9
+ module StopByRedirect
10
+ def execute_command(command)
11
+ return super unless command.is_a?(Punchblock::Component::Stop)
12
+
13
+ component_actor = current_actor
14
+ call.register_handler :ami, lambda { |e| e['SubEvent'] == 'Start' }, :name => 'AsyncAGI' do |event|
15
+ component_actor.send_complete_event! Punchblock::Event::Complete::Stop.new
16
+ end
17
+ command.response = true
18
+ call.redirect_back!
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -9,6 +9,7 @@ module Punchblock
9
9
  autoload :Asterisk
10
10
  autoload :Input
11
11
  autoload :Output
12
+ autoload :StopByRedirect
12
13
 
13
14
  class Component
14
15
  include Celluloid
@@ -27,7 +28,7 @@ module Punchblock
27
28
  end
28
29
 
29
30
  def execute_command(command)
30
- command.response = ProtocolError.new 'command-not-acceptable', "Did not understand command for component #{id}", call_id, id
31
+ command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for component #{id}", call_id, id
31
32
  end
32
33
 
33
34
  def send_complete_event(reason)
@@ -73,7 +74,7 @@ module Punchblock
73
74
  end
74
75
 
75
76
  def with_error(name, text)
76
- set_node_response ProtocolError.new(name, text)
77
+ set_node_response ProtocolError.new.setup(name, text)
77
78
  end
78
79
  end
79
80
  end
@@ -98,7 +98,7 @@ module Punchblock
98
98
  if call = call_with_id(command.call_id)
99
99
  call.execute_command! command
100
100
  else
101
- command.response = ProtocolError.new 'call-not-found', "Could not find a call with ID #{command.call_id}", command.call_id
101
+ command.response = ProtocolError.new.setup 'call-not-found', "Could not find a call with ID #{command.call_id}", command.call_id
102
102
  end
103
103
  end
104
104
 
@@ -106,7 +106,7 @@ module Punchblock
106
106
  if (component = component_with_id(command.component_id))
107
107
  component.execute_command! command
108
108
  else
109
- command.response = ProtocolError.new 'component-not-found', "Could not find a component with ID #{command.component_id}", command.call_id, command.component_id
109
+ command.response = ProtocolError.new.setup 'component-not-found', "Could not find a component with ID #{command.component_id}", command.call_id, command.component_id
110
110
  end
111
111
  end
112
112
 
@@ -121,7 +121,7 @@ module Punchblock
121
121
  register_call call
122
122
  call.dial! command
123
123
  else
124
- command.response = ProtocolError.new 'command-not-acceptable', "Did not understand command"
124
+ command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command"
125
125
  end
126
126
  end
127
127
 
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Punchblock
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ class Event
7
+ describe StartedSpeaking do
8
+ it 'registers itself' do
9
+ RayoNode.class_from_registration(:'started-speaking', 'urn:xmpp:rayo:1').should be == StartedSpeaking
10
+ end
11
+
12
+ describe "from a stanza" do
13
+ let :stanza do
14
+ '<started-speaking xmlns="urn:xmpp:rayo:1" call-id="x0yz4ye-lx7-6ai9njwvw8nsb"/>'
15
+ end
16
+
17
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
18
+
19
+ it { should be_instance_of StartedSpeaking }
20
+
21
+ it_should_behave_like 'event'
22
+
23
+ its(:other_call_id) { should be == "x0yz4ye-lx7-6ai9njwvw8nsb" }
24
+ its(:xmlns) { should be == 'urn:xmpp:rayo:1' }
25
+ end
26
+
27
+ describe "when setting options in initializer" do
28
+ subject do
29
+ StartedSpeaking.new :other_call_id => 'abc123'
30
+ end
31
+
32
+ its(:other_call_id) { should be == 'abc123' }
33
+ end
34
+ end
35
+ end
36
+ end # Punchblock
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ class Event
7
+ describe StoppedSpeaking do
8
+ it 'registers itself' do
9
+ RayoNode.class_from_registration(:'stopped-speaking', 'urn:xmpp:rayo:1').should be == StoppedSpeaking
10
+ end
11
+
12
+ describe "from a stanza" do
13
+ let :stanza do
14
+ '<stopped-speaking xmlns="urn:xmpp:rayo:1" call-id="x0yz4ye-lx7-6ai9njwvw8nsb"/>'
15
+ end
16
+
17
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
18
+
19
+ it { should be_instance_of StoppedSpeaking }
20
+
21
+ it_should_behave_like 'event'
22
+
23
+ its(:other_call_id) { should be == "x0yz4ye-lx7-6ai9njwvw8nsb" }
24
+ its(:xmlns) { should be == 'urn:xmpp:rayo:1' }
25
+ end
26
+
27
+ describe "when setting options in initializer" do
28
+ subject do
29
+ StoppedSpeaking.new :other_call_id => 'abc123'
30
+ end
31
+
32
+ its(:other_call_id) { should be == 'abc123' }
33
+ end
34
+ end
35
+ end
36
+ end # Punchblock
@@ -8,33 +8,81 @@ module Punchblock
8
8
  let(:text) { 'Could not find call [id=f6d437f4-1e18-457b-99f8-b5d853f50347]' }
9
9
  let(:call_id) { 'f6d437f4-1e18-457b-99f8-b5d853f50347' }
10
10
  let(:component_id) { 'abc123' }
11
- subject { ProtocolError.new name, text, call_id, component_id }
11
+ subject { ProtocolError.new.setup name, text, call_id, component_id }
12
12
 
13
13
  its(:inspect) { should be == '#<Punchblock::ProtocolError: name=:item_not_found text="Could not find call [id=f6d437f4-1e18-457b-99f8-b5d853f50347]" call_id="f6d437f4-1e18-457b-99f8-b5d853f50347" component_id="abc123">' }
14
14
 
15
+ describe ".exception" do
16
+ context "with no arguments" do
17
+ it "returns the original object" do
18
+ ProtocolError.exception.should be == ProtocolError.new
19
+ end
20
+ end
21
+
22
+ context "with self as the argument" do
23
+ it "returns the original object" do
24
+ ProtocolError.exception(subject).should be == ProtocolError.new(subject.to_s)
25
+ end
26
+ end
27
+
28
+ context "with other values" do
29
+ it "returns a new object with the appropriate values" do
30
+ e = ProtocolError.exception 'FooBar'
31
+ e.name.should == nil
32
+ e.text.should == nil
33
+ e.call_id.should == nil
34
+ e.component_id.should == nil
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "#exception" do
40
+ context "with no arguments" do
41
+ it "returns the original object" do
42
+ subject.exception.should be subject
43
+ end
44
+ end
45
+
46
+ context "with self as the argument" do
47
+ it "returns the original object" do
48
+ subject.exception(subject).should be subject
49
+ end
50
+ end
51
+
52
+ context "with other values" do
53
+ it "returns a new object with the appropriate values" do
54
+ e = subject.exception("Boo")
55
+ e.name.should == name
56
+ e.text.should == text
57
+ e.call_id.should == call_id
58
+ e.component_id.should == component_id
59
+ end
60
+ end
61
+ end
62
+
15
63
  describe "comparison" do
16
64
  context "with the same name, text, call ID and component ID" do
17
- let(:comparison) { ProtocolError.new name, text, call_id, component_id }
65
+ let(:comparison) { ProtocolError.new.setup name, text, call_id, component_id }
18
66
  it { should be == comparison }
19
67
  end
20
68
 
21
69
  context "with a different name" do
22
- let(:comparison) { ProtocolError.new :foo, text, call_id, component_id }
70
+ let(:comparison) { ProtocolError.new.setup :foo, text, call_id, component_id }
23
71
  it { should_not be == comparison }
24
72
  end
25
73
 
26
74
  context "with a different text" do
27
- let(:comparison) { ProtocolError.new name, 'foo', call_id, component_id }
75
+ let(:comparison) { ProtocolError.new.setup name, 'foo', call_id, component_id }
28
76
  it { should_not be == comparison }
29
77
  end
30
78
 
31
79
  context "with a different call ID" do
32
- let(:comparison) { ProtocolError.new name, text, 'foo', component_id }
80
+ let(:comparison) { ProtocolError.new.setup name, text, 'foo', component_id }
33
81
  it { should_not be == comparison }
34
82
  end
35
83
 
36
84
  context "with a different component ID" do
37
- let(:comparison) { ProtocolError.new name, text, call_id, 'foo' }
85
+ let(:comparison) { ProtocolError.new.setup name, text, call_id, 'foo' }
38
86
  it { should_not be == comparison }
39
87
  end
40
88
  end
@@ -745,7 +745,7 @@ module Punchblock
745
745
  context "for an unknown component ID" do
746
746
  it 'sends an error in response to the command' do
747
747
  subject.execute_command command
748
- command.response.should be == ProtocolError.new('component-not-found', "Could not find a component with ID #{component_id} for call #{subject.id}", subject.id, component_id)
748
+ command.response.should be == ProtocolError.new.setup('component-not-found', "Could not find a component with ID #{component_id} for call #{subject.id}", subject.id, component_id)
749
749
  end
750
750
  end
751
751
  end
@@ -757,7 +757,7 @@ module Punchblock
757
757
 
758
758
  it 'sends an error in response to the command' do
759
759
  subject.execute_command command
760
- command.response.should be == ProtocolError.new('command-not-acceptable', "Did not understand command for call #{subject.id}", subject.id)
760
+ command.response.should be == ProtocolError.new.setup('command-not-acceptable', "Did not understand command for call #{subject.id}", subject.id)
761
761
  end
762
762
  end
763
763
 
@@ -848,6 +848,37 @@ module Punchblock
848
848
  subject.send_ami_action 'foo', :foo => :bar
849
849
  end
850
850
  end
851
+
852
+ describe '#redirect_back' do
853
+ let(:other_channel) { 'SIP/bar' }
854
+ let :other_call do
855
+ Call.new other_channel, translator
856
+ end
857
+
858
+ it "executes the proper AMI action with only the subject call" do
859
+ subject.redirect_back
860
+ ami_action = subject.wrapped_object.instance_variable_get(:'@current_ami_action')
861
+ ami_action.name.should be == "redirect"
862
+ ami_action.headers['Channel'].should be == channel
863
+ ami_action.headers['Exten'].should be == Punchblock::Translator::Asterisk::REDIRECT_EXTENSION
864
+ ami_action.headers['Priority'].should be == Punchblock::Translator::Asterisk::REDIRECT_PRIORITY
865
+ ami_action.headers['Context'].should be == Punchblock::Translator::Asterisk::REDIRECT_CONTEXT
866
+ end
867
+
868
+ it "executes the proper AMI action with another call specified" do
869
+ subject.redirect_back other_call
870
+ ami_action = subject.wrapped_object.instance_variable_get(:'@current_ami_action')
871
+ ami_action.name.should be == "redirect"
872
+ ami_action.headers['Channel'].should be == channel
873
+ ami_action.headers['Exten'].should be == Punchblock::Translator::Asterisk::REDIRECT_EXTENSION
874
+ ami_action.headers['Priority'].should be == Punchblock::Translator::Asterisk::REDIRECT_PRIORITY
875
+ ami_action.headers['Context'].should be == Punchblock::Translator::Asterisk::REDIRECT_CONTEXT
876
+ ami_action.headers['ExtraChannel'].should be == other_channel
877
+ ami_action.headers['ExtraExten'].should be == Punchblock::Translator::Asterisk::REDIRECT_EXTENSION
878
+ ami_action.headers['ExtraPriority'].should be == Punchblock::Translator::Asterisk::REDIRECT_PRIORITY
879
+ ami_action.headers['ExtraContext'].should be == Punchblock::Translator::Asterisk::REDIRECT_CONTEXT
880
+ end
881
+ end
851
882
  end
852
883
  end
853
884
  end