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
@@ -0,0 +1,41 @@
1
+ module Adhearsion
2
+
3
+ # https://gist.github.com/1350729
4
+ #
5
+ # Eric Lindvall <eric@5stops.com>
6
+ #
7
+ # Update the process name for the process you're running in.
8
+ #
9
+ # $0 => updates proc name for ps command
10
+ # prctl => updates proc name for lsof, top, killall commands (...)
11
+ #
12
+ # prctl does not work on OS X
13
+ #
14
+ module LinuxProcName
15
+ # Set process name
16
+ PR_SET_NAME = 15
17
+
18
+ class << self
19
+ attr_accessor :error
20
+
21
+ def set_proc_name(name)
22
+ $0 = name # process name in ps command
23
+ if error
24
+ logger.warn error
25
+ return false
26
+ end
27
+ return false unless LibC.respond_to?(:prctl)
28
+
29
+ # The name can be up to 16 bytes long, and should be null-terminated if
30
+ # it contains fewer bytes.
31
+ name = name.slice(0, 16)
32
+ ptr = FFI::MemoryPointer.from_string(name)
33
+ LibC.prctl(PR_SET_NAME, ptr.address, 0, 0) # process name in top, lsof, etc
34
+ ensure
35
+ ptr.free if ptr
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
@@ -2,6 +2,8 @@ module Adhearsion
2
2
  class OutboundCall < Call
3
3
  attr_reader :dial_command
4
4
 
5
+ delegate :to, :from, :to => :dial_command, :allow_nil => true
6
+
5
7
  class << self
6
8
  def originate(to, opts = {})
7
9
  new.tap do |call|
@@ -15,10 +17,6 @@ module Adhearsion
15
17
  dial_command.call_id if dial_command
16
18
  end
17
19
 
18
- def variables
19
- {}
20
- end
21
-
22
20
  def client
23
21
  PunchblockPlugin::Initializer.client
24
22
  end
@@ -34,7 +32,14 @@ module Adhearsion
34
32
 
35
33
  def dial(to, options = {})
36
34
  options.merge! :to => to
37
- write_and_await_response(Punchblock::Command::Dial.new(options)).tap do |dial_command|
35
+ if options[:timeout]
36
+ wait_timeout = options[:timeout]
37
+ options[:timeout] = options[:timeout] * 1000
38
+ else
39
+ wait_timeout = 60
40
+ end
41
+
42
+ write_and_await_response(Punchblock::Command::Dial.new(options), wait_timeout).tap do |dial_command|
38
43
  @dial_command = dial_command
39
44
  Adhearsion.active_calls << self
40
45
  end
@@ -12,7 +12,6 @@ module Adhearsion
12
12
  # * create initializers
13
13
  # * add rake tasks to Adhearsion
14
14
  # * add/modify configuration files
15
- # * add dialplan, rpc, console and events methods
16
15
  #
17
16
  # == How to create your Adhearsion Plugin
18
17
  #
@@ -25,19 +24,6 @@ module Adhearsion
25
24
  # end
26
25
  # end
27
26
  #
28
- # == How to add a new dialplan method
29
- #
30
- # module MyPlugin
31
- # class Plugin < Adhearsion::Plugin
32
- # dialplan :my_new_dialplan_method do
33
- # logger.info "this dialplan method is really awesome #{call.inspect}. It says 'hello world'"
34
- # speak "hello world"
35
- # end
36
- # end
37
- # end
38
- #
39
- # Create a new rpc, console or events methods is as ease just following this approach
40
- #
41
27
  # == Execute a specific code while initializing Adhearison
42
28
  #
43
29
  # module MyPlugin
@@ -49,7 +35,7 @@ module Adhearsion
49
35
  # end
50
36
  #
51
37
  # As Rails::Railtie does, you can define the exact point when you want to load your plugin
52
- # during the initilization process
38
+ # during the initialization process.
53
39
  #
54
40
  # module MyPlugin
55
41
  # class Plugin < Adhearsion::Plugin
@@ -65,81 +51,11 @@ module Adhearsion
65
51
 
66
52
  METHODS_OPTIONS = {:load => true, :scope => false}
67
53
 
68
- SCOPE_NAMES = [:dialplan, :rpc, :events, :console]
69
-
70
54
  autoload :Configuration
71
55
  autoload :Collection
72
56
  autoload :Initializer
73
- autoload :MethodsContainer
74
57
 
