adhearsion 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.version +1 -1
  2. data/CHANGELOG +43 -25
  3. data/Rakefile +0 -5
  4. data/TODO +51 -2
  5. data/ahn +2 -1
  6. data/apps/default/Rakefile +16 -7
  7. data/apps/default/config/adhearsion.yml +22 -1
  8. data/apps/default/config/helpers/manager_proxy.yml +1 -0
  9. data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
  10. data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
  11. data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
  12. data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
  13. data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
  14. data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
  15. data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
  16. data/apps/default/config/helpers/micromenus/images/green.png +0 -0
  17. data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
  18. data/apps/default/config/helpers/micromenus/images/red.png +0 -0
  19. data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
  20. data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
  21. data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
  22. data/apps/default/config/helpers/micromenus/javascripts/animation.js +1341 -0
  23. data/apps/default/config/helpers/micromenus/javascripts/carousel.js +1238 -0
  24. data/apps/default/config/helpers/micromenus/javascripts/columnav.js +306 -0
  25. data/apps/default/config/helpers/micromenus/javascripts/connection.js +965 -0
  26. data/apps/default/config/helpers/micromenus/javascripts/container.js +4727 -0
  27. data/apps/default/config/helpers/micromenus/javascripts/container_core.js +2915 -0
  28. data/apps/default/config/helpers/micromenus/javascripts/dom.js +892 -0
  29. data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +2921 -907
  30. data/apps/default/config/helpers/micromenus/javascripts/event.js +1771 -0
  31. data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +433 -0
  32. data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +78 -0
  33. data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +135 -0
  34. data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +42 -0
  35. data/apps/default/config/helpers/multi_messenger.yml +5 -1
  36. data/apps/default/config/migration.rb +10 -0
  37. data/apps/default/extensions.rb +1 -1
  38. data/apps/default/helpers/factorial.alien.c +3 -3
  39. data/apps/default/helpers/lookup.rb +2 -1
  40. data/apps/default/helpers/manager_proxy.rb +67 -15
  41. data/apps/default/helpers/micromenus.rb +173 -31
  42. data/apps/default/helpers/multi_messenger.rb +20 -3
  43. data/lib/adhearsion.rb +218 -88
  44. data/lib/constants.rb +1 -0
  45. data/lib/core_extensions.rb +15 -9
  46. data/lib/phone_number.rb +85 -0
  47. data/lib/rami.rb +3 -2
  48. data/lib/servlet_container.rb +47 -24
  49. data/lib/sexy_migrations.rb +70 -0
  50. data/test/asterisk_module_test.rb +9 -9
  51. data/test/specs/numerical_string_spec.rb +53 -0
  52. metadata +31 -11
  53. data/apps/default/config/helpers/micromenus/javascripts/builder.js +0 -131
  54. data/apps/default/config/helpers/micromenus/javascripts/controls.js +0 -834
  55. data/apps/default/config/helpers/micromenus/javascripts/effects.js +0 -956
  56. data/apps/default/config/helpers/micromenus/javascripts/prototype.js +0 -2319
  57. data/apps/default/config/helpers/micromenus/javascripts/scriptaculous.js +0 -51
  58. data/apps/default/config/helpers/micromenus/javascripts/slider.js +0 -278
  59. data/apps/default/config/helpers/micromenus/javascripts/unittest.js +0 -557
  60. data/apps/default/config/helpers/micromenus/stylesheets/firefox.css +0 -10
  61. data/apps/default/config/helpers/micromenus/stylesheets/firefox.xul.css +0 -44
@@ -19,5 +19,6 @@
19
19
  LOCAL_NUMBER = /^[1-9]\d{6}$/
20
20
  US_NUMBER = /^1?[1-9]\d{2}[1-9]\d{6}$/
21
21
  ISN = /^\d+\*\d+$/ # See http://freenum.org
22
+ SIP_URI = /^sip:[\w\._%+-]+@[\w\.-]+\.[a-zA-Z]{2,4}$/
22
23
 
23
24
  Infinity = 1.0/0.0
@@ -39,14 +39,6 @@ class Object
39
39
  nil
40
40
  end
41
41
 
42
- def is_local_number?
43
- to_s =~ LOCAL_NUMBER
44
- end
45
-
46
- def is_national_number?
47
- to_s =~ US_NUMBER
48
- end
49
-
50
42
  def mutex() @mutex ||= Mutex.new end
51
43
  def synchronize() mutex.synchronize { yield self } end
