punchblock 0.7.1 → 0.7.2

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 (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