75
58
  class << self
76
- # Metaprogramming to create the class methods that can be used in user defined plugins to
77
- # create specific scope methods
78
- SCOPE_NAMES.each do |name|
79
-
80
- # This block will create the relevant methods to handle how to add new methods
81
- # to Adhearsion scopes via an Adhearsion Plugin.
82
- # The scope method should have a name and a lambda block that will be executed in the
83
- # call ExecutionEnvironment context.
84
- #
85
- # class AhnPluginDemo < Adhearsion::Plugin
86
- # dialplan :adh_plugin_demo do
87
- # speak "hello world"
88
- # end
89
- # end
90
- #
91
- # You could also defined a dialplan or other scope method as above, but you cannot access
92
- # the ExecutionEnvironment methods from your specific method due to ruby restrictions
93
- # when defining methods (the above lambda version should fit any requirement)
94
- #
95
- # class AhnPluginDemo < Adhearsion::Plugin
96
- # dialplan :adh_plugin_demo
97
- #
98
- # def self.adh_plugin_demo
99
- # logger.debug "I can do fun stuff here, but I cannot access methods as speak"
100
- # logger.debug "I can make an HTTP request"
101
- # logger.debug "I can log to a specific logging system"
102
- # logger.debug "I can access database..."
103
- # logger.debug "but I cannot access call control methods"
104
- # end
105
- #
106
- # end
107
- #
108
- define_method name do |method_name, &block|
109
- case method_name
110
- when Array
111
- method_name.each do |method|
112
- send name, method
113
- end
114
- return
115
- when Hash
116
- args = method_name
117
- method_name = method_name[:name]
118
- end
119
-
120
- options = args.nil? ? METHODS_OPTIONS : METHODS_OPTIONS.merge(args)
121
- options[:load] or return
122
- logger.debug "Adding method #{method_name} to scope #{name}"
123
- @@methods_container[name].store({:class => self, :method => method_name}, block.nil? ? nil : block)
124
- end
125
-
126
- # This method is a helper to retrieve the specific module that holds the user
127
- # defined scope methods
128
- define_method "#{name.to_s}_module" do
129
- Adhearsion::Plugin.methods_scope[name]
130
- end
131
-
132
- # Helper to add scope methods to any class/instance
133
- define_method "add_#{name.to_s}_methods" do |object|
134
- if object.kind_of?(Module)
135
- object.send :include, Adhearsion::Plugin.methods_scope[name]
136
- else
137
- object.extend Adhearsion::Plugin.methods_scope[name]
138
- end
139
- object
140
- end
141
- end
142
-
143
59
  ##
144
60
  # Class method that allows any subclass (any Adhearsion plugin) to register rake tasks.
145
61
  #
@@ -189,13 +105,6 @@ module Adhearsion
189
105
  end
190
106
  end
191
107
 
192
- def methods_scope
193
- @methods_scope ||= Hash.new { |hash, key| hash[key] = Module.new }
194
- end
195
-
196
- # Keep methods to be added
197
- @@methods_container = Hash.new { |hash, key| hash[key] = MethodsContainer.new }
198
-
199
108
  def subclasses
200
109
  @subclasses ||= []
201
110
  end
@@ -217,7 +126,7 @@ module Adhearsion
217
126
  @plugin_name = name
218
127
  end
219
128
 
220
- def config name = nil
129
+ def config(name = nil, &block)
221
130
  if block_given?
222
131
  if name.nil?
223
132
  name = self.plugin_name
@@ -225,51 +134,16 @@ module Adhearsion
225
134
  self.plugin_name = name
226
135
  end
227
136
  ::Loquacious::Configuration.defaults_for name, &Proc.new
137
+ ::Loquacious.configuration_for plugin_name, &block
138
+ else
139
+ ::Loquacious.configuration_for plugin_name
228
140
  end
229
-
230
- ::Loquacious.configuration_for plugin_name
231
141
  end
232
142
 
233
143
  def show_description
234
144
  ::Loquacious::Configuration.help_for plugin_name
235
145
  end
236
146
 