52
44
 
@@ -63,13 +55,15 @@ class Object
63
55
  name = priority == :normal ? "AFTER_CALL" : "AFTER_CALL_#{priority.to_s.upcase}"
64
56
  eval("$#{name}") << block
65
57
  end
58
+
59
+ def segment(&block) block.call end
66
60
  end
67
61
 
68
62
  class String
69
63
 
70
64
  def String.random_char
71
65
  case r = rand(62)
72
- when 0...10 then r.to_s
66
+ when 0...10 then r.to_s
73
67
  when 10...36 then (r+55).chr
74
68
  when 36...62 then (r+61).chr
75
69
  end
@@ -136,6 +130,8 @@ class Numeric
136
130
  def =~ other
137
131
  to_s =~ other
138
132
  end
133
+ def length() to_s.length end
134
+ alias size length
139
135
 
140
136
  def am() self end
141
137
  def pm() self+12 end
@@ -148,6 +144,16 @@ class Numeric
148
144
  end
149
145
  end
150
146
 
147
+ class Integer
148
+ alias old_to_i to_i
149
+ # Allows 123.to_i(2) to work for base conversions.
150
+ def to_i base=nil
151
+ if base then to_s.to_i base
152
+ else self
153
+ end
154
+ end
155
+ end
156
+
151
157
  class Time
152
158
  def weekday?() (1..5).include? wday end
153
159
  def weekend?() !weekday? end
@@ -0,0 +1,85 @@
1
+ class NumericalString
2
+
3
+ (instance_methods - %w"__id__ __send__ __real_num __real_string").each do |m|
4
+ undef_method m
5
+ end
6
+
7
+ def initialize str
8
+ @__real_string = str.to_s
9
+ @__real_num = str.to_i if @__real_string =~ /^\d+$/
10
+ end
11
+
12
+ attr_reader :__real_num, :__real_string
13
+
14
+ def method_missing name, *args, &block
15
+ @__real_string.__send__ name, *args, &block
16
+ end
17
+
18
+ def respond_to? m
19
+ @__real_string.respond_to?(m) || m == :__real_num || m == :__real_string
20
+ end
21
+
22
+ end
23
+
24
+ # The PhoneNumber class is used by one object throughout Adhearsion: the AGI
25
+ # "extension" variable. Using some clevery Ruby hackery, the Extension class allows
26
+ # dialplan writers to use the best of Fixnum and String usage such as
27
+ #
28
+ # - Dialing international numbers
29
+ # - Using a regexp in a case statement for "extension"
30
+ # - Using a numerical range against extension -- e.g. (100...200)
31
+ # - Using the thousands separator
32
+ class PhoneNumber < NumericalString
33
+
34
+ # Checks against a pattern identifying US local numbers (i.e numbers
35
+ # without an area code seven digits long)
36
+ def local_number?() to_s =~ LOCAL_NUMBER end
37
+
38
+ # Checks against a pattern identifying US domestic numbers.
39
+ def national_number?() to_s =~ US_NUMBER end
40
+
41
+ # Checks against a pattern identifying an ISN number. See http://freenum.org
42
+ # for more info.
43
+ def isn?() to_s =~ ISN end
44
+
45
+ # Useful for dialing those 1-800-FUDGEME type numbers with letters in them. Letters
46
+ # in the argument will be converted to their appropriate keypad key.
47
+ def self.from_vanity str
48
+ str.gsub(/\W/, '').upcase.tr('A-Z', '22233344455566677778889999')
49
+ end
50
+
51
+ end
52
+
53
+ class Object
54
+
55
+ alias _equalequalequal ===
56
+
57
+ def === arg
58
+ if arg.respond_to? :__real_string
59
+ arg = arg.__real_num if kind_of?(Numeric) || kind_of?(Range)
60
+ _equalequalequal arg
61
+ else
62
+ _equalequalequal arg
63
+ end
64
+ end
65
+ end
66
+
67
+ class Range
68
+ alias _old_equalequalequal ===
69
+ def === arg
70
+ if arg.respond_to? :__real_string
71
+ arg = arg.__real_num if kind_of?(Numeric) || kind_of?(Range)
72
+ _old_equalequalequal arg
73
+ else
74
+ _old_equalequalequal arg
75
+ end
76
+ end
77
+ end
78
+
79
+ class << String
80
+ # Had to do this for ActiveRecord compatability.
81
+ alias __equalequalequal ===
82
+ def === arg
83
+ arg.respond_to?(:__real_string) ? true : __equalequalequal(arg)
84
+ end
85
+ end
@@ -215,7 +215,7 @@ class Client
215
215
  'Exten' =>h['Exten'],
