adhearsion 2.0.0.alpha1 → 2.0.0.alpha2

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 (57) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +12 -0
  3. data/CHANGELOG.md +17 -0
  4. data/adhearsion.gemspec +4 -3
  5. data/features/app_generator.feature +3 -1
  6. data/features/cli.feature +7 -7
  7. data/features/support/env.rb +46 -0
  8. data/lib/adhearsion.rb +1 -2
  9. data/lib/adhearsion/call.rb +59 -19
  10. data/lib/adhearsion/call_controller.rb +20 -24
  11. data/lib/adhearsion/call_controller/dial.rb +18 -18
  12. data/lib/adhearsion/cli_commands.rb +26 -9
  13. data/lib/adhearsion/configuration.rb +39 -10
  14. data/lib/adhearsion/console.rb +61 -42
  15. data/lib/adhearsion/foundation/libc.rb +13 -0
  16. data/lib/adhearsion/generators/app/app_generator.rb +4 -1
  17. data/lib/adhearsion/generators/app/templates/{Gemfile → Gemfile.erb} +1 -1
  18. data/lib/adhearsion/generators/app/templates/Rakefile +3 -22
  19. data/lib/adhearsion/generators/app/templates/gitignore +7 -0
  20. data/lib/adhearsion/generators/app/templates/lib/simon_game.rb +1 -0
  21. data/lib/adhearsion/generators/app/templates/script/ahn +1 -0
  22. data/lib/adhearsion/initializer.rb +24 -12
  23. data/lib/adhearsion/linux_proc_name.rb +41 -0
  24. data/lib/adhearsion/outbound_call.rb +10 -5
  25. data/lib/adhearsion/plugin.rb +29 -132
  26. data/lib/adhearsion/process.rb +4 -1
  27. data/lib/adhearsion/punchblock_plugin.rb +14 -5
  28. data/lib/adhearsion/punchblock_plugin/initializer.rb +8 -1
  29. data/lib/adhearsion/router/route.rb +1 -3
  30. data/lib/adhearsion/tasks.rb +6 -12
  31. data/lib/adhearsion/tasks/configuration.rb +7 -24
  32. data/lib/adhearsion/tasks/environment.rb +12 -0
  33. data/lib/adhearsion/tasks/plugins.rb +9 -14
  34. data/lib/adhearsion/version.rb +1 -1
  35. data/spec/adhearsion/call_controller/dial_spec.rb +46 -22
  36. data/spec/adhearsion/call_controller_spec.rb +48 -13
  37. data/spec/adhearsion/call_spec.rb +144 -23
  38. data/spec/adhearsion/calls_spec.rb +8 -4
  39. data/spec/adhearsion/console_spec.rb +24 -0
  40. data/spec/adhearsion/initializer/logging_spec.rb +0 -3
  41. data/spec/adhearsion/initializer_spec.rb +52 -37
  42. data/spec/adhearsion/logging_spec.rb +0 -3
  43. data/spec/adhearsion/outbound_call_spec.rb +12 -2
  44. data/spec/adhearsion/plugin_spec.rb +74 -184
  45. data/spec/adhearsion/process_spec.rb +59 -26
  46. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +3 -4
  47. data/spec/adhearsion/punchblock_plugin_spec.rb +11 -0
  48. data/spec/adhearsion/router/route_spec.rb +37 -6
  49. data/spec/adhearsion_spec.rb +31 -8
  50. data/spec/spec_helper.rb +14 -0
  51. data/spec/support/call_controller_test_helpers.rb +2 -2
  52. data/spec/support/logging_helpers.rb +2 -0
  53. metadata +85 -68
  54. data/lib/adhearsion/dialplan_controller.rb +0 -9
  55. data/lib/adhearsion/foundation/synchronized_hash.rb +0 -93
  56. data/lib/adhearsion/plugin/methods_container.rb +0 -6
  57. data/spec/adhearsion/dialplan_controller_spec.rb +0 -26
data/.gitignore CHANGED
@@ -9,6 +9,7 @@ vendor
9
9
  coverage
10
10
  spec/reports
11
11
  example.log
12
+ adhearsion.pid
12
13
 
13
14
  # RBX stuff
