adhearsion 0.8.6 → 1.0.0

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