216
216
  'Priority' =>h['Priority'],
217
217
  'Timeout' =>h['Timeout'],
218
- 'CallerID' =>h['CallerID'],
218
+ 'CallerID' =>h['Callerid'] || h['CallerID'],
219
219
  'Variable' =>h['Variable'],
220
220
  'Account' =>h['Account'],
221
221
  'Application' =>h['Application'],
@@ -387,7 +387,8 @@ def connect
387
387
  $HUTDOWN.now!
388
388
  return
389
389
  end
390
- login = {'Action' => 'login', 'Username' => @username, 'Secret' => @secret, 'Events' => 'Off'}
390
+ events = $HELPERS['manager_proxy']['events']
391
+ login = {'Action' => 'login', 'Username' => @username, 'Secret' => @secret, 'Events' => events ? 'On' : 'Off'}
391
392
  writesock(login)
392
393
  accum = {}
393
394
  login = 0
@@ -17,7 +17,7 @@
17
17
 
18
18
  class ServletContainer
19
19
 
20
- # TODO: Port Mongrel server here.
20
+ # TODO: Port more efficient server here. Erlang perhaps?
21
21
  class NativeServer;end
22
22
 
23
23
  require 'gserver'
@@ -30,18 +30,38 @@ class ServletContainer
30
30
  end
31
31
 
32
32
  def read_variables io
33
- call_variables = {}
34
- while(line = io.gets.chomp)
33
+ vars = {}
34
+ while line = io.gets.chomp
35
35
  break if line.empty? # Empty lines signify no more variables
36
36
  variable = line.split(/:\s*/)
37
37
  new_name, new_value = variable.first[4..-1].downcase, variable.last
38
- if new_name == 'extension'
39
- new_value.gsub! '-', '_'
40
- call_variables['str_extension'] = new_value
38
+
39
+ if new_name == 'extension' then vars['extension'] = PhoneNumber.new new_value
40
+ elsif new_name == 'context' then vars['context'] = new_value.gsub '-', '_'
41
+ elsif new_name == 'request'
42
+ uri = URI.parse new_value
43
+ query = {}
44
+ if uri.query
45
+ hash = {}
46
+ uri.query.split('&').each do |a|
47
+ k,v = a.split('=')
48
+ query[k] = v
49
+ end
50
+ context = query['context']
51
+ end
52
+ vars['request'], vars['uri'], vars['query'] = new_value, uri, query
53
+ else
54
+ vars[new_name.to_s] = if new_value =~ /^\d+$/
55
+ if new_value.starts_with?('0') then NumericalString.new(new_value)
56
+ else new_value.to_i
57
+ end
58
+ else new_value
59
+ end
41
60
  end
42
- call_variables["#{new_name}"] = new_value =~ /^\d+$/ ? Integer(new_value) : new_value
61
+
43
62
  end
44
- call_variables
63
+ vars['context'] = vars['query']['context'] if vars['query']['context']
64
+ vars
45
65
  end
46
66
 
47
67
  # GServer allows all functionality to be packed into this one serve() method.
@@ -70,7 +90,7 @@ class ServletContainer
70
90
  end
71
91
 
72
92
  log "Executing call with variables: " + call_variables.inspect
73
-
93
+
74
94
  call_variables.each do |k,v|
75
95
  Thread.current[:container].run_inside do
76
96
  meta_def(k) { v }
@@ -78,17 +98,18 @@ class ServletContainer
78
98
  end
79
99
 
80
100
  # Execute all before_call hooks
81
- [$BEFORE_CALl_HIGH, $BEFORE_CALL, $BEFORE_CALL_LOW].flatten.compact.each(&:call)
82
- +lambda { answer if CONFIG.answer_before_call }
101
+ [$BEFORE_CALL_HIGH, $BEFORE_CALL, $BEFORE_CALL_LOW].flatten.compact.each(&:call)
102
+ +lambda { answer if CONFIG['answer_before_call'] }
83
103
 
84
104
  # A call hook may decree that the call shouldn't be processed further (e.g. if
85
105
  # it processed the call itself). This is done be rewriting the context variable.
86
106
  unless Thread.current[:VARS]['context'] == :interrupted
87
-
107
+
88
108
  begin
109
+
89
110
  Contexts.new.instance_eval do
