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