punchblock 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG.md +12 -0
  2. data/lib/punchblock.rb +1 -0
  3. data/lib/punchblock/command/join.rb +6 -6
  4. data/lib/punchblock/command/unjoin.rb +6 -6
  5. data/lib/punchblock/command_node.rb +1 -0
  6. data/lib/punchblock/component/input.rb +24 -4
  7. data/lib/punchblock/component/output.rb +5 -1
  8. data/lib/punchblock/component/tropo/ask.rb +3 -1
  9. data/lib/punchblock/connection/xmpp.rb +28 -10
  10. data/lib/punchblock/event/joined.rb +6 -6
  11. data/lib/punchblock/event/unjoined.rb +6 -6
  12. data/lib/punchblock/media_container.rb +6 -5
  13. data/lib/punchblock/protocol_error.rb +5 -0
  14. data/lib/punchblock/rayo_node.rb +1 -1
  15. data/lib/punchblock/translator/asterisk.rb +3 -3
  16. data/lib/punchblock/translator/asterisk/call.rb +9 -3
  17. data/lib/punchblock/translator/asterisk/component.rb +35 -0
  18. data/lib/punchblock/translator/asterisk/component/asterisk.rb +1 -0
  19. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +14 -23
  20. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +5 -18
  21. data/lib/punchblock/translator/asterisk/component/asterisk/output.rb +94 -0
  22. data/lib/punchblock/version.rb +1 -1
  23. data/punchblock.gemspec +1 -0
  24. data/spec/punchblock/command/join_spec.rb +4 -4
  25. data/spec/punchblock/command/unjoin_spec.rb +4 -4
  26. data/spec/punchblock/component/input_spec.rb +28 -31
  27. data/spec/punchblock/component/output_spec.rb +23 -5
  28. data/spec/punchblock/component/tropo/ask_spec.rb +31 -34
  29. data/spec/punchblock/connection/xmpp_spec.rb +105 -3
  30. data/spec/punchblock/event/joined_spec.rb +4 -4
  31. data/spec/punchblock/event/unjoined_spec.rb +4 -4
  32. data/spec/punchblock/protocol_error_spec.rb +32 -1
  33. data/spec/punchblock/translator/asterisk/call_spec.rb +17 -3
  34. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +17 -0
  35. data/spec/punchblock/translator/asterisk/component/asterisk/output_spec.rb +489 -0
  36. data/spec/punchblock/translator/asterisk_spec.rb +14 -3
  37. metadata +53 -44
  38. data/assets/ozone/ask-1.0.xsd +0 -56
  39. data/assets/ozone/conference-1.0.xsd +0 -17
  40. data/assets/ozone/ozone-1.0.xsd +0 -127
  41. data/assets/ozone/say-1.0.xsd +0 -24
  42. data/assets/ozone/transfer-1.0.xsd +0 -32
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # develop
2
2
 
3
+ # v0.7.2 - 2011-12-28
4
+ * Feature: Allow sending commands to mixers easily
5
+ * Feature: Allow configuration of Rayo XMPP domains (root, call and mixer)
6
+ * Feature: Log Blather messages to the trace log level
7
+ * Feature: Return an error when trying to execute an Output on Asterisk with unsupported options set
8
+ * Feature: Add basic support for media output via MRCPSynth on Asterisk
9
+ * API change: Rename mixer_id to mixer_name to align with change to Rayo
10
+ * Bugfix: Handle and expose RubySpeech GRXML documents on Input/Ask properly
11
+ * Bugfix: Compare ProtocolErrors correctly
12
+ * Bugfix: Asterisk media output should default to Asterisk native output (STREAM FILE)
13
+ * Bugfix: An Output node's default max_time value should be nil rather than zero
14
+
3
15
  # v0.7.1 - 2011-11-24
4
16
  * [FEATURE] Add `Connection#not_ready!`, to instruct the server not to send any more offers.
5
17
  * [BUGFIX] Translate all exceptions raised by the XMPP connection into a ProtocolError
data/lib/punchblock.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  active_support/core_ext/module/delegation
5
5
  future-resource
6
6
  has_guarded_handlers
7
+ ruby_speech
7
8
  punchblock/core_ext/ruby
8
9
  }.each { |l| require l }
9
10
 