90
111
  # Interpret the extensions.rb file!
91
- eval File.read(File.join(Dir.pwd, "extensions.rb"))
112
+ eval File.read("extensions.rb")
92
113
  end
93
114
  rescue => detail
94
115
  log "Exception raised in extensions.rb! " << detail.message
@@ -98,21 +119,23 @@ class ServletContainer
98
119
 
99
120
  log "Parsing of extensions.rb complete"
100
121
 
101
- # Because Asterisk allows dashes in its context names and Ruby interprets
102
- # these as minuses, let's convert them to underscores.
103
- call_variables['context'].gsub! '-', '_'
104
-
105
122
  begin
106
123
  target_context = call_variables['context']
107
124
  if target_context
108
125
  Thread.current[:container].run_inside do
109
126
  begin
110
- +send(target_context.to_s.to_sym)
127
+ unless target_context.kind_of? Proc
128
+ target_context = send(target_context.to_s.to_sym)
129
+ end
130
+ +target_context
131
+ rescue ControlPassingException => new_target
132
+ target_context = new_target.target
133
+ retry
111
134
  rescue => e
112
135
  error e.inspect + "\n " + e.backtrace * "\n "
113
136
  +lambda {
114
- play 'were-sorry'
115
- hangup
137
+ play 'an-error-has-occurred', 'were-sorry'
138
+ hangup if CONFIG.key?('hangup_after_error') && CONFIG['hangup_after_error']
116
139
  }
117
140
  end
118
141
  end
@@ -122,14 +145,14 @@ class ServletContainer
122
145
  return
123
146
  end
124
147
  rescue => detail
125
- log "ERROR: #{detail.class.name} => #{detail.inspect}"
126
- detail.backtrace.each do |msg| log " "*8 << msg end
148
+ error "ERROR: #{detail.class.name} => #{detail.inspect}"
149
+ detail.backtrace.each do |msg| error " " * 8 << msg end
127
150
  end
128
151
  log "Call routing complete"
129
152
  end
130
153
  rescue => detail
131
- log "Call thread raised an exception! #{detail.message}"
132
- detail.backtrace.each do |msg| log " "*8 << msg end
154
+ error "Call thread raised an exception! #{detail.message}"
155
+ detail.backtrace.each { |msg| error " " * 8 << msg }
133
156
  end
134
157
 
135
158
  [$AFTER_CALL_HIGH, $AFTER_CALL, $AFTER_CALL_LOW].flatten.compact.each(&:call)
@@ -0,0 +1,70 @@
1
+ # Taken from svn://errtheblog.com/svn/plugins/sexy_migrations/lib/sexy_migrations.rb
2
+ module SexyMigrations
3
+ module Table
4
+ attr_reader :fk_references
5
+
6
+ def foreign_key(*args)
7
+ options = args.last.is_a?(Hash) ? args.pop : {}
8
+ args.each do |col|
9
+ column(id = "#{col}_id", :integer, options)
10
+ (@fk_references ||= []) << [id, options[:ref].to_s] if options[:ref]
11
+ end
12
+ end
13
+ alias :fkey :foreign_key
14
+ alias :fkeys :foreign_key
15
+ alias :foreign_keys :foreign_key
16
+
17
+ def timestamps(*extras)
18
+ (Array(extras) + %w(created_at updated_at)).each do |stamp|
19
+ datetime stamp
20
+ end
21
+ end
22
+ alias :timestamps! :timestamps
23
+ alias :auto_dates :timestamps
24
+ alias :auto_dates! :timestamps
25
+
26
+ def polymorphic(name)
27
+ integer "#{name}_id"
28
+ string "#{name}_type"
29
+ end
30
+ alias :polymorphic! :polymorphic
31
+
32
+ def method_missing(name, *args)
33
+ return super unless type = simplified_type(name)
34
+ options = args.last.is_a?(Hash) ? args.pop : {}
35
+ args.each { |col| column(col, type, options) }
36
+ end
37
+
38
+ private
39
+ def simplified_type(type)
40
+ ActiveRecord::ConnectionAdapters::Column.new(:type_check, false, type.to_s).type
41
+ end
42
+ end
43
+
44
+ module Schema
45
+ def create_table(name, options = {}, &block)
46
+ table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(self)
47
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
48
+
49
+ table_definition.instance_eval &block
50
+
51
+ if options[:force]
52
+ drop_table(name, options) rescue nil
53
+ end
54
+
55
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
56
+ create_sql << "#{name} ("
57
+ create_sql << table_definition.to_sql
58
+ create_sql << ") #{options[:options]}"
59
+
60
+ # if any fk contraints, add them
61
+ if table_definition.fk_references
62
+ create_sql << "; "
63
+ table_definition.fk_references.each do |fk|
64
+ create_sql << "ALTER TABLE #{name} ADD FOREIGN KEY (#{fk[0]}) REFERENCES #{fk[1]} (id); "
65
+ end
66
+ end
67
+ execute create_sql
68
+ end
69
+ end
70
+ end
@@ -1,13 +1,13 @@
1
- context "The Asterisk module" do
1
+ context "The PBX object" do
2
2
  specify "should properize properly" do
