adhearsion 0.8.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG +24 -0
  2. data/Rakefile +3 -1
  3. data/adhearsion.gemspec +21 -4
  4. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +6 -2
  5. data/app_generators/ahn/templates/config/startup.rb +11 -2
  6. data/lib/adhearsion.rb +7 -1
  7. data/lib/adhearsion/component_manager.rb +67 -2
  8. data/lib/adhearsion/foundation/all.rb +7 -1
  9. data/lib/adhearsion/foundation/blank_slate.rb +1 -3
  10. data/lib/adhearsion/initializer.rb +1 -2
  11. data/lib/adhearsion/initializer/asterisk.rb +2 -2
  12. data/lib/adhearsion/initializer/configuration.rb +16 -5
  13. data/lib/adhearsion/tasks.rb +1 -0
  14. data/lib/adhearsion/tasks/components.rb +32 -0
  15. data/lib/adhearsion/version.rb +3 -3
  16. data/lib/adhearsion/voip/asterisk/commands.rb +44 -16
  17. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +2 -2
  18. data/lib/adhearsion/voip/asterisk/manager_interface.rb +14 -13
  19. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +134 -134
  20. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +63 -10
  21. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +1 -1
  22. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +2 -2
  23. data/lib/adhearsion/voip/call.rb +11 -11
  24. data/lib/adhearsion/voip/call_routing.rb +1 -1
  25. data/lib/adhearsion/voip/dial_plan.rb +15 -6
  26. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +1 -1
  27. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +1 -1
  28. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +1 -1
  29. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +4 -6
  30. data/lib/adhearsion/voip/dsl/numerical_string.rb +1 -3
  31. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +2 -2
  32. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +2 -2
  33. data/lib/adhearsion/voip/freeswitch/oes_server.rb +1 -1
  34. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +1 -1
  35. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +1 -1
  36. data/lib/theatre/callback_definition_loader.rb +1 -1
  37. metadata +78 -19
