adhearsion 2.0.0.alpha3 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/CHANGELOG.md +13 -1
  2. data/README.markdown +1 -1
  3. data/features/cli_daemon.feature +2 -3
  4. data/features/step_definitions/cli_steps.rb +0 -1
  5. data/lib/adhearsion/call.rb +50 -32
  6. data/lib/adhearsion/call_controller.rb +53 -4
  7. data/lib/adhearsion/call_controller/dial.rb +26 -6
  8. data/lib/adhearsion/call_controller/input.rb +1 -1
  9. data/lib/adhearsion/call_controller/menu.rb +2 -2
  10. data/lib/adhearsion/call_controller/output.rb +11 -11
  11. data/lib/adhearsion/call_controller/utility.rb +3 -3
  12. data/lib/adhearsion/calls.rb +0 -4
  13. data/lib/adhearsion/cli_commands.rb +2 -3
  14. data/lib/adhearsion/console.rb +42 -14
  15. data/lib/adhearsion/foundation/thread_safety.rb +8 -2
  16. data/lib/adhearsion/generators/app/templates/Rakefile +1 -4
  17. data/lib/adhearsion/initializer.rb +12 -5
  18. data/lib/adhearsion/logging.rb +23 -4
  19. data/lib/adhearsion/process.rb +11 -2
  20. data/lib/adhearsion/punchblock_plugin.rb +8 -0
  21. data/lib/adhearsion/punchblock_plugin/initializer.rb +11 -4
  22. data/lib/adhearsion/version.rb +1 -1
  23. data/spec/adhearsion/call_controller/dial_spec.rb +123 -85
  24. data/spec/adhearsion/call_controller/output_spec.rb +10 -0
  25. data/spec/adhearsion/call_controller_spec.rb +59 -52
  26. data/spec/adhearsion/call_spec.rb +47 -3
  27. data/spec/adhearsion/console_spec.rb +10 -1
  28. data/spec/adhearsion/initializer_spec.rb +5 -5
  29. data/spec/adhearsion/logging_spec.rb +17 -1
  30. data/spec/adhearsion/process_spec.rb +18 -0
  31. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +20 -8
  32. data/spec/adhearsion/punchblock_plugin_spec.rb +42 -0
  33. data/spec/spec_helper.rb +5 -1
  34. metadata +64 -64
@@ -1,3 +1,14 @@
1
+ # 2.0.0.beta1 - 2012-03-07
2
+ * Bugfix: #speak now correctly casts the argument to string if it is not SSML
3
+ * Bugfix: The console pauses controllers on a call while taking control
4
+ * Feature: Reopen logfiles on SIGHUP
5
+ * Feature: Toggle :trace logging on SIGALRM (useful for debugging a live process)
6
+ * Feature: It is now possible to execute a global component (using `Adhearsion::PunchblockPlugin.execute_component`)
7
+ * Feature: Now set XMPP JID resource to a concatenation of hostname and process ID for ID/debugging purposes
8
+ * Feature: CallController#dial now returns a DialStatus object indicating the status of the dial command
9
+ * Feature: Punchblock plugin can now configure the active media engine (mostly for use on Asterisk)
10
+ * Bugfix: Fix forcing Adhearsion to stop with enough SIGTERM or CTRL+C
11
+
1
12
  # 2.0.0.alpha3 - 2012-02-21
2
13
  * Feature: Add `ahn generate` command to allow invocation of generators
3
14
  * Feature: Add simple generator for call controllers
@@ -10,11 +21,12 @@
10
21
  * Feature: The console can take control of a call
11
22
  * Bugfix: CallController#dial now blocks until all outbound calls complete
12
23
  * Bugfix: Call commands timing out now raise a timeout exception in the caller, but do not crash the actor
24
+ * Feature: It is now possible to pause/resume call controllers
13
25
  * Bugfix: CallController#dial now unblocks immediately if the original call ends
14
26
  * Bugfix: CallController#dial now unblocks when the connected outbound call unjoins, rather than ending, incase post-processing on the outbound call is required
15
27
  * Bugfix: CallController#dial now hangs up outbound legs when it unblocks
16
28
  * Feature: CallController#dial now defaults the outbound caller ID to that of the controller's call
17
- * Change: The command to take control of a call is now 'take' rather than 'use'. 'take' called without a call ID present a list of currently running calls
29
+ * Change: The command to take control of a call is now 'take' rather than 'use'. 'take' called without a call ID present a list of currently running calls
18
30
 
19
31
  # 2.0.0.alpha2 - 2012-01-30