@@ -8,7 +8,7 @@ module Punchblock
8
8
  #
9
9
  # @param [Hash] options
10
10
  # @option options [String, Optional] :other_call_id the call ID to join
11
- # @option options [String, Optional] :mixer_id the mixer name to join
11
+ # @option options [String, Optional] :mixer_name the mixer name to join
12
12
  # @option options [Symbol, Optional] :direction the direction in which media should flow
13
13
  # @option options [Symbol, Optional] :media the method by which to negotiate media
14
14
  #
@@ -39,14 +39,14 @@ module Punchblock
39
39
 
40
40
  ##
41
41
  # @return [String] the mixer name to join
42
- def mixer_id
43
- read_attr :'mixer-id'
42
+ def mixer_name
43
+ read_attr :'mixer-name'
44
44
  end
45
45
 
46
46
  ##
47
47
  # @param [String] other the mixer name to join
48
- def mixer_id=(other)
49
- write_attr :'mixer-id', other
48
+ def mixer_name=(other)
49
+ write_attr :'mixer-name', other
50
50
  end
51
51
 
52
52
  ##
@@ -74,7 +74,7 @@ module Punchblock
74
74
  end
75
75
 
76
76
  def inspect_attributes # :nodoc:
77
- [:other_call_id, :mixer_id, :direction, :media] + super
77
+ [:other_call_id, :mixer_name, :direction, :media] + super
78
78
  end
79
79
  end # Join
80
80
  end # Command
@@ -8,7 +8,7 @@ module Punchblock
8
8
  #
9
9
  # @param [Hash] options
10
10
  # @option options [String, Optional] :other_call_id the call ID to unjoin
11
- # @option options [String, Optional] :mixer_id the mixer name to unjoin
11
+ # @option options [String, Optional] :mixer_name the mixer name to unjoin
12
12
  #
13
13
  # @return [Command::Unjoin] a formatted Rayo unjoin command
14
14
  #
@@ -32,18 +32,18 @@ module Punchblock
32
32
 
33
33
  ##
34
34
  # @return [String] the mixer name to unjoin
35
- def mixer_id
36
- read_attr :'mixer-id'
35
+ def mixer_name
36
+ read_attr :'mixer-name'
37
37
  end
38
38
 
39
39
  ##
40
40
  # @param [String] other the mixer name to unjoin
41
- def mixer_id=(other)
42
- write_attr :'mixer-id', other
41
+ def mixer_name=(other)
42
+ write_attr :'mixer-name', other
43
43
  end
44
44
 
45
45
  def inspect_attributes # :nodoc:
46
- [:other_call_id, :mixer_id] + super
46
+ [:other_call_id, :mixer_name] + super
47
47
  end
48
48
  end # Unjoin
49
49
  end # Command
@@ -5,6 +5,7 @@ module Punchblock
5
5
  def self.new(options = {})
6
6
  super().tap do |new_node|
7
7
  new_node.call_id = options[:call_id]
8
+ new_node.mixer_name = options[:mixer_name]
8
9
  new_node.component_id = options[:component_id]
9
10
  end
10
11
  end
@@ -208,7 +208,8 @@ module Punchblock
208
208
  # @return [Choices] the choices available
209
209
  #
210
210
  def grammar
211
- Grammar.new find_first('ns:grammar', :ns => self.class.registered_ns)
211
+ node = find_first 'ns:grammar', :ns => self.class.registered_ns
212
+ Grammar.new node if node
212
213
  end
213
214
 
214
215
  ##
@@ -217,6 +218,7 @@ module Punchblock
217
218
  # @option choices [String] :value the choices available
218
219
  #
219
220
  def grammar=(other)
221
+ return unless other
220
222
  remove_children :grammar
221
223
  grammar = Grammar.new(other) unless other.is_a?(Grammar)
222
224
  self << grammar
@@ -252,21 +254,29 @@ module Punchblock
252
254
  end
253
255
 
254
256
  ##
255
- # @param [String] content_type Defaults to the Voxeo Simple Grammar
257
+ # @param [String] content_type Defaults to GRXML
256
258
  #
257
259
  def content_type=(content_type)
258
- write_attr 'content-type', content_type || 'application/grammar+grxml'
260
+ write_attr 'content-type', content_type || grxml_content_type
259
261
  end