14
15
  *~
@@ -21,3 +22,4 @@ nbproject
21
22
  .rvmrc
22
23
  .yardoc
23
24
  doc
25
+ tmp
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-19mode # currently in active development, may or may not work for your project
7
+ - ruby-head
8
+ env: ARUBA_TIMEOUT=120
9
+ notifications:
10
+ # irc: "irc.freenode.org#adhearsion"
11
+ email:
12
+ - blangfeld@adhearsion.com
@@ -1,5 +1,22 @@
1
1
  # develop (2.0.0.head)
2
2
 
3
+ # 2.0.0.alpha2 - 2012-01-30
4
+ * Change: Plugins no longer load dialplan/event/rpc/console methods using corresponding class methods
5
+ * Feature: CallController and Console can have modules of methods mixed in using `CallController.mixin` and `Console.mixin`
6
+ * Feature: Added the ability to override configuration using environment variables. The correct names are given when running `rake adhearsion:config:show`, and are automatically added for all plugins. Plugins may define how the string environment variable is transformed to be useful.
7
+ * Feature: Rake task adhearsion:config:show improved to make the output copy and paste-able in a configuration file.
8
+ * Feature: Call variables are aggregated from the headers sent and received during its existence
9
+ * Feature: Call variables are accessible using `#[]` and `#[]=` on the call
10
+ * Feature: Router can match against variables on a call using `#[]`
11
+ * Feature: adhearsion process is named via configuration module
12
+ * Feature: CallController#dial now takes a `:for` (or `:timeout`) option to specify a timeout on the dial command
13
+ * Feature: Include a sensible `.gitignore` in generated apps
14
+ * Feature: CallController can now perform join operations on calls, and take either a call ID, a call object or a mixer name as the target
15
+ * Bugfix: `Call` and `OutboundCall` now respond to `#to` and `#from` with the correct values from the offer/dial
16
+ * Bugfix: An `OutboundCall` allows storing call variables just like a `Call`
17
+ * Bugfix: The console should be shut down when shutting down the process
18
+ * Rake tasks cleaned up and some initialization bugs fixed
19
+
3
20
  # 2.0.0.alpha1 - 2012-01-17
4
21
 
5
22
  ## Major architectural changes
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
20
20
 
21
21
  # Runtime dependencies
22
22
  s.add_runtime_dependency "bundler", [">= 1.0.10"]
23
- s.add_runtime_dependency 'punchblock', [">= 0.8.3"]
23
+ s.add_runtime_dependency 'punchblock', [">= 0.9.1"]
24
24
  s.add_runtime_dependency "logging", [">= 1.6.1"]
25
- s.add_runtime_dependency "loquacious", [">= 1.9.0"]
25
+ s.add_runtime_dependency "adhearsion-loquacious", [">= 1.9.0"]
26
26
  s.add_runtime_dependency "activesupport", [">= 3.0.10"]
27
27
  # i18n is only strictly a dependency for ActiveSupport >= 3.0.0
28
28
  # Since it doesn't conflict with <3.0.0 we'll require it to be
@@ -36,9 +36,10 @@ Gem::Specification.new do |s|
36
36
  s.add_runtime_dependency "future-resource", [">= 0.0.2"]
37
37
  s.add_runtime_dependency "ruby_speech", [">= 0.4.0"]
38
38
  s.add_runtime_dependency 'countdownlatch'
39
- s.add_runtime_dependency 'has-guarded-handlers', [">= 0.1.1"]
39
+ s.add_runtime_dependency 'has-guarded-handlers', [">= 1.1.0"]
40
40
  s.add_runtime_dependency 'girl_friday'
41
41
  s.add_runtime_dependency 'jruby-openssl' if RUBY_PLATFORM == 'java'
42
+ s.add_runtime_dependency "ffi", [">= 1.0.11"]
42
43
 
43
44
  # Development dependencies
44
45
  s.add_development_dependency 'rspec', ["~> 2.7.0"]
@@ -1,6 +1,6 @@
1
1
  Feature: Adhearsion App Generator
2
2
  In order to do development on new Adhearsion apps
3
- As a Adhearsiohn developer
3
+ As an Adhearsion developer
4
4
  I want to generate an Adhearsion app
