adhearsion 2.0.0.alpha1 → 2.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
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