260
262
 
261
263
  ##
262
264
  # @return [String] the choices available
263
265
  def value
264
- content
266
+ if grxml?
267
+ RubySpeech::GRXML.import content
268
+ else
269
+ content
270
+ end
265
271
  end
266
272
 
267
273
  ##
268
274
  # @param [String] value the choices available
269
275
  def value=(value)
276
+ return unless value
277
+ if grxml? && !value.is_a?(RubySpeech::GRXML::Element)
278
+ value = RubySpeech::GRXML.import value
279
+ end
270
280
  Nokogiri::XML::Builder.with(self) do |xml|
271
281
  xml.cdata " #{value} "
272
282
  end
@@ -282,6 +292,16 @@ module Punchblock
282
292
  def inspect_attributes # :nodoc:
283
293
  [:content_type, :value] + super
284
294
  end
295
+
296
+ private
297
+
298
+ def grxml_content_type
299
+ 'application/grammar+grxml'
300
+ end
301
+
302
+ def grxml?
303
+ content_type == grxml_content_type
304
+ end
285
305
  end # Choices
286
306
 
287
307
  class Complete
@@ -109,7 +109,7 @@ module Punchblock
109
109
  # @return [String] the TTS voice to use
110
110
  #
111
111
  def max_time
112
- read_attr(:'max-time').to_i
112
+ read_attr :'max-time', :to_i
113
113
  end
114
114
 
115
115
  ##
@@ -119,6 +119,10 @@ module Punchblock
119
119
  write_attr :'max-time', other
120
120
  end
121
121
 
122
+ def inspect_attributes
123
+ super + [:interrupt_on, :start_offset, :start_paused, :repeat_interval, :repeat_times, :max_time]
124
+ end
125
+
122
126
  state_machine :state do
123
127
  event :paused do
124
128
  transition :executing => :paused
@@ -147,7 +147,8 @@ module Punchblock
147
147
  # @return [Choices] the choices available
148
148
  #
149
149
  def choices
150
- Choices.new find_first('ns:choices', :ns => self.class.registered_ns)
150
+ node = find_first 'ns:choices', :ns => self.class.registered_ns
151
+ Choices.new node if node
151
152
  end
152
153
 
153
154
  ##
@@ -156,6 +157,7 @@ module Punchblock
156
157
  # @option choices [String] :value the choices available
157
158
  #
158
159
  def choices=(choices)
160
+ return unless choices
159
161
  remove_children :choices
160
162
  choices = Choices.new(choices) unless choices.is_a?(Choices)
161
163
  self << choices
@@ -9,7 +9,7 @@ module Punchblock
9
9
  module Connection
10
10
  class XMPP < GenericConnection
11
11
  include Blather::DSL
12
- attr_accessor :event_handler
12
+ attr_accessor :event_handler, :root_domain, :calls_domain, :mixers_domain
13
13
 
14
14
  ##
15
15
  # Initialize the required connection attributes
@@ -27,7 +27,9 @@ module Punchblock
27
27
 
28
28
  setup *[:username, :password, :host, :port, :certs].map { |key| options.delete key }
29
29
 
30
- @rayo_domain = options[:rayo_domain] || Blather::JID.new(@username).domain
30
+ @root_domain = Blather::JID.new(options[:root_domain] || options[:rayo_domain] || @username).domain
31
+ @calls_domain = options[:calls_domain] || "calls.#{@root_domain}"
32
+ @mixers_domain = options[:mixers_domain] || "mixers.#{@root_domain}"
31
33
 
32
34
  @callmap = {} # This hash maps call IDs to their XMPP domain.
33
35
 
@@ -37,6 +39,7 @@ module Punchblock
37
39
  @ping_period = options.has_key?(:ping_period) ? options[:ping_period] : 60
38
40
 
39
41
  Blather.logger = pb_logger
42
+ Blather.default_log_level = :trace if Blather.respond_to? :default_log_level
40
43
 
41
44
  super()
42
45
  end
@@ -54,12 +57,11 @@ module Punchblock
54
57
  end
55
58
 
56
59
  def prep_command_for_execution(command, options = {})
