punchblock 0.10.0 → 0.11.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.
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