237
- def load_plugins
238
- load_methods
239
- init_plugins
240
- end
241
-
242
- # Load plugins scope methods (scope = dialplan, console, etc)
243
- def load_methods
244
- unless @@methods_container.empty?
245
-
246
- @@methods_container.each_pair do |scope, methods|
247
-
248
- logger.debug "Loading #{methods.length} #{scope} methods"
249
-
250
- methods.each_pair do |class_method, block|
251
- klass, method = class_method[:class], class_method[:method]
252
- if block.nil?
253
- if klass.respond_to?(method)
254
- block = klass.method(method).to_proc
255
- elsif klass.instance_methods.include?(method)
256
- block = klass.instance_method(method).bind(klass.new)
257
- else
258
- logger.warn "Unable to load #{scope} method #{method} from plugin class #{klass}"
259
- end
260
- end
261
-
262
- logger.debug "Defining method #{method}"
263
- block.nil? and raise NoMethodError.new "Invalid #{scope} method: <#{method}>"
264
- self.send("#{scope}_module").send(:define_method, method, &block)
265
- end
266
- end
267
-
268
- # We need to extend Console class with the plugin defined methods
269
- Adhearsion::Console.extend(self.console_module) unless self.console_module.instance_methods.empty?
270
- end
271
- end
272
-
273
147
  # Recursively initialization of all the loaded plugins
274
148
  def init_plugins *args
275
149
  initializers.tsort.each do |initializer|
@@ -277,21 +151,44 @@ module Adhearsion
277
151
  end
278
152
  end
279
153
 
154
+ def run_plugins *args
155
+ runners.tsort.each do |runner|
156
+ runner.run *args
157
+ end
158
+ end
159
+
280
160
  def initializers
281
161
  @initializers ||= Collection.new
282
162
  end
283
163
 
164
+ def runners
165
+ @runners ||= Collection.new
166
+ end
167
+
284
168
  # Class method that will be used by subclasses to initialize the plugin
285
169
  # @param name Symbol plugin initializer name
286
170
  # @param opts Hash
287
171
  # * :before specify the plugin to be loaded before another plugin
288
172
  # * :after specify the plugin to be loaded after another plugin
289
- def init(name, opts = {})
173
+ def init(name = nil, opts = {})
174
+ name = plugin_name unless name
290
175
  block_given? or raise ArgumentError, "A block must be passed while defining the Plugin initialization process"
291
176
  opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
292
177
  Adhearsion::Plugin.initializers << Initializer.new(name, nil, opts, &Proc.new)
293
178
  end
294
179
 
180
+ # Class method that will be used by subclasses to run the plugin
181
+ # @param name Symbol plugin initializer name
182
+ # @param opts Hash
183
+ # * :before specify the plugin to be loaded before another plugin
184
+ # * :after specify the plugin to be loaded after another plugin
185
+ def run(name = nil, opts = {})
186
+ name = plugin_name unless name
187
+ block_given? or raise ArgumentError, "A block must be passed while defining the Plugin run process"
188
+ opts[:after] ||= runners.last.name unless runners.empty? || runners.find { |i| i.name == opts[:before] }
189
+ Adhearsion::Plugin.runners << Initializer.new(name, nil, opts, &Proc.new)
190
+ end
191
+
295
192
  def count
296
193
  subclasses.length
297
194
  end
@@ -57,7 +57,7 @@ module Adhearsion
57
57
 
58
58
  def log_state_change(transition)
59
59
  event, from, to = transition.event, transition.from_name, transition.to_name
60
- logger.info "Transitioning from #{from} to #{to} with #{Adhearsion.active_calls.size} active calls."
60
+ logger.info "Transitioning from #{from} to #{to} with #{Adhearsion.active_calls.size} active calls due to #{event} event."
61
61
  end
62
62
 
63
63
  def request_stop
@@ -69,10 +69,13 @@ module Adhearsion
69
69
  Adhearsion.active_calls.each do |call|
70
70
  call.hangup
71
71
  end
72
+
72
73
  # This should shut down any remaining threads. Once those threads have
73
74
  # stopped, important_threads will be empty and the process will exit
74
75
  # normally.
75
76
  Events.trigger_immediately :shutdown
77
+
78
+ Console.stop
76
79
  end
77
80
 
78
81
  def stop_when_zero_calls
@@ -5,7 +5,7 @@ module Adhearsion
5
5
  autoload :Initializer
6
6
 
7
7
  config :punchblock do
8
- platform :xmpp , :desc => <<-__
8
+ platform :xmpp , :transform => Proc.new { |v| v.to_sym }, :desc => <<-__
9
9
  Platform punchblock shall use to connect to the Telephony provider. Currently supported values:
10
10
  - :xmpp