57
- call_id, component_id = options.values_at :call_id, :component_id
58
- command.connection = self
59
- command.call_id = call_id
60
- jid = command.is_a?(Command::Dial) ? @rayo_domain : "#{call_id}@#{@callmap[call_id]}"
61
- jid << "/#{component_id}" if component_id
62
- create_iq(jid).tap do |iq|
60
+ command.connection = self
61
+ command.call_id ||= options[:call_id]
62
+ command.mixer_name ||= options[:mixer_name]
63
+ command.component_id ||= options[:component_id]
64
+ create_iq(jid_for_command(command)).tap do |iq|
63
65
  pb_logger.debug "Sending IQ ID #{iq.id} #{command.inspect} to #{jid}"
64
66
  iq << command
65
67
  end
@@ -102,9 +104,25 @@ module Punchblock
102
104
 
103
105
  private
104
106
 
107
+ def jid_for_command(command)
108
+ return root_domain if command.is_a?(Command::Dial)
109
+
110
+ if command.call_id
111
+ node = command.call_id
112
+ domain = @callmap[command.call_id] || calls_domain
113
+ elsif command.mixer_name
114
+ node = command.mixer_name
115
+ domain = @callmap[command.mixer_name] || mixers_domain
116
+ else
117
+ domain = calls_domain
118
+ end
119
+
120
+ Blather::JID.new(node, domain, command.component_id).to_s
121
+ end
122
+
105
123
  def send_presence(presence)
106
124
  status = Blather::Stanza::Presence::Status.new presence
107
- status.to = @rayo_domain
125
+ status.to = root_domain
108
126
  client.write status
109
127
  end
110
128
 
@@ -160,7 +178,7 @@ module Punchblock
160
178
  end
161
179
 
162
180
  def ping_rayo
163
- client.write_with_handler Blather::Stanza::Iq::Ping.new(:set, @rayo_domain) do |response|
181
+ client.write_with_handler Blather::Stanza::Iq::Ping.new(:set, root_domain) do |response|
164
182
  begin
165
183
  handle_error response if response.is_a? Blather::BlatherError
166
184
  rescue ProtocolError => e
@@ -8,7 +8,7 @@ module Punchblock
8
8
  #
9
9
  # @param [Hash] options
10
10
  # @option options [String, Optional] :other_call_id the call ID that was joined
11
- # @option options [String, Optional] :mixer_id the mixer name that was joined
11
+ # @option options [String, Optional] :mixer_name the mixer name that was joined
12
12
  #
13
13
  # @return [Event::Joined] a formatted Rayo joined event
14
14
  #
@@ -32,18 +32,18 @@ module Punchblock
32
32
 
33
33
  ##
34
34
  # @return [String] the mixer name that was joined
35
- def mixer_id
36
- read_attr :'mixer-id'
35
+ def mixer_name
36
+ read_attr :'mixer-name'
37
37
  end
38
38
 
39
39
  ##
40
40
  # @param [String] other the mixer name that was joined
41
- def mixer_id=(other)
42
- write_attr :'mixer-id', other
41
+ def mixer_name=(other)
42
+ write_attr :'mixer-name', other
43
43
  end
44
44
 
45
45
  def inspect_attributes # :nodoc:
46
- [:other_call_id, :mixer_id] + super
46
+ [:other_call_id, :mixer_name] + super
47
47
  end
48
48
  end # Joined
49
49
  end
@@ -8,7 +8,7 @@ module Punchblock
8
8
  #
9
9
  # @param [Hash] options
10
10
  # @option options [String, Optional] :other_call_id the call ID that was unjoined
11
- # @option options [String, Optional] :mixer_id the mixer name that was unjoined
11
+ # @option options [String, Optional] :mixer_name the mixer name that was unjoined
12
12
  #
13
13
  # @return [Event::Unjoined] a formatted Rayo unjoined event
14
14
  #
@@ -32,18 +32,18 @@ module Punchblock
32
32
 
33
33
  ##
34
34
  # @return [String] the mixer name that was unjoined
35
- def mixer_id
36
- read_attr :'mixer-id'
35
+ def mixer_name
36
+ read_attr :'mixer-name'
37
37
  end
38
38
 
39
39
  ##
40
40
  # @param [String] other the mixer name that was unjoined
41
- def mixer_id=(other)
42
- write_attr :'mixer-id', other
41
+ def mixer_name=(other)
42
+ write_attr :'mixer-name', other
43
43
  end