5
5
 
6
6
  Scenario: Generate application with valid layout
@@ -12,6 +12,7 @@ Feature: Adhearsion App Generator
12
12
  | script |
13
13
 
14
14
  And the following files should exist:
15
+ | .gitignore |
15
16
  | config/adhearsion.rb |
16
17
  | config/environment.rb |
17
18
  | Gemfile |
@@ -19,6 +20,7 @@ Feature: Adhearsion App Generator
19
20
  | script/ahn |
20
21
  | README.md |
21
22
  | Rakefile |
23
+ | Procfile |
22
24
 
23
25
  And the file "config/adhearsion.rb" should contain each of these content parts:
24
26
  """
@@ -1,5 +1,5 @@
1
1
  Feature: Adhearsion Ahn CLI
2
- As a Adhearsion user
2
+ As an Adhearsion user
3
3
  I want a cli command (ahn)
4
4
  So that I can create and interact with adhearsion apps
5
5
 
@@ -45,27 +45,27 @@ Feature: Adhearsion Ahn CLI
45
45
  And the exit status should be 1
46
46
 
47
47
  Scenario: Command start with no path inside of the app directory
48
+ Given JRuby skip test
48
49
  Given that I create a valid app under "path/somewhere"
49
50
  When I cd to "path/somewhere"
50
51
  And I run `ahn start` interactively
51
- And I wait for output to contain "Transitioning from booting to running"
52
+ And I wait for output to contain "Starting connection to server"
52
53
  And I terminate the interactive process
53
54
  Then the output should contain "Loaded config"
54
55
  And the output should contain "Adhearsion::Console: Starting up..."
55
56
  And the output should contain "AHN>"
56
- And the output should contain "Starting connection"
57
- And the output should contain "Transitioning from running to stopping"
57
+ And the output should contain "Transitioning from booting to force_stop"
58
58
 
59
59
  Scenario: Command start with only path works properly
60
+ Given JRuby skip test
60
61
  Given that I create a valid app under "path/somewhere"
61
62
  When I run `ahn start path/somewhere` interactively
62
- And I wait for output to contain "Transitioning from booting to running"
63
+ And I wait for output to contain "Starting connection to server"
63
64
  And I terminate the interactive process
64
65
  Then the output should contain "Loaded config"
65
66
  And the output should contain "Adhearsion::Console: Starting up..."
66
67
  And the output should contain "AHN>"
67
- And the output should contain "Starting connection"
68
- And the output should contain "Transitioning from running to stopping"
68
+ And the output should contain "Transitioning from booting to force_stop"
69
69
 
70
70
  Scenario: Command daemon with path works correctly
71
71
  Given JRuby skip test
@@ -18,6 +18,52 @@ require 'cucumber'
18
18
  require 'aruba/cucumber'
19
19
  require 'adhearsion'
20
20
 
21
+ module ChildProcess
22
+ class << self
23
+ def new(*args)
24
+ case os
25
+ when :unix, :macosx, :linux, :solaris, :bsd, :cygwin
26
+ if posix_spawn?
27
+ Unix::PosixSpawnProcess.new(args)
28
+ elsif jruby?
29
+ JRuby::Process.new(args)
30
+ else
31
+ Unix::ForkExecProcess.new(args)
32
+ end
33
+ when :windows
34
+ Windows::Process.new(args)
35
+ else
36
+ raise Error, "unsupported OS #{os.inspect}"
37
+ end
38
+ end
39
+ alias_method :build, :new
40
+
41
+ def os
42
+ @os ||= (
43
+ require "rbconfig"
44
+ host_os = RbConfig::CONFIG['host_os'].downcase
45
+
46
+ case host_os
47
+ when /linux/
48
+ :linux
49
+ when /darwin|mac os/
50
+ :macosx
51
+ when /mswin|msys|mingw32/
52
+ :windows
53
+ when /cygwin/
54
+ :cygwin
55
+ when /solaris|sunos/
56
+ :solaris
57
+ when /bsd/
58
+ :bsd
59
+ else
60
+ raise Error, "unknown os: #{host_os.inspect}"
61
+ end
62
+ )
63
+ end
64
+ end # class << self
65
+ end # ChildProcess
66
+
21
67
  Before do
22
68
  @aruba_timeout_seconds = ENV['ARUBA_TIMEOUT'] || RUBY_PLATFORM == 'java' ? 60 : 30
23
69
  end
@@ -14,6 +14,7 @@ abort "ERROR: You are running Adhearsion on an unsupported version of Ruby (Ruby
14
14
  girl_friday
15
15
  loquacious
16
16
 
17
+ adhearsion/version
17
18
  adhearsion/foundation/all
18
19
  }.each { |f| require f }
19
20
 
@@ -27,7 +28,6 @@ module Adhearsion
27
28
  autoload :Configuration
28
29
  autoload :Console
29
30
  autoload :Conveniences
30
- autoload :DialplanController
31
31
  autoload :Dispatcher
32
32
  autoload :Events
33
33
  autoload :MenuDSL
@@ -36,7 +36,6 @@ module Adhearsion
36
36
  autoload :OutboundCall
37
37
  autoload :Plugin
38
38
  autoload :Router
39
- autoload :Version
40
39
 
41
40
  class << self
42
41
 
@@ -8,25 +8,25 @@ module Adhearsion
8
8
 
9
9
  include HasGuardedHandlers
10
10
 
11
- attr_accessor :offer, :client, :end_reason, :commands
11
+ attr_accessor :offer, :client, :end_reason, :commands, :variables
12
+
13
+ delegate :[], :[]=, :to => :variables
14
+ delegate :to, :from, :to => :offer, :allow_nil => true
12
15
 
13
16
  def initialize(offer = nil)
14
- if offer
15
- @offer = offer
16
- @client = offer.client
17
- end
17
+ register_initial_handlers
18
18
 
19
19
  @tag_mutex = Mutex.new
20
20
  @tags = []
21
21
  @end_reason_mutex = Mutex.new
22
- end_reason = nil
23
22
  @commands = CommandRegistry.new
23
+ @variables = {}
24
24
 
25
- register_initial_handlers
25
+ self << offer if offer
26
26
  end
27
27
 
28
28
  def id
29
- @offer.call_id
29
+ offer.call_id
30
30
  end
31
31
 
32
32
  def tags
@@ -58,9 +58,21 @@ module Adhearsion
58
58
  def deliver_message(message)
59
59
  trigger_handler :event, message
60
60
  end
61
+
61
62
  alias << deliver_message
62
63
 
63
64
  def register_initial_handlers
65
+ register_event_handler Punchblock::Event::Offer do |offer|
66
+ @offer = offer
67
+ @client = offer.client
68
+ throw :pass
69
+ end
70
+
71
+ register_event_handler Punchblock::HasHeaders do |event|
72
+ variables.merge! event.headers_hash
73
+ throw :pass
74
+ end
75
+
64
76
  on_end do |event|
65
77
  hangup
66
78
  @end_reason_mutex.synchronize { @end_reason = event.reason }
@@ -69,7 +81,7 @@ module Adhearsion
69
81
  end
70
82
 
71
83
  def on_end(&block)
72
- register_event_handler :class => Punchblock::Event::End do |event|
84
+ register_event_handler Punchblock::Event::End do |event|
73
85
  block.call event
74
86
  throw :pass
75
87
  end
@@ -101,19 +113,51 @@ module Adhearsion
101
113
  Adhearsion.active_calls.remove_inactive_call self
102
114
  end
103
115
 
104
- def join(other_call_id)
105
- write_and_await_response Punchblock::Command::Join.new :other_call_id => other_call_id
116
+ ##
117
+ # Joins this call to another call or a mixer
118
+ #
119
+ # @param [Call, String, Hash] target the target to join to. May be a Call object, a call ID (String, Hash) or a mixer name (Hash)
120
+ # @option target [String] call_id The call ID to join to
121
+ # @option target [String] mixer_name The mixer to join to
122
+ # @param [Hash, Optional] options further options to be joined with
123
+ #
124
+ def join(target, options = {})
125
+ case target
126
+ when Call
127
+ options[:other_call_id] = target.id
128
+ when String
129
+ options[:other_call_id] = target
130
+ when Hash
131
+ raise ArgumentError, "You cannot specify both a call ID and mixer name" if target.has_key?(:call_id) && target.has_key?(:mixer_name)
132
+ target.tap do |t|
133
+ t[:other_call_id] = t[:call_id]
134
+ t.delete :call_id
135
+ end
136
+
137
+ options.merge! target
138
+ else
139
+ raise ArgumentError, "Don't know how to join to #{target.inspect}"
140
+ end
141
+ command = Punchblock::Command::Join.new options
142
+ write_and_await_response command
143
+ end
144
+
145
+ def mute
146
+ write_and_await_response ::Punchblock::Command::Mute.new
147
+ end
148
+
149
+ def unmute
150
+ write_and_await_response ::Punchblock::Command::Unmute.new
106
151
  end
107
152
 
108
- # Lock the socket for a command. Can be used to allow the console to take
109
- # control of the thread in between AGI commands coming from the dialplan.
110
153
  def with_command_lock
111
154
  @command_monitor ||= Monitor.new
112
155
  @command_monitor.synchronize { yield }
113
156
  end
114
157
 
115
158
  def write_and_await_response(command, timeout = 60)
116
- logger.trace "Executing command #{command.inspect}"
159
+ # TODO: Put this back once we figure out why it's causing CI to fail
160
+ # logger.trace "Executing command #{command.inspect}"
117
161
  commands << command
118
162
  write_command command
119
163
  response = command.response timeout
@@ -123,18 +167,14 @@ module Adhearsion
123
167
 
124
168
  def write_command(command)
125
169
  raise Hangup unless active? || command.is_a?(Punchblock::Command::Hangup)
170
+ variables.merge! command.headers_hash if command.respond_to? :headers_hash
126
171
  client.execute_command command, :call_id => id
127
172
  end
128
173
 
129
- # Sanitize the offer id
130
174
  def logger_id
131
175
  "#{self.class}: #{id}"
132
176
  end
133
177
 
134
- def variables
135
- offer ? offer.headers_hash : nil or {}
136
- end
137
-
138
178
  def execute_controller(controller, latch = nil)
139
179
  Adhearsion::Process.important_threads << Thread.new do
140
180
  catching_standard_errors do
@@ -33,31 +33,34 @@ module Adhearsion
33
33
  STOP
34
34
  end
35
35
 
36
- def self.exec(controller, fresh_call = true)
37
- return unless controller
36
+ class << self
37
+ def exec(controller, fresh_call = true)
38
+ return unless controller
39
+
40
+ new_controller = catch :pass_controller do
41
+ controller.skip_accept! unless fresh_call
42
+ controller.execute!
43
+ nil
44
+ end
38
45
 
39
- new_controller = catch :pass_controller do
40
- controller.skip_accept! unless fresh_call
41
- controller.execute!
42
- nil
46
+ exec new_controller, false
43
47
  end
44
48
 
45
- exec new_controller, false
49
+ ##
50
+ # Include another module into all CallController classes
51
+ def mixin(mod)
52
+ include mod
53
+ end
46
54
  end
47
55
 
48
- attr_reader :call, :metadata
56
+ attr_reader :call, :metadata, :block
49
57
 
50
58
  delegate :[], :[]=, :to => :@metadata
51
59
  delegate :variables, :logger, :to => :call
52
- delegate :write_and_await_response, :accept, :answer, :reject, :to => :call
53
-
54
- def initialize(call, metadata = nil)
55
- @call, @metadata = call, metadata || {}
56
- setup
57
- end
60
+ delegate :write_and_await_response, :accept, :answer, :reject, :mute, :unmute, :join, :to => :call
58
61
 
59
- def setup
60
- Plugin.add_dialplan_methods self if Plugin
62
+ def initialize(call, metadata = nil, &block)
63
+ @call, @metadata, @block = call, metadata || {}, block
61
64
  end
62
65
 
63
66
  def execute!(*options)
@@ -73,6 +76,7 @@ module Adhearsion
73
76
  end
74
77
 
75
78
  def run
79
+ instance_exec &block if block
76
80
  end
77
81
 
78
82
  def invoke(controller_class, metadata = nil)
@@ -113,14 +117,6 @@ module Adhearsion
113
117
  after_call unless hangup_response == false
114
118
  end
115
119
 
116
- def mute
117
- write_and_await_response ::Punchblock::Command::Mute.new
118
- end
119
-
120
- def unmute
121
- write_and_await_response ::Punchblock::Command::Unmute.new
122
- end
123
-
124
120
  def execute_component_and_await_completion(component)
125
121
  write_and_await_response component
126
122
 
@@ -13,34 +13,29 @@ module Adhearsion
13
13
  #
14
14
  # @param [Hash] options
15
15
  #
16
- # +:caller_id+ - the caller id number to be used when the call is placed. It is advised you properly adhere to the
16
+ # +:from+ - the caller id to be used when the call is placed. It is advised you properly adhere to the
17
17
  # policy of VoIP termination providers with respect to caller id values.
18
18
  #
19
- # +:name+ - this is the name which should be passed with the caller ID information
20
- # if :name=>"John Doe" and :caller_id => "444-333-1000" then the compelete CID and name would be "John Doe" <4443331000>
21
- # support for caller id information varies from country to country and from one VoIP termination provider to another.
22
- #
23
19
  # +:for+ - this option can be thought of best as a timeout. i.e. timeout after :for if no one answers the call
24
- # For example, dial("SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => callerid)
20
+ # For example, dial(%w{SIP/jay-desk-650 SIP/jay-desk-601 SIP/jay-desk-601-2}, :for => 15.seconds, :from => callerid)
25
21
  # this call will timeout after 15 seconds if 1 of the 3 extensions being dialed do not pick prior to the 15 second time limit
26
22
  #
27
- # +:options+ - This is a string of options like "Tr" which are supported by the asterisk DIAL application.
28
- # for a complete list of these options and their usage please check the link below.
29
- #
30
- # +:confirm+ - ?
31
- #
32
23
  # @example Make a call to the PSTN using my SIP provider for VoIP termination
33
24
  # dial "SIP/19095551001@my.sip.voip.terminator.us"
34
25
  #
35
26
  # @example Make 3 Simulataneous calls to the SIP extensions, try for 15 seconds and use the callerid
36
27
  # for this call specified by the variable my_callerid
37
- # dial ["SIP/jay-desk-650", "SIP/jay-desk-601", "SIP/jay-desk-601-2"], :for => 15.seconds, :caller_id => my_callerid
28
+ # dial %w{SIP/jay-desk-650 SIP/jay-desk-601 SIP/jay-desk-601-2}, :for => 15.seconds, :from => my_callerid
38
29
  #
39
30
  # @example Make a call using the IAX provider to the PSTN
40
- # dial "IAX2/my.id@voipjet/19095551234", :name => "John Doe", :caller_id => "9095551234"
31
+ # dial "IAX2/my.id@voipjet/19095551234", :from => "John Doe <9095551234>"
41
32
  #
42
- def dial(to, options = {})
43
- latch = CountDownLatch.new 1
33
+ def dial(to, options = {}, latch = nil)
34
+ latch ||= CountDownLatch.new 1
35
+
36
+ _for = options.delete :for
37
+ options[:timeout] ||= _for if _for
38
+
44
39
  calls = Array(to).map do |target|
45
40
  new_call = OutboundCall.new options
46
41
 
@@ -48,7 +43,7 @@ module Adhearsion
48
43
  calls.each do |call_to_hangup, target|
49
44
  call_to_hangup.hangup! unless call_to_hangup.id == new_call.id
50
45
  end
51
- new_call.join call.id
46
+ new_call.join call
52
47
  end
53
48
 
54
49
  new_call.on_end do |event|
@@ -58,11 +53,16 @@ module Adhearsion
58
53
  [new_call, target]
59
54
  end
60
55
 
61
- calls.each do |call, target|
56
+ calls.map! do |call, target|
62
57
  call.dial target, options
58
+ call
63
59
  end
64
60
 
65
- latch.wait
61
+ timeout = latch.wait options[:timeout]
62
+
63
+ return timeout unless timeout
64
+
65
+ calls.size == 1 ? calls.first : calls
66
66
  end
67
67
 
68
68
  end#module Dial