20
32
  * Change: Plugins no longer load dialplan/event/rpc/console methods using corresponding class methods
@@ -20,7 +20,7 @@ Features
20
20
  Requirements
21
21
  ------------
22
22
 
23
- * Ruby 1.9.2+ or JRuby 1.6.5+
23
+ * Ruby 1.9.2+ or JRuby 1.6.7+
24
24
  * A VoIP platform:
25
25
  * Asterisk 1.8+
26
26
  * Prism 11+ with rayo-server
@@ -9,8 +9,7 @@ Feature: Adhearsion Ahn CLI (daemon)
9
9
  When I run `ahn daemon path/somewhere`
10
10
  And I cd to "path/somewhere"
11
11
  And I terminate the process using the pid file "adhearsion.pid"
12
- Then the output should contain "Daemonizing now"
13
- And the exit status should be 0
12
+ Then the exit status should be 0
14
13
 
15
14
  Scenario: Command daemon with pid option
16
15
  Given JRuby skip test
@@ -18,4 +17,4 @@ Feature: Adhearsion Ahn CLI (daemon)
18
17
  When I run `ahn daemon path/somewhere --pid-file=ahn.pid`
19
18
  And I cd to "path/somewhere"
20
19
  And I terminate the process using the pid file "ahn.pid"
21
- Then the output should contain "Daemonizing now"
20
+ Then the exit status should be 0
@@ -69,6 +69,5 @@ When /^I terminate the process using the pid file "([^"]*)"$/ do |pidfile|
69
69
  pid = File.read(pidfile).to_i
70
70
  Process.kill("TERM", pid)
71
71
  sleep 1
72
- Process.kill("KILL", pid)
73
72
  end
74
73
  end
@@ -1,33 +1,27 @@
1
1
  require 'thread'
2
2
 
3
- module Celluloid
4
- module ClassMethods
5
- def ===(other)
6
- other.kind_of? self
7
- end
8
- end
9
-
10
- class ActorProxy
11
- def is_a?(klass)
12
- Actor.call @mailbox, :is_a?, klass
13
- end
14
-
15
- def kind_of?(klass)
16
- Actor.call @mailbox, :kind_of?, klass
17
- end
18
- end
19
- end
20
-
21
3
  module Adhearsion
22
4
  ##
23
5
  # Encapsulates call-related data and behavior.
24
6
  #
25
7
  class Call
26
8
 
9
+ ExpiredError = Class.new Celluloid::DeadActorError
10
+
27
11
  include Celluloid
28
12
  include HasGuardedHandlers
29
13
 
30
- attr_accessor :offer, :client, :end_reason, :commands, :variables
14
+ def self.new(*args, &block)
15
+ super.tap do |proxy|
16
+ def proxy.method_missing(*args)
17
+ super
18
+ rescue Celluloid::DeadActorError => e
19
+ raise ExpiredError, "This call is expired and is no longer accessible"
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_accessor :offer, :client, :end_reason, :commands, :variables, :controllers
31
25
 
32
26
  delegate :[], :[]=, :to => :variables
33
27
  delegate :to, :from, :to => :offer, :allow_nil => true
@@ -35,9 +29,10 @@ module Adhearsion
35
29
  def initialize(offer = nil)
36
30
  register_initial_handlers
37
31
 
38
- @tags = []
39
- @commands = CommandRegistry.new
40
- @variables = {}
32
+ @tags = []
33
+ @commands = CommandRegistry.new
34
+ @variables = {}
35
+ @controllers = []
41
36
 
42
37
  self << offer if offer
43
38
  end
@@ -77,7 +72,7 @@ module Adhearsion
77
72
 
78
73
  alias << deliver_message
79
74
 
80
- def register_initial_handlers
75
+ def register_initial_handlers # :nodoc:
81
76
  register_event_handler Punchblock::Event::Offer do |offer|
82
77
  @offer = offer
83
78
  @client = offer.client
@@ -93,10 +88,14 @@ module Adhearsion
93
88
  clear_from_active_calls
94
89
  @end_reason = event.reason
95
90
  commands.terminate
96
- after(5) { current_actor.terminate! }
91
+ after(after_end_hold_time) { current_actor.terminate! }
97
92
  end
98
93
  end
99
94
 
95
+ def after_end_hold_time # :nodoc:
96
+ 30
97
+ end
98
+
100
99
  def on_end(&block)
101
100
  register_event_handler Punchblock::Event::End do |event|
102
101
  block.call event
@@ -126,7 +125,7 @@ module Adhearsion
126
125
  write_and_await_response Punchblock::Command::Hangup.new(:headers => headers)