3
- properize("123").should == 'SIP/123'
4
- properize(1_555_444_1234).should == 'SIP/15554441234'
5
- properize(:jay).should == 'SIP/jay'
6
- properize("jay").should == 'SIP/jay'
7
- properize("SIP/123").should == 'SIP/123'
8
- properize("IAX2/jay").should == 'IAX2/jay'
9
- properize((100..110).to_a).should == 'SIP/100&SIP/101&SIP/102&SIP/103&SIP/104&SIP/105&SIP/106&SIP/107&SIP/108&SIP/109&SIP/110'
10
- properize(["SIP/123", "SIP/456"]).should == 'SIP/123&SIP/456'
3
+ PBX.properize("123").should == 'SIP/123'
4
+ PBX.properize(1_555_444_1234).should == 'SIP/15554441234'
5
+ PBX.properize(:jay).should == 'SIP/jay'
6
+ PBX.properize("jay").should == 'SIP/jay'
7
+ PBX.properize("SIP/123").should == 'SIP/123'
8
+ PBX.properize("IAX2/jay").should == 'IAX2/jay'
9
+ PBX.properize((100..110).to_a).should == 'SIP/100&SIP/101&SIP/102&SIP/103&SIP/104&SIP/105&SIP/106&SIP/107&SIP/108&SIP/109&SIP/110'
10
+ PBX.properize(["SIP/123", "SIP/456"]).should == 'SIP/123&SIP/456'
11
11
  # TODO: Should mock up dialing a user
12
12
  # TODO: Should mock up dialing a group
13
13
  end
@@ -0,0 +1,53 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'phone_number.rb')
2
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'constants.rb')
3
+
4
+ context "A NumericalString" do
5
+ specify "should be accessable as a Fixnum to a case statement" do
6
+ (123 === NumericalString.new("123")).should == true
7
+ (987 === NumericalString.new("0987")).should == true
8
+ end
9
+ specify "should be accessable as a String to a case statement" do
10
+ ("123" === NumericalString.new("123")).should == true
11
+ ("0987" === NumericalString.new("0987")).should == true
12
+ end
13
+
14
+ specify "should satisfy Ranges" do
15
+ (100..200).should === NumericalString.new("150")
16
+ (100..200).should === NumericalString.new("0150")
17
+ ((100..200) === NumericalString.new("1000000")).should === false
18
+ end
19
+
20
+ specify "should satify regular expressions" do
21
+ /^\d+$/.should === NumericalString.new("027316287")
22
+ end
23
+ end
24
+ #
25
+ # case extension
26
+ # when US_NUMBER
27
+ # when (100..200)
28
+ # when _('12Z')
29
+ # when 123
30
+ # when "123"
31
+ # end
32
+
33
+ context "A PhoneNumber" do
34
+ specify "should have an ISN pattern-matching method" do
35
+ !! PhoneNumber.new("0115544332211").isn?.should == false
36
+ !! PhoneNumber.new("1*548").isn?.should === false
37
+ end
38
+
39
+ specify "should have a US local number pattern-matching method" do
40
+ !! PhoneNumber.new("18887776665555").local_number?.should == false
41
+ !! PhoneNumber.new("18887776665555").national_number?.should == true
42
+
43
+ !! PhoneNumber.new("8887776665555").local_number?.should == false
44
+ !! PhoneNumber.new("8887776665555").national_number?.should == true
45
+
46
+ !! PhoneNumber.new("4445555").local_number?.should == true
47
+ !! PhoneNumber.new("4445555").national_number?.should == false
48
+ end
49
+
50
+ specify "should convert from vanity numbers properly" do
51
+ PhoneNumber.from_vanity("1-800-FUDGEME").should == "18003834363"
52
+ end
53
+ end