data/CHANGELOG CHANGED
@@ -1,9 +1,33 @@
1
+ 1.0.0
2
+ - Fall back to using Asterisk's context if the AGI URI context is not found
3
+ - Enable configuration of :auto_reconnect parameter for AMI
4
+ - Replace all uses of Object#returning with Object#tap
5
+ - Add support for loading Adhearsion components from RubyGems
6
+ - Fix long-running AMI session parser failure bug (#72)
7
+ - Support for Rails 3 (and ActiveSupport 3.0)
8
+
1
9
  0.8.6
2
10
  - Fix packaging problem so all files are publicly readable
3
11
  - Improve AMI reconnecting logic; add "connection refused" retry timer
4
12
  - AGI protocol improvements: parse the status code and response text
5
13
 
6
14
  0.8.5
15
+ NOTE: If you are upgrading an Adhearsion application to 0.8.5, note the change
16
+ to how request URIs are handled. With 0.8.4, the context name in Asterisk was
17
+ required to match the Adhearsion context in dialplan.rb. Starting in 0.8.5 if
18
+ an application path is passed in on the AGI URI, it will be preferred over the
19
+ context name. For example:
20
+
21
+ [stuff]
22
+ exten => _X.,1,AGI(agi://localhost/myapp)
23
+
24
+ AHN 0.8.4- will execute the "stuff" context in dialplan.rb
25
+ AHN 0.8.5+ will execute the "myapp" context in dialplan.rb
26
+
27
+ If you followed the documentation and did not specify an application path in
28
+ the URI (eg. agi://localhost) you will not be impacted by this change.
29
+
30
+ Other changes:
7
31
  - Added XMPP module and sample component. This allows you to easily write components which utilise a persistent XMPP connection maintained by Adhearsion
8
32
  - Prefer finding the dialplan.rb entry point by the AGI request URI instead of the calling context
9
33
  - Added :use_static_conf option for "meetme" to allow the use of disk-file-managed conferences
data/Rakefile CHANGED
@@ -4,11 +4,13 @@ ENV['RUBY_FLAGS'] = "-I#{%w(lib ext bin test).join(File::PATH_SEPARATOR)}"
4
4
  require 'rubygems'
5
5
  require 'rake/gempackagetask'
6
6
  require 'rake/testtask'
7
+ require 'date'
7
8
 
8
9
  begin
10
+ gem 'rspec', '~> 1.3.0'
9
11
  require 'spec/rake/spectask'
10
12
  rescue LoadError
11
- abort "You must install RSpec: sudo gem install rspec"
13
+ abort "You must install RSpec 1.3: sudo gem install rspec -v'<2.0.0'"
12
14
  end
13
15
 
14
16
  begin
@@ -60,6 +60,7 @@ ADHEARSION_FILES = %w{
60
60
  lib/adhearsion/initializer/xmpp.rb
61
61
  lib/adhearsion/logging.rb
62
62
  lib/adhearsion/tasks.rb
63
+ lib/adhearsion/tasks/components.rb
63
64
  lib/adhearsion/tasks/database.rb
64
65
  lib/adhearsion/tasks/deprecations.rb
65
66
  lib/adhearsion/tasks/generating.rb
@@ -122,7 +123,7 @@ Gem::Specification.new do |s|
122
123
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
123
124
  s.authors = ["Jay Phillips", "Jason Goecke", "Ben Klang"]
124
125
 
125
- s.date = "2010-08-24"
126
+ s.date = Date.today.to_s
126
127
  s.description = "Adhearsion is an open-source telephony development framework"
127
128
  s.email = "dev&Adhearsion.com"
128
129
  s.executables = ["ahn", "ahnctl", "jahn"]
@@ -135,22 +136,38 @@ Gem::Specification.new do |s|
135
136
  s.rubyforge_project = "adhearsion"
136
137
  s.rubygems_version = "1.2.0"
137
138
  s.summary = "Adhearsion, open-source telephony development framework"
139
+ s.post_install_message =<<-EOM
140
+ *******************************************************************
141
+ * NOTE: You must manually install the "rubigen" gem to create *
142
+ * new Adhearsion applications. *
143
+ * *
144
+ * The Rubigen package is no longer automatically installed due to *
145
+ * dependency conflicts with ActiveSupport 3.0. *
146
+ * Users of existing Adhearsion applications can safely ignore *
147
+ * this message. *
148
+ *******************************************************************
149
+ EOM
138
150
 
139
151
  if s.respond_to? :specification_version then
140
152
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
141
153
  s.specification_version = 2
142
154
 
143
155
  if current_version >= 3 then
144
- s.add_runtime_dependency("rubigen", [">= 1.0.6"])
156
+ # Runtime dependencies
145
157
  s.add_runtime_dependency("log4r", [">= 1.0.5"])
146
158
  s.add_runtime_dependency("activesupport", [">= 2.1.0"])
159
+
160
+ # Development dependencies
161
+ s.add_development_dependency('rubigen', [">= 1.0.6"])
162
+ s.add_development_dependency('rspec', ["< 2.0.0"])
163
+ s.add_development_dependency('test-unit')
164
+ s.add_development_dependency('flexmock')
165
+ s.add_development_dependency('active_record')
147
166
  else
148
- s.add_dependency("rubigen", [">= 1.0.6"])
149
167
  s.add_dependency("log4r", [">= 1.0.5"])
150
168
  s.add_dependency("activesupport", [">= 2.1.0"])
151
169
  end
152
170
  else
153
- s.add_dependency("rubigen", [">= 1.0.6"])
154
171
  s.add_dependency("log4r", [">= 1.0.5"])
155
172
  s.add_dependency("activesupport", [">= 2.1.0"])
156
173
  end
@@ -1,5 +1,9 @@
1
- require 'rack'
2
- require 'json'
1
+ begin
2
+ require 'rack'
3
+ require 'json'
4
+ rescue LoadError
5
+ abort "ERROR: restful_rpc requires the 'rack' and 'json' gems"
6
+ end
3
7
 
4
8
  # Don't you love regular expressions? Matches only 0-255 octets. Recognizes "*" as an octet wildcard.
5
9
  VALID_IP_ADDRESS = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|\*)$/
@@ -1,7 +1,9 @@
1
1
  unless defined? Adhearsion
2
2
  if File.exists? File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
3
- # If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
4
- # into a folder named "adhearsion" within this app's main directory.
3
+ # For development purposes try to load a local copy of Adhearsion here.
4
+ # This will not work if started using "ahn" or "jahn". You must execute
5
+ # config/startup.rb directly and have a local checkout of Adhearsion in your
6
+ # application directory.
5
7
  require File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
6
8
  else
7
9
  require 'rubygems'
@@ -12,6 +14,13 @@ end
12
14
 
13
15
  Adhearsion::Configuration.configure do |config|
14
16
 
17
+ # Components to load from the system.
18
+ # All components that are activated in components/ will be automatically
19
+ # loaded and made available.
20
+ # This configuration option allows you to load components provided by gems.
21
+ # List the gem names here:
22
+ # config.add_component "ahn_test_component"
23
+
15
24
  # Supported levels (in increasing severity) -- :debug < :info < :warn < :error < :fatal
16
25
  config.logging :level => :info
17
26
 
@@ -24,7 +24,13 @@ require 'adhearsion/voip/asterisk/commands'
24
24
  require 'adhearsion/voip/dsl/dialing_dsl'
25
25
  require 'adhearsion/voip/call_routing'
26
26
 
27
- require 'active_support/core_ext'
27
+ begin
28
+ # Try ActiveSupport >= 2.3.0
29
+ require 'active_support/all'
30
+ rescue LoadError
31
+ # Assume ActiveSupport < 2.3.0
32
+ require 'active_support'
33
+ end
28
34
 
29
35
  module Adhearsion
30
36
  # Sets up the Gem require path.
@@ -43,6 +43,13 @@ module Adhearsion
43
43
  components.map! { |path| File.basename path }
44
44
  components.each do |component|
45
45
  next if component == "disabled"
46
+ component_file = File.join(@path_to_container_directory, component, 'lib', component + ".rb")
47
+ if File.exists? component_file
48
+ load_file component_file
49
+ next
50
+ end
51
+
52
+ # Try the old-style components/<component>/<component>.rb
46
53
  component_file = File.join(@path_to_container_directory, component, component + ".rb")
47
54
  if File.exists? component_file
48
55
  load_file component_file
@@ -51,6 +58,11 @@ module Adhearsion
51
58
  end
52
59
  end
53
60
 
61
+ # Load configured system- or gem-provided components
62
+ AHN_CONFIG.components_to_load.each do |component|
63
+ require component
64
+ end
65
+
54
66
  end
55
67
 
56
68
  ##
@@ -59,11 +71,19 @@ module Adhearsion
59
71
  # @return [Hash] The loaded YAML for the given component name. An empty Hash if no YAML file exists.
60
72
  #
61
73
  def configuration_for_component_named(component_name)
74
+ # Look for configuration in #{AHN_ROOT}/config/components first
75
+ if File.exists?("#{AHN_ROOT}/config/components/#{component_name}.yml")
76
+ return YAML.load_file "#{AHN_ROOT}/config/components/#{component_name}.yml"
77
+ end
78
+
79
+ # Next try the local app component directory
62
80
  component_dir = File.join(@path_to_container_directory, component_name)
63
81
  config_file = File.join component_dir, "#{component_name}.yml"
64
82
  if File.exists?(config_file)
65
83
  YAML.load_file config_file
66
84
  else
85
+ # Nothing found? Return an empty hash
86
+ ahn_log.warn "No configuration found for requested component #{component_name}"
67
87
  return {}
68
88
  end
69
89
  end
@@ -92,6 +112,10 @@ module Adhearsion
92
112
  load_container ComponentDefinitionContainer.load_file(filename)
93
113
  end
94
114
 
115
+ def require(filename)
116
+ load_container ComponentDefinitionContainer.require(filename)
117
+ end
118
+
95
119
  protected
96
120
 
97
121
  def load_container(container)
@@ -116,16 +140,37 @@ module Adhearsion
116
140
 
117
141
  class << self
118
142
  def load_code(code)
119
- returning(new) do |instance|
143
+ new.tap do |instance|
120
144
  instance.module_eval code
121
145
  end
122
146
  end
123
147
 
124
148
  def load_file(filename)
125
- returning(new) do |instance|
149
+ new.tap do |instance|
126
150
  instance.module_eval File.read(filename), filename
127
151
  end
128
152
  end
153
+
154
+ def require(filename)
155
+ filename = filename + ".rb" if !(filename =~ /\.rb$/)
156
+ begin
157
+ # Try loading the exact filename first
158
+ load_file(filename)
159
+ rescue LoadError, Errno::ENOENT
160
+ end
161
+
162
+ # Next try Rubygems
163
+ filepath = get_gem_path_for(filename)
164
+ return load_file(filepath) if !filepath.nil?
165
+
166
+ # Finally try the system search path
167
+ filepath = get_system_path_for(filename)
168
+ return load_file(filepath) if !filepath.nil?
169
+
170
+ # Raise a LoadError exception if the file is still not found
171
+ raise LoadError, "File not found: #{filename}"
172
+ end
173
+
129
174
  end
130
175
 
131
176
  def initialize(&block)
@@ -170,6 +215,26 @@ module Adhearsion
170
215
  @methods ||= []
171
216
  @methods << method_name
172
217
  end
218
+
219
+ def get_gem_path_for(filename)
220
+ # Look for component files provided by rubygems
221
+ spec = Gem.searcher.find(filename)
222
+ return nil if spec.nil?
223
+ File.join(spec.full_gem_path, spec.require_path, filename)
224
+ rescue NameError
225
+ # In case Rubygems are not available
226
+ nil
227
+ end
228
+
229
+ def get_system_path_for(filename)
230
+ $:.each do |path|
231
+ filepath = File.join(path, filename)
232
+ return filepath if File.exists?(filepath)
233
+ end
234
+
235
+ # Not found? Return nil
236
+ return nil
237
+ end
173
238
  end
174
239
 
175
240
  end
@@ -1,7 +1,13 @@
1
1
  require 'English'
2
2
  require 'tmpdir'
3
3
  require 'tempfile'
4
- require 'active_support'
4
+ begin
5
+ # Try ActiveSupport >= 2.3.0
6
+ require 'active_support/all'
7
+ rescue LoadError
8
+ # Assume ActiveSupport < 2.3.0
9
+ require 'active_support'
10
+ end
5
11
 
6
12
  # Require all other files here.
7
13
  Dir.glob File.join(File.dirname(__FILE__), "*rb") do |file|
@@ -1,5 +1,3 @@
1
1
  class BlankSlate
2
- instance_methods.each do |method|
3
- undef_method method unless method =~ /^__/ || method == 'instance_eval'
4
- end
2
+ (instance_methods - %w{instance_eval object_id}).each { |m| undef_method m unless m =~ /^__/ }
5
3
  end
@@ -128,11 +128,10 @@ module Adhearsion
128
128
  @daemon = options[:daemon] || ENV['DAEMON']
129
129
  @pid_file = options[:pid_file].nil? ? ENV['PID_FILE'] : options[:pid_file]
130
130
  @loaded_init_files = options[:loaded_init_files]
131
+ self.class.ahn_root = path
131
132
  end
132
133
 
133
134
  def start
134
- self.class.ahn_root = path
135
-
136
135
  Adhearsion.status = :starting
137
136
 
138
137
  resolve_pid_file_path
@@ -35,7 +35,7 @@ module Adhearsion
35
35
  def initialize_ami
36
36
  options = ami_options
37
37
  start_ami_after_initialized
38
- returning VoIP::Asterisk::Manager::ManagerInterface.new(options) do
38
+ VoIP::Asterisk::Manager::ManagerInterface.new(options).tap do
39
39
  class << VoIP::Asterisk
40
40
  if respond_to?(:manager_interface)
41
41
  ahn_log.warn "Asterisk.manager_interface already initialized?"
@@ -50,7 +50,7 @@ module Adhearsion
50
50
  end
51
51
 
52
52
  def ami_options
53
- %w(host port username password events).inject({}) do |options, property|
53
+ %w(host port username password events auto_reconnect).inject({}) do |options, property|
54
54
  options[property.to_sym] = config.ami.send property
55
55
  options
56
56
  end
@@ -35,11 +35,13 @@ module Adhearsion
35
35
  attr_accessor :automatically_answer_incoming_calls
36
36
  attr_accessor :end_call_on_hangup
37
37
  attr_accessor :end_call_on_error
38
+ attr_accessor :components_to_load
38
39
 
39
40
  def initialize
40
41
  @automatically_answer_incoming_calls = true
41
42
  @end_call_on_hangup = true
42
43
  @end_call_on_error = true
44
+ @components_to_load = []
43
45
  yield self if block_given?
44
46
  end
45
47
 
@@ -67,6 +69,10 @@ module Adhearsion
67
69
  Adhearsion::Logging.logging_level = options[:level]
68
70
  end
69
71
 
72
+ def add_component(*list)
73
+ AHN_CONFIG.components_to_load |= list
74
+ end
75
+
70
76
  ##
71
77
  # Adhearsion's .ahnrc file is used to define paths to certain parts of the framework. For example, the name dialplan.rb
72
78
  # is actually specified in .ahnrc. This file can actually be just a filename, a filename with a glob (.e.g "*.rb"), an
@@ -129,8 +135,8 @@ module Adhearsion
129
135
  end
130
136
 
131
137
  def initialize(overrides = {})
132
- @listening_port = overrides.has_key?(:port) ? overrides.delete(:port) : self.class.default_listening_port
133
138
  @listening_host = overrides.has_key?(:host) ? overrides.delete(:host) : self.class.default_listening_host
139
+ @listening_port = overrides.has_key?(:port) ? overrides.delete(:port) : self.class.default_listening_port
134
140
  super
135
141
  end
136
142
  end
@@ -158,7 +164,7 @@ module Adhearsion
158
164
  end
159
165
 
160
166
  class AMIConfiguration < AbstractConfiguration
161
- attr_accessor :port, :username, :password, :events, :host
167
+ attr_accessor :port, :username, :password, :events, :host, :auto_reconnect
162
168
 
163
169
  class << self
164
170
  def default_port
@@ -172,12 +178,17 @@ module Adhearsion
172
178
  def default_host
173
179
  'localhost'
174
180
  end
181
+
182
+ def default_auto_reconnect
183
+ true
184
+ end
175
185
  end
176
186
 
177
187
  def initialize(overrides = {})
178
- self.host = self.class.default_host
179
- self.port = self.class.default_port
180
- self.events = self.class.default_events
188
+ self.host = self.class.default_host
189
+ self.port = self.class.default_port
190
+ self.events = self.class.default_events
191
+ self.auto_reconnect = self.class.default_auto_reconnect
181
192
  super
182
193
  end
183
194
  end
@@ -1,5 +1,6 @@
1
1
  require 'rake/testtask'
2
2
  require 'adhearsion'
3
+ require 'adhearsion/tasks/components'
3
4
  require 'adhearsion/tasks/database'
4
5
  require 'adhearsion/tasks/testing'
5
6
  require 'adhearsion/tasks/generating'
@@ -0,0 +1,32 @@
1
+ namespace:components do
2
+ desc "Install component configuration from templates"
3
+ task :genconfig do
4
+ init = Adhearsion::Initializer.new(Rake.original_dir)
5
+ init.bootstrap_rc
6
+ init.load_all_init_files
7
+ Adhearsion::AHN_CONFIG.components_to_load.each do |component|
8
+ spec = Gem.searcher.find(component)
9
+ if spec.nil?
10
+ abort "ERROR: Required gem component #{component} not found."
11
+ end
12
+
13
+ yml = File.join(spec.full_gem_path, 'config', "#{component}.yml")
14
+ target = File.join(AHN_ROOT, 'config', 'components', "#{component}.yml")
15
+ Dir.mkdir(File.dirname(target)) if !File.exists?(File.dirname(target))
16
+ if File.exists?(target)
17
+ puts "Skipping existing configuration for component #{component}"
18
+ next
19
+ end
20
+ if File.exists?(yml)
21
+ begin
22
+ FileUtils.cp(yml, target)
23
+ puts "Installed default configuration for component #{component}"
24
+ rescue => e
25
+ abort "Error copying configuration for component #{component}: #{e.message}"
26
+ end
27
+ else
28
+ puts "No template configuration found for component #{component}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,8 +1,8 @@
1
1
  module Adhearsion #:nodoc:
2
2
  module VERSION #:nodoc:
3
- MAJOR = 0 unless defined? MAJOR
4
- MINOR = 8 unless defined? MINOR
5
- TINY = 6 unless defined? TINY
3
+ MAJOR = 1 unless defined? MAJOR
4
+ MINOR = 0 unless defined? MINOR
5
+ TINY = 0 unless defined? TINY
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.') unless defined? STRING
8
8
  end