127
126
  end
128
127
 
129
- def clear_from_active_calls
128
+ def clear_from_active_calls # :nodoc:
130
129
  Adhearsion.active_calls.remove_inactive_call current_actor
131
130
  end
132
131
 
@@ -180,17 +179,17 @@ module Adhearsion
180
179
  end
181
180
 
182
181
  def write_command(command)
183
- abort Hangup.new unless active? || command.is_a?(Punchblock::Command::Hangup)
182
+ abort Hangup.new(@end_reason) unless active? || command.is_a?(Punchblock::Command::Hangup)
184
183
  variables.merge! command.headers_hash if command.respond_to? :headers_hash
185
- logger.trace "Executing command #{command.inspect}"
184
+ logger.debug "Executing command #{command.inspect}"
186
185
  client.execute_command command, :call_id => id
187
186
  end
188
187
 
189
- def logger_id
188
+ def logger_id # :nodoc:
190
189
  "#{self.class}: #{id}"
191
190
  end
192
191
 
193
- def logger
192
+ def logger # :nodoc:
194
193
  super
195
194
  end
196
195
 
@@ -198,8 +197,15 @@ module Adhearsion
198
197
  [current_actor]
199
198
  end
200
199
 
200
+ def inspect
201
+ attrs = [:offer, :end_reason, :commands, :variables, :controllers, :to, :from].map do |attr|
202
+ "#{attr}=#{send(attr).inspect}"
203
+ end
204
+ "#<#{self.class}:#{id} #{attrs.join ', '}>"
205
+ end
206
+
201
207
  def execute_controller(controller, latch = nil)
202
- Adhearsion::Process.important_threads << Thread.new do
208
+ Thread.new do
203
209
  catching_standard_errors do
204
210
  begin
205
211
  CallController.exec controller
@@ -208,10 +214,22 @@ module Adhearsion
208
214
  end
209
215
  latch.countdown! if latch
210
216
  end
211
- end
217
+ end.tap { |t| Adhearsion::Process.important_threads << t }
218
+ end
219
+
220
+ def register_controller(controller)
221
+ @controllers << controller
222
+ end
223
+
224
+ def pause_controllers
225
+ controllers.each &:pause!
226
+ end
227
+
228
+ def resume_controllers
229
+ controllers.each &:resume!
212
230
  end
213
231
 
214
- class CommandRegistry < ThreadSafeArray
232
+ class CommandRegistry < ThreadSafeArray # :nodoc:
215
233
  def terminate
216
234
  hangup = Hangup.new
217
235
  each { |command| command.response = hangup if command.requested? }
@@ -54,13 +54,13 @@ module Adhearsion
54
54
 
55
55
  delegate :[], :[]=, :to => :@metadata
56
56
  delegate :variables, :logger, :to => :call
57
- delegate :write_and_await_response, :answer, :reject, :mute, :unmute, :join, :to => :call
58
57
 
59
58
  def initialize(call, metadata = nil, &block)
60
59
  @call, @metadata, @block = call, metadata || {}, block
61
60
  end
62
61
 
63
- def execute!(*options)
62
+ def execute!(*options) # :nodoc:
63
+ call.register_controller! self
64
64
  execute_callbacks :before_call
65
65
  run
66
66
  rescue Hangup
@@ -84,7 +84,7 @@ module Adhearsion
84
84
  throw :pass_controller, controller_class.new(call, metadata)
85
85
  end
86
86
 
87
- def execute_callbacks(type)
87
+ def execute_callbacks(type) # :nodoc:
88
88
  self.class.callbacks[type].each do |callback|
89
89
  catching_standard_errors do
90
90
  instance_exec &callback
@@ -92,15 +92,21 @@ module Adhearsion
92
92
  end
93
93
  end
94
94
 
95
- def after_call
95
+ def after_call # :nodoc:
96
96
  @after_call ||= execute_callbacks :after_call
97
97
  end
98
98
 
99
99
  def hangup(headers = nil)
100
+ block_until_resumed
100
101
  hangup_response = call.hangup headers
101
102
  after_call unless hangup_response == false
102
103
  end
103
104
 
105
+ def write_and_await_response(command)
106
+ block_until_resumed
107
+ call.write_and_await_response command
108
+ end
109
+
104
110
  def execute_component_and_await_completion(component)
105
111
  write_and_await_response component
106
112
 
@@ -110,5 +116,48 @@ module Adhearsion
110
116
  raise StandardError, complete_event.reason.details if complete_event.reason.is_a? Punchblock::Event::Complete::Error
111
117
  component
112
118
  end
119
+
120
+ def answer(*args)
121
+ block_until_resumed
122
+ call.answer *args
123
+ end
124
+
125
+ def reject(*args)
126
+ block_until_resumed
127
+ call.reject *args
128
+ end
129
+
130
+ def mute(*args)
131
+ block_until_resumed
132
+ call.mute *args
133
+ end
134
+
135
+ def unmute(*args)
136
+ block_until_resumed
137
+ call.unmute *args
138
+ end
139
+
140
+ def join(*args)
141
+ block_until_resumed
142
+ call.join *args
143
+ end
144
+
145
+ def block_until_resumed # :nodoc:
146
+ @pause_latch && @pause_latch.wait
147
+ end
148
+
149
+ def pause! # :nodoc:
150
+ @pause_latch = CountDownLatch.new 1
151
+ end
152
+
153
+ def resume! # :nodoc:
154
+ return unless @pause_latch
155
+ @pause_latch.countdown!
156
+ @pause_latch = nil
157
+ end
158
+
159
+ def inspect
160
+ "#<#{self.class} call=#{call.id}, metadata=#{metadata.inspect}>"
161
+ end
113
162
  end#class
114
163
  end
@@ -32,6 +32,7 @@ module Adhearsion
32
32
  #
33
33
  def dial(to, options = {}, latch = nil)
34
34
  targets = Array(to)
35
+ status = DialStatus.new
35
36
 
36
37
  latch ||= CountDownLatch.new targets.size
37
38
 
@@ -43,7 +44,7 @@ module Adhearsion
43
44
  options[:from] ||= call.from
44
45
 
45
46
  calls = targets.map do |target|
46
- new_call = OutboundCall.new options
47
+ new_call = OutboundCall.new
47
48
 
48
49
  new_call.on_answer do |event|
49
50
  calls.each do |call_to_hangup, target|
@@ -57,17 +58,18 @@ module Adhearsion
57
58
  end
58
59
 
59
60
  new_call.register_event_handler Punchblock::Event::Unjoined, :other_call_id => call.id do |event|
60
- new_call[:"dial_countdown_#{call.id}"] = true
61
+ new_call["dial_countdown_#{call.id}"] = true
61
62
  latch.countdown!
62
63
  throw :pass
63
64
  end
64
65
 
65
66
  logger.debug "Joining call #{new_call.id} to #{call.id} due to a #dial"
66
67
  new_call.join call
68
+ status.answer!
67
69
  end
68
70
 
69
71
  new_call.on_end do |event|
70
- latch.countdown! unless new_call[:"dial_countdown_#{call.id}"]
72
+ latch.countdown! unless new_call["dial_countdown_#{call.id}"]
71
73
  end
72
74
 
73
75
  [new_call, target]
@@ -78,7 +80,10 @@ module Adhearsion
78
80
  call
79
81
  end
80
82
 
81
- timeout = latch.wait options[:timeout]
83
+ status.calls = calls
84
+
85
+ no_timeout = latch.wait options[:timeout]
86
+ status.timeout! unless no_timeout
82
87
 
83
88
  logger.debug "#dial finished. Hanging up #{calls.size} outbound calls #{calls.inspect}."
84
89
  calls.each do |outbound_call|
@@ -90,9 +95,24 @@ module Adhearsion
90
95
  end
91
96
  end
92
97
 
93
- return timeout unless timeout
98
+ status
99
+ end
100
+
101
+ class DialStatus
102
+ attr_accessor :calls
103
+ attr_reader :result
104
+
105
+ def initialize
106
+ @result = :no_answer
107
+ end
108
+
109
+ def answer!
110
+ @result = :answer
111
+ end
94
112
 
95
- calls.size == 1 ? calls.first : calls
113
+ def timeout!
114
+ @result = :timeout
115
+ end
96
116
  end
97
117
 
98
118
  end#module Dial
@@ -7,7 +7,7 @@ module Adhearsion
7
7
  # @param [Integer] the timeout to wait before returning, in seconds. nil or -1 mean no timeout.
8
8
  # @return [String|nil] the pressed key, or nil if timeout was reached.
9
9
  #
10
- def wait_for_digit(timeout = 1)
10
+ def wait_for_digit(timeout = 1) # :nodoc:
11
11
  timeout = nil if timeout == -1
12
12
  timeout *= 1_000 if timeout
13
13
  input_component = execute_component_and_await_completion ::Punchblock::Component::Input.new :mode => :dtmf,
@@ -103,7 +103,7 @@ module Adhearsion
103
103
  return :done
104
104
  end
105
105
 
106
- def play_sound_files_for_menu(menu_instance, sound_files)
106
+ def play_sound_files_for_menu(menu_instance, sound_files) # :nodoc:
107
107
  digit = nil
108
108
  if sound_files.any? && menu_instance.digit_buffer_empty?
109
109
  digit = interruptible_play *sound_files
@@ -111,7 +111,7 @@ module Adhearsion
111
111
  digit || wait_for_digit(menu_instance.timeout)
112
112
  end
113
113
 
114
- def jump_to(match_object, overrides = nil)
114
+ def jump_to(match_object, overrides = nil) # :nodoc:
115
115
  if match_object.block
116
116
  instance_exec overrides[:extension], &match_object.block
117
117
  else
@@ -2,7 +2,7 @@ module Adhearsion
2
2
  class CallController
3
3
  module Output
4
4
  def speak(text, options = {})
5
- play_ssml(text, options) || output(:text, text, options)
5
+ play_ssml(text, options) || output(:text, text.to_s, options)
6
6
  end
7
7
 
8
8
  #
@@ -110,18 +110,18 @@ module Adhearsion
110
110
  play_ssml ssml_for_audio(argument, options)
111
111
  end
112
112
 
113
- def play_ssml(ssml, options = {})
113
+ def play_ssml(ssml, options = {}) # :nodoc:
114
114
  if [RubySpeech::SSML::Speak, Nokogiri::XML::Document].include? ssml.class
115
115
  output :ssml, ssml.to_s, options
116
116
  end
117
117
  end
118
118
 
119
- def output(type, content, options = {})
119
+ def output(type, content, options = {}) # :nodoc:
120
120
  options.merge! type => content
121
121
  execute_component_and_await_completion ::Punchblock::Component::Output.new(options)
122
122
  end
123
123
 
124
- def output!(type, content, options = {})
124
+ def output!(type, content, options = {}) # :nodoc:
125
125
  options.merge! type => content
126
126
  execute_component_and_await_completion ::Punchblock::Component::Output.new(options)
127
127
  end
@@ -170,7 +170,7 @@ module Adhearsion
170
170
  result
171
171
  end
172
172
 
173
- def detect_type(output)
173
+ def detect_type(output) # :nodoc:
174
174
  result = nil
175
175
  result = :time if [Date, Time, DateTime].include? output.class
176
176
  result = :numeric if output.kind_of?(Numeric) || output =~ /^\d+$/
@@ -178,7 +178,7 @@ module Adhearsion
178
178
  result ||= :text
179
179
  end
180
180
 
181
- def play_ssml_for(*args)
181
+ def play_ssml_for(*args) # :nodoc:
182
182
  play_ssml ssml_for(args)
183
183
  end
184
184
 
@@ -189,7 +189,7 @@ module Adhearsion
189
189
  # @param [String|Hash|RubySpeech::SSML::Speak] the argument with options as accepted by the play_ methods, or an SSML document
190
190
  # @return [RubySpeech::SSML::Speak] an SSML document
191
191
  #
192
- def ssml_for(*args)
192
+ def ssml_for(*args) # :nodoc:
193
193
  return args[0] if args.size == 1 && args[0].is_a?(RubySpeech::SSML::Speak)
194
194
  argument, options = args.flatten
195
195
  options ||= {}
@@ -197,11 +197,11 @@ module Adhearsion
197
197
  send "ssml_for_#{type}", argument, options
198
198
  end
199
199
 
200
- def ssml_for_text(argument, options = {})
200
+ def ssml_for_text(argument, options = {}) # :nodoc:
201
201
  RubySpeech::SSML.draw { argument }
202
202
  end
203
203
 
204
- def ssml_for_time(argument, options = {})
204
+ def ssml_for_time(argument, options = {}) # :nodoc:
205
205
  interpretation = case argument
206
206
  when Date then 'date'
207
207
  when Time then 'time'
@@ -217,13 +217,13 @@ module Adhearsion
217
217
  end
218
218
  end
219
219
 
220
- def ssml_for_numeric(argument, options = {})
220
+ def ssml_for_numeric(argument, options = {}) # :nodoc:
221
221
  RubySpeech::SSML.draw do
222
222
  say_as(:interpret_as => 'cardinal') { argument.to_s }
223
223
  end
224
224
  end
225
225
 
226
- def ssml_for_audio(argument, options = {})
226
+ def ssml_for_audio(argument, options = {}) # :nodoc:
227
227
  fallback = (options || {}).delete :fallback
228
228
  RubySpeech::SSML.draw do
229
229
  audio(:src => argument) { fallback }