11
11
  - :asterisk
@@ -13,17 +13,26 @@ module Adhearsion
13
13
  username "usera@127.0.0.1", :desc => "Authentication credentials"
14
14
  password "1" , :desc => "Authentication credentials"
15
15
  host nil , :desc => "Host punchblock needs to connect (where rayo or asterisk are located)"
16
- port nil , :desc => "Port punchblock needs to connect (by default 5038 for Asterisk, 5222 for Rayo)"
16
+ port nil , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "Port punchblock needs to connect (by default 5038 for Asterisk, 5222 for Rayo)"
17
17
  root_domain nil , :desc => "The root domain at which to address the server"
18
18
  calls_domain nil , :desc => "The domain at which to address calls"
19
19
  mixers_domain nil , :desc => "The domain at which to address mixers"
20
- connection_timeout 60 , :desc => "The amount of time to wait for a connection"
21
- reconnect_attempts 1.0/0.0 , :desc => "The number of times to (re)attempt connection to the server"
22
- reconnect_timer 5 , :desc => "Delay between connection attempts"
20
+ connection_timeout 60 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "The amount of time to wait for a connection"
21
+ reconnect_attempts 1.0/0.0 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "The number of times to (re)attempt connection to the server"
22
+ reconnect_timer 5 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "Delay between connection attempts"
23
23
  end
24
24
 
25
25
  init :punchblock do
26
26
  Initializer.start
27
27
  end
28
+
29
+ class << self
30
+ delegate :client, :to => Initializer
31
+
32
+ def validate_number(value)
33
+ return 1.0/0.0 if ["Infinity", 1.0/0.0].include? value
34
+ value.to_i
35
+ end
36
+ end
28
37
  end
29
38
  end
@@ -36,7 +36,7 @@ module Adhearsion
36
36
  # When a stop is requested, change our status to "Do Not Disturb"
37
37
  # This should prevent the telephony engine from sending us any new calls.
38
38
  Events.register_callback :stop_requested do
39
- connection.not_ready!
39
+ connection.not_ready! if connection.connected?
40
40
  end
41
41
 
42
42
  # Make sure we stop everything when we shutdown
@@ -70,12 +70,19 @@ module Adhearsion
70
70
  end
71
71
 
72
72
  def connect
73
+ return unless Process.state_name == :booting
73
74
  m = Mutex.new
74
75
  blocker = ConditionVariable.new
76
+
75
77
  Events.punchblock ::Punchblock::Connection::Connected do
76
78
  m.synchronize { blocker.broadcast }
77
79
  end
78
80
 
81
+ Events.shutdown do
82
+ logger.info "Shutting down while connecting. Breaking the connection block."
83
+ m.synchronize { blocker.broadcast }
84
+ end
85
+
79
86
  Adhearsion::Process.important_threads << Thread.new do
80
87
  catching_standard_errors { connect_to_server }
81
88
  end
@@ -22,9 +22,7 @@ module Adhearsion
22
22
  def dispatcher
23
23
  @dispatcher ||= lambda do |call|
24
24
  controller = if target.respond_to?(:call)
25
- DialplanController.new(call).tap do |controller|
26
- controller.dialplan = target
27
- end
25
+ CallController.new call, &target
28
26
  else
29
27
  target.new call
30
28
  end
@@ -1,22 +1,16 @@
1
1
  require 'adhearsion'
2
2
 
3
- %w<
4
- configuration
5
- testing
6
- plugins
7
- >.each do |file|
8
- require "adhearsion/tasks/#{file}"
3
+ Dir[File.join(File.dirname(__FILE__), "tasks/*.rb")].each do |file|
4
+ require file
9
5
  end
10
6
 
11
7
  Adhearsion::Plugin.load_tasks
12
8
 
13
9
  puts "\nAdhearsion configured environment: #{Adhearsion.config.platform.environment}\n" unless ARGV.empty?
14
10
 
15
- namespace :adhearsion do
16
- desc "Dump useful information about this application's Adhearsion environment"
17
- task :about do
18
- puts "Adhearsion version: #{Adhearsion::VERSION}"
19
- end
11
+ desc "Dump useful information about this application's Adhearsion environment"
12
+ task :about do
13
+ puts "Adhearsion version: #{Adhearsion::VERSION}"
20
14
  end
21
15
 
22
- task :default => "adhearsion:about"
16
+ task :default => :about