44
44
 
45
45
  def inspect_attributes # :nodoc:
46
- [:other_call_id, :mixer_id] + super
46
+ [:other_call_id, :mixer_name] + super
47
47
  end
48
48
  end # Unjoined
49
49
  end
@@ -18,18 +18,19 @@ module Punchblock
18
18
  # @return [String] the SSML document to render TTS
19
19
  #
20
20
  def ssml
21
- children.to_xml
21
+ node = children.first
22
+ RubySpeech::SSML.import node if node
22
23
  end
23
24
 
24
25
  ##
25
26
  # @param [String] ssml the SSML document to render TTS
26
27
  #
27
28
  def ssml=(ssml)
28
- if ssml.instance_of?(String)
29
- self << RayoNode.new('').parse(ssml) do |config|
30
- config.noblanks.strict
31
- end
29
+ return unless ssml
30
+ unless ssml.is_a?(RubySpeech::SSML::Element)
31
+ ssml = RubySpeech::SSML.import ssml
32
32
  end
33
+ self << ssml
33
34
  end
34
35
 
35
36
  def inspect_attributes # :nodoc:
@@ -12,5 +12,10 @@ module Punchblock
12
12
  "#<#{self.class}: name=#{name.inspect} text=#{text.inspect} call_id=#{call_id.inspect} component_id=#{component_id.inspect}>"
13
13
  end
14
14
  alias :inspect :to_s
15
+
16
+ def eql?(other)
17
+ other.is_a?(self.class) && [:name, :text, :call_id, :component_id].all? { |f| self.__send__(f) == other.__send__(f) }
18
+ end
19
+ alias :== :eql?
15
20
  end
16
21
  end
@@ -7,7 +7,7 @@ module Punchblock
7
7
 
8
8
  class_attribute :registered_ns, :registered_name
9
9
 
10
- attr_accessor :call_id, :component_id, :domain, :connection, :client, :original_component
10
+ attr_accessor :call_id, :mixer_name, :component_id, :domain, :connection, :client, :original_component
11
11
 
12
12
  # Register a new stanza class to a name and/or namespace
13
13
  #
@@ -11,11 +11,11 @@ module Punchblock
11
11
  autoload :Call
12
12
  autoload :Component
13
13
 
14
- attr_reader :ami_client, :connection, :calls
14
+ attr_reader :ami_client, :connection, :media_engine, :calls
15
15
 
16
- def initialize(ami_client, connection)
16
+ def initialize(ami_client, connection, media_engine = nil)
17
17
  pb_logger.debug "Starting up..."
18
- @ami_client, @connection = ami_client, connection
18
+ @ami_client, @connection, @media_engine = ami_client, connection, media_engine
19
19
  @calls, @components, @channel_to_call_id = {}, {}, {}
20
20
  @fully_booted_count = 0
21
21
  end
@@ -64,12 +64,14 @@ module Punchblock
64
64
  end
65
65
  when Punchblock::Component::Asterisk::AGI::Command
66
66
  execute_agi_command command
67
+ when Punchblock::Component::Output
68
+ execute_component Component::Asterisk::Output, command
67
69
  end
68
70
  end
69
71
 
70
- def send_agi_action(command, &block)
72
+ def send_agi_action(command, *params, &block)
71
73
  pb_logger.debug "Sending AGI action #{command}"
72
- @current_agi_command = Punchblock::Component::Asterisk::AGI::Command.new :name => command, :call_id => id
74
+ @current_agi_command = Punchblock::Component::Asterisk::AGI::Command.new :name => command, :params => params, :call_id => id
73
75
  @current_agi_command.request!
74
76
  @current_agi_command.register_handler :internal, Punchblock::Event::Complete do |e|
75
77
  pb_logger.debug "AGI action received complete event #{e.inspect}"
@@ -89,7 +91,11 @@ module Punchblock
89
91
  private
90
92
 
91
93
  def execute_agi_command(command)
92
- Component::Asterisk::AGICommand.new(command, current_actor).tap do |component|
94
+ execute_component Component::Asterisk::AGICommand, command
95
+ end
96
+
97
+ def execute_component(type, command)
98
+ type.new(command, current_actor).tap do |component|
93
99
  register_component component
94
100
  component.execute!
95
101
  end