adhearsion 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. data/CHANGELOG +9 -42
  2. data/EVENTS +11 -0
  3. data/README.txt +5 -0
  4. data/Rakefile +94 -84
  5. data/adhearsion.gemspec +148 -0
  6. data/app_generators/ahn/USAGE +5 -0
  7. data/app_generators/ahn/ahn_generator.rb +87 -0
  8. data/app_generators/ahn/templates/.ahnrc +34 -0
  9. data/app_generators/ahn/templates/README +8 -0
  10. data/app_generators/ahn/templates/Rakefile +23 -0
  11. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  12. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  13. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  14. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  15. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  16. data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
  17. data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
  18. data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
  19. data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
  20. data/app_generators/ahn/templates/components/restful_rpc/spec/restful_rpc_spec.rb +263 -0
  21. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  22. data/app_generators/ahn/templates/config/startup.rb +50 -0
  23. data/app_generators/ahn/templates/dialplan.rb +3 -0
  24. data/app_generators/ahn/templates/events.rb +32 -0
  25. data/bin/ahn +28 -0
  26. data/bin/ahnctl +68 -0
  27. data/bin/jahn +42 -0
  28. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  29. data/lib/adhearsion.rb +35 -953
  30. data/lib/adhearsion/cli.rb +223 -0
  31. data/lib/adhearsion/component_manager.rb +208 -0
  32. data/lib/adhearsion/component_manager/component_tester.rb +55 -0
  33. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  34. data/lib/adhearsion/events_support.rb +84 -0
  35. data/lib/adhearsion/foundation/all.rb +9 -0
  36. data/lib/adhearsion/foundation/blank_slate.rb +5 -0
  37. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  38. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  39. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  40. data/lib/adhearsion/foundation/global.rb +1 -0
  41. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  42. data/lib/adhearsion/foundation/numeric.rb +13 -0
  43. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  44. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  45. data/lib/adhearsion/foundation/string.rb +26 -0
  46. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  47. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  48. data/lib/adhearsion/host_definitions.rb +67 -0
  49. data/lib/adhearsion/initializer.rb +373 -0
  50. data/lib/adhearsion/initializer/asterisk.rb +81 -0
  51. data/lib/adhearsion/initializer/configuration.rb +254 -0
  52. data/lib/adhearsion/initializer/database.rb +49 -0
  53. data/lib/adhearsion/initializer/drb.rb +31 -0
  54. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  55. data/lib/adhearsion/initializer/rails.rb +40 -0
  56. data/lib/adhearsion/logging.rb +92 -0
  57. data/lib/adhearsion/tasks.rb +16 -0
  58. data/lib/adhearsion/tasks/database.rb +5 -0
  59. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  60. data/lib/adhearsion/tasks/generating.rb +20 -0
  61. data/lib/adhearsion/tasks/lint.rb +4 -0
  62. data/lib/adhearsion/tasks/testing.rb +37 -0
  63. data/lib/adhearsion/version.rb +9 -0
  64. data/lib/adhearsion/voip/asterisk.rb +4 -0
  65. data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
  66. data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
  67. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  68. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  69. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  70. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  71. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  72. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  73. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  74. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  75. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  76. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  77. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  78. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  79. data/lib/adhearsion/voip/call.rb +440 -0
  80. data/lib/adhearsion/voip/call_routing.rb +64 -0
  81. data/lib/adhearsion/voip/commands.rb +9 -0
  82. data/lib/adhearsion/voip/constants.rb +39 -0
  83. data/lib/adhearsion/voip/conveniences.rb +18 -0
  84. data/lib/adhearsion/voip/dial_plan.rb +218 -0
  85. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  86. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  87. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  88. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  89. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
  90. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  91. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  92. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  93. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  94. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  95. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  96. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  97. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  98. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  99. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  100. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  101. data/lib/theatre.rb +151 -0
  102. data/lib/theatre/README.markdown +64 -0
  103. data/lib/theatre/callback_definition_loader.rb +84 -0
  104. data/lib/theatre/guid.rb +23 -0
  105. data/lib/theatre/invocation.rb +121 -0
  106. data/lib/theatre/namespace_manager.rb +153 -0
  107. data/lib/theatre/version.rb +2 -0
  108. metadata +160 -140
  109. data/.version +0 -1
  110. data/TODO +0 -71
  111. data/ahn +0 -223
  112. data/apps/default/Rakefile +0 -65
  113. data/apps/default/config/adhearsion.sqlite3 +0 -0
  114. data/apps/default/config/adhearsion.yml +0 -95
  115. data/apps/default/config/database.rb +0 -50
  116. data/apps/default/config/database.yml +0 -12
  117. data/apps/default/config/helpers/drb_server.yml +0 -43
  118. data/apps/default/config/helpers/factorial.alien.c.yml +0 -1
  119. data/apps/default/config/helpers/growler.yml +0 -21
  120. data/apps/default/config/helpers/lookup.yml +0 -1
  121. data/apps/default/config/helpers/manager_proxy.yml +0 -8
  122. data/apps/default/config/helpers/micromenus.yml +0 -1
  123. data/apps/default/config/helpers/micromenus/collab.rb +0 -60
  124. data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
  125. data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
  126. data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
  127. data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
  128. data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
  129. data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
  130. data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
  131. data/apps/default/config/helpers/micromenus/images/green.png +0 -0
  132. data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
  133. data/apps/default/config/helpers/micromenus/images/red.png +0 -0
  134. data/apps/default/config/helpers/micromenus/images/tux.bmp +0 -0
  135. data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
  136. data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
  137. data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
  138. data/apps/default/config/helpers/micromenus/javascripts/animation.js +0 -1341
  139. data/apps/default/config/helpers/micromenus/javascripts/carousel.js +0 -1238
  140. data/apps/default/config/helpers/micromenus/javascripts/columnav.js +0 -306
  141. data/apps/default/config/helpers/micromenus/javascripts/connection.js +0 -965
  142. data/apps/default/config/helpers/micromenus/javascripts/container.js +0 -4727
  143. data/apps/default/config/helpers/micromenus/javascripts/container_core.js +0 -2915
  144. data/apps/default/config/helpers/micromenus/javascripts/dom.js +0 -892
  145. data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +0 -2958
  146. data/apps/default/config/helpers/micromenus/javascripts/event.js +0 -1771
  147. data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +0 -433
  148. data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +0 -78
  149. data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +0 -135
  150. data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +0 -42
  151. data/apps/default/config/helpers/multi_messenger.yml +0 -9
  152. data/apps/default/config/helpers/weather.yml +0 -1
  153. data/apps/default/config/helpers/xbmc.yml +0 -2
  154. data/apps/default/config/migration.rb +0 -59
  155. data/apps/default/extensions.rb +0 -41
  156. data/apps/default/helpers/factorial.alien.c +0 -32
  157. data/apps/default/helpers/growler.rb +0 -53
  158. data/apps/default/helpers/lookup.rb +0 -44
  159. data/apps/default/helpers/manager_proxy.rb +0 -112
  160. data/apps/default/helpers/micromenus.rb +0 -514
  161. data/apps/default/helpers/multi_messenger.rb +0 -53
  162. data/apps/default/helpers/oscar_wilde_quotes.rb +0 -197
  163. data/apps/default/helpers/weather.rb +0 -85
  164. data/apps/default/helpers/xbmc.rb +0 -39
  165. data/apps/default/logs/adhearsion.log +0 -0
  166. data/apps/default/logs/database.log +0 -0
  167. data/lib/constants.rb +0 -24
  168. data/lib/core_extensions.rb +0 -180
  169. data/lib/drb_server.rb +0 -101
  170. data/lib/logging.rb +0 -85
  171. data/lib/phone_number.rb +0 -85
  172. data/lib/rami.rb +0 -823
  173. data/lib/servlet_container.rb +0 -174
  174. data/lib/sexy_migrations.rb +0 -70
  175. data/test/asterisk_module_test.rb +0 -14
  176. data/test/core_extensions_test.rb +0 -26
  177. data/test/dial_test.rb +0 -43
  178. data/test/specs/numerical_string_spec.rb +0 -53
  179. data/test/test_micromenus.rb +0 -0
@@ -0,0 +1,80 @@
1
+ module Adhearsion
2
+ class DialPlan
3
+ class ConfirmationManager
4
+
5
+ class << self
6
+
7
+ def encode_hash_for_dial_macro_argument(options)
8
+ options = options.clone
9
+ macro_name = options.delete :macro
10
+ options[:play] &&= options[:play].kind_of?(Array) ? options[:play].join('++') : options[:play]
11
+ encoded_options = URI.escape options.map { |key,value| "#{key}:#{value}" }.join('!')
12
+ returning "M(#{macro_name}^#{encoded_options})" do |str|
13
+ if str.rindex('^') != str.index('^')
14
+ raise ArgumentError, "You seem to have supplied a :confirm option with a caret (^) in it!" +
15
+ " Please remove it. This will blow Asterisk up."
16
+ end
17
+ end
18
+ end
19
+
20
+ def handle(call)
21
+ new(call).handle
22
+ end
23
+
24
+ def confirmation_call?(call)
25
+ call.variables.has_key?(:network_script) && call.variables[:network_script].starts_with?('confirm!')
26
+ end
27
+
28
+ def decode_hash(encoded_hash)
29
+ encoded_hash = encoded_hash =~ /^M\((.+)\)$/ ? $1 : encoded_hash
30
+ encoded_hash = encoded_hash =~ /^([^:]+\^)?(.+)$/ ? $2 : encoded_hash # Remove the macro name if it's there
31
+ unencoded = URI.unescape(encoded_hash).split('!')
32
+ unencoded.shift unless unencoded.first.include?(':')
33
+ unencoded = unencoded.map { |pair| key, value = pair.split(':'); [key.to_sym ,value] }.flatten
34
+ returning Hash[*unencoded] do |hash|
35
+ hash[:timeout] &&= hash[:timeout].to_i
36
+ hash[:play] &&= hash[:play].split('++')
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ attr_reader :call
43
+ def initialize(call)
44
+ @call = call
45
+ extend Adhearsion::VoIP::Commands.for(call.originating_voip_platform)
46
+ end
47
+
48
+ def handle
49
+ variables = self.class.decode_hash call.variables[:network_script]
50
+
51
+ answer
52
+ loop do
53
+ response = interruptable_play(*variables[:play])
54
+ if response && response.to_s == variables[:key].to_s
55
+ # Don't set a variable to pass through to dial()
56
+ break
57
+ elsif response && response.to_s != variables[:key].to_s
58
+ next
59
+ else
60
+ response = wait_for_digit variables[:timeout]
61
+ if response
62
+ if response.to_s == variables[:key].to_s
63
+ # Don't set a variable to pass through to dial()
64
+ break
65
+ else
66
+ next
67
+ end
68
+ else
69
+ # By setting MACRO_RESULT to CONTINUE, we cancel the dial.
70
+ variable 'MACRO_RESULT' => "CONTINUE"
71
+ break
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ module Asterisk
4
+ module Manager
5
+
6
+ ##
7
+ # Higher level abstraction of the Asterisk Manager Interface.
8
+ #
9
+ class SuperManager
10
+
11
+ def initialize
12
+ raise NotImplementedError
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,440 @@
1
+ require 'uri'
2
+ #TODO Some of this is asterisk-specific
3
+ module Adhearsion
4
+ class << self
5
+ def active_calls
6
+ @calls ||= Calls.new
7
+ end
8
+
9
+ def receive_call_from(io, &block)
10
+ active_calls << (call = Call.receive_from(io, &block))
11
+ call
12
+ end
13
+
14
+ def remove_inactive_call(call)
15
+ active_calls.remove_inactive_call(call)
16
+ end
17
+ end
18
+
19
+ ##
20
+ # This manages the list of calls the Adhearsion service receives
21
+ class Calls
22
+ def initialize
23
+ @semaphore = Monitor.new
24
+ @calls = {}
25
+ end
26
+
27
+ def <<(call)
28
+ atomically do
29
+ calls[call.unique_identifier] = call
30
+ end
31
+ end
32
+
33
+ def any?
34
+ atomically do
35
+ !calls.empty?
36
+ end
37
+ end
38
+
39
+ def size
40
+ atomically do
41
+ calls.size
42
+ end
43
+ end
44
+
45
+ def remove_inactive_call(call)
46
+ atomically do
47
+ calls.delete call.unique_identifier
48
+ end
49
+ end
50
+
51
+ # Searches all active calls by their unique_identifier. See Call#unique_identifier.
52
+ def find(id)
53
+ atomically do
54
+ return calls[id]
55
+ end
56
+ end
57
+
58
+ def clear!
59
+ atomically do
60
+ calls.clear
61
+ end
62
+ end
63
+
64
+ def with_tag(tag)
65
+ atomically do
66
+ calls.inject(Array.new) do |calls_with_tag,(key,call)|
67
+ call.tagged_with?(tag) ? calls_with_tag << call : calls_with_tag
68
+ end
69
+ end
70
+ end
71
+
72
+ def to_a
73
+ calls.values
74
+ end
75
+
76
+ private
77
+ attr_reader :semaphore, :calls
78
+
79
+ def atomically(&block)
80
+ semaphore.synchronize(&block)
81
+ end
82
+
83
+ end
84
+
85
+ class UselessCallException < Exception; end
86
+
87
+ class MetaAgiCallException < Exception
88
+ attr_reader :call
89
+ def initialize(call)
90
+ super()
91
+ @call = call
92
+ end
93
+ end
94
+
95
+ class FailedExtensionCallException < MetaAgiCallException; end
96
+
97
+ class HungupExtensionCallException < MetaAgiCallException; end
98
+
99
+ ##
100
+ # Encapsulates call-related data and behavior.
101
+ # For example, variables passed in on call initiation are
102
+ # accessible here as attributes
103
+ class Call
104
+
105
+ # This is basically a translation of ast_channel_reason2str() from main/channel.c and
106
+ # ast_control_frame_type in include/asterisk/frame.h in the Asterisk source code. When
107
+ # Asterisk jumps to the 'failed' extension, it sets a REASON channel variable to a number.
108
+ # The indexes of these symbols represent the possible numbers REASON could be.
109
+ ASTERISK_FRAME_STATES = [
110
+ :failure, # "Call Failure (not BUSY, and not NO_ANSWER, maybe Circuit busy or down?)"
111
+ :hangup, # Other end has hungup
112
+ :ring, # Local ring
113
+ :ringing, # Remote end is ringing
114
+ :answer, # Remote end has answered
115
+ :busy, # Remote end is busy
116
+ :takeoffhook, # Make it go off hook
117
+ :offhook, # Line is off hook
118
+ :congestion, # Congestion (circuits busy)
119
+ :flash, # Flash hook
120
+ :wink, # Wink
121
+ :option, # Set a low-level option
122
+ :radio_key, # Key Radio
123
+ :radio_unkey, # Un-Key Radio
124
+ :progress, # Indicate PROGRESS
125
+ :proceeding, # Indicate CALL PROCEEDING
126
+ :hold, # Indicate call is placed on hold
127
+ :unhold, # Indicate call is left from hold
128
+ :vidupdate # Indicate video frame update
129
+ ]
130
+
131
+
132
+ class << self
133
+ ##
134
+ # The primary public interface for creating a Call instance.
135
+ # Given an IO (probably a socket accepted from an Asterisk service),
136
+ # creates a Call instance which encapsulates everything we know about that call.
137
+ def receive_from(io, &block)
138
+ returning new(io, variable_parser_for(io).variables) do |call|
139
+ block.call(call) if block
140
+ end
141
+ end
142
+
143
+ private
144
+ def variable_parser_for(io)
145
+ Variables::Parser.parse(io)
146
+ end
147
+
148
+ end
149
+
150
+ attr_accessor :io, :type, :variables, :originating_voip_platform, :inbox
151
+ def initialize(io, variables)
152
+ @io, @variables = io, variables.symbolize_keys
153
+ check_if_valid_call
154
+ define_variable_accessors
155
+ set_originating_voip_platform!
156
+ @tag_mutex = Mutex.new
157
+ @tags = []
158
+ end
159
+
160
+ def tags
161
+ @tag_mutex.synchronize do
162
+ return @tags.clone
163
+ end
164
+ end
165
+
166
+ def tag(symbol)
167
+ raise ArgumentError, "tag must be a Symbol" unless symbol.is_a? Symbol
168
+ @tag_mutex.synchronize do
169
+ @tags << symbol
170
+ end
171
+ end
172
+
173
+ def remove_tag(symbol)
174
+ @tag_mutex.synchronize do
175
+ @tags.reject! { |tag| tag == symbol }
176
+ end
177
+ end
178
+
179
+ def tagged_with?(symbol)
180
+ @tag_mutex.synchronize do
181
+ @tags.include? symbol
182
+ end
183
+ end
184
+
185
+ def deliver_message(message)
186
+ inbox << message
187
+ end
188
+ alias << deliver_message
189
+
190
+ def inbox
191
+ @inbox ||= Queue.new
192
+ end
193
+
194
+ def hangup!
195
+ io.close
196
+ Adhearsion.remove_inactive_call self
197
+ end
198
+
199
+ def closed?
200
+ io.closed?
201
+ end
202
+
203
+ # Asterisk sometimes uses the "failed" extension to indicate a failed dial attempt.
204
+ # Since it may be important to handle these, this flag helps the dialplan Manager
205
+ # figure that out.
206
+ def failed_call?
207
+ @failed_call
208
+ end
209
+
210
+ def hungup_call?
211
+ @hungup_call
212
+ end
213
+
214
+ # Adhearsion indexes calls by this identifier so they may later be found and manipulated. For calls from Asterisk, this
215
+ # method uses the following properties for uniqueness, falling back to the next if one is for some reason unavailable:
216
+ #
217
+ # Asterisk channel ID -> unique ID -> Call#object_id
218
+ # (e.g. SIP/mytrunk-jb12c88a) -> (e.g. 1215039989.47033) -> (e.g. 2792080)
219
+ #
220
+ # Note: channel is used over unique ID because channel may be used to bridge two channels together.
221
+ def unique_identifier
222
+ case originating_voip_platform
223
+ when :asterisk
224
+ variables[:channel] || variables[:uniqueid] || object_id
225
+ else
226
+ raise NotImplementedError
227
+ end
228
+ end
229
+
230
+ def define_variable_accessors(recipient=self)
231
+ variables.each do |key, value|
232
+ define_singleton_accessor_with_pair(key, value, recipient)
233
+ end
234
+ end
235
+
236
+ def extract_failed_reason_from(environment)
237
+ if originating_voip_platform == :asterisk
238
+ failed_reason = environment.variable 'REASON'
239
+ failed_reason &&= ASTERISK_FRAME_STATES[failed_reason.to_i]
240
+ define_singleton_accessor_with_pair(:failed_reason, failed_reason, environment)
241
+ end
242
+ end
243
+
244
+ private
245
+
246
+ def define_singleton_accessor_with_pair(key, value, recipient=self)
247
+ recipient.metaclass.send :attr_accessor, key unless recipient.class.respond_to?("#{key}=")
248
+ recipient.send "#{key}=", value
249
+ end
250
+
251
+ def check_if_valid_call
252
+ extension = variables[:extension]
253
+ @failed_call = true if extension == 'failed'
254
+ @hungup_call = true if extension == 'h'
255
+ raise UselessCallException if extension == 't' # TODO: Move this whole method to Manager
256
+ end
257
+
258
+ def set_originating_voip_platform!
259
+ # TODO: we can make this determination programatically at some point,
260
+ # but it will probably involve a bit more engineering than just a case statement (like
261
+ # subclasses of Call for the various platforms), so we'll be totally cheap for now.
262
+ self.originating_voip_platform = :asterisk
263
+ end
264
+
265
+ module Variables
266
+
267
+ module Coercions
268
+
269
+ COERCION_ORDER = %w{
270
+ remove_agi_prefixes_from_keys_and_strip_whitespace
271
+ coerce_keys_into_symbols
272
+ coerce_extension_into_phone_number_object
273
+ coerce_numerical_values_to_numerics
274
+ replace_unknown_values_with_nil
275
+ replace_yes_no_answers_with_booleans
276
+ coerce_request_into_uri_object
277
+ decompose_uri_query_into_hash
278
+ override_variables_with_query_params
279
+ remove_dashes_from_context_name
280
+ coerce_type_of_number_into_symbol
281
+ }
282
+
283
+ class << self
284
+
285
+ def remove_agi_prefixes_from_keys_and_strip_whitespace(variables)
286
+ variables.inject({}) do |new_variables,(key,value)|
287
+ returning new_variables do
288
+ stripped_name = key.kind_of?(String) ? key[/^(agi_)?(.+)$/,2] : key
289
+ new_variables[stripped_name] = value.kind_of?(String) ? value.strip : value
290
+ end
291
+ end
292
+ end
293
+
294
+ def coerce_keys_into_symbols(variables)
295
+ variables.inject({}) do |new_variables,(key,value)|
296
+ returning new_variables do
297
+ new_variables[key.to_sym] = value
298
+ end
299
+ end
300
+ end
301
+
302
+ def coerce_extension_into_phone_number_object(variables)
303
+ returning variables do
304
+ variables[:extension] = Adhearsion::VoIP::DSL::PhoneNumber.new(variables[:extension])
305
+ end
306
+ end
307
+
308
+ def coerce_numerical_values_to_numerics(variables)
309
+ variables.inject({}) do |vars,(key,value)|
310
+ returning vars do
311
+ is_numeric = value =~ /^-?\d+(?:(\.)\d+)?$/
312
+ is_float = $1
313
+ vars[key] = if is_numeric
314
+ if Adhearsion::VoIP::DSL::NumericalString.starts_with_leading_zero?(value)
315
+ Adhearsion::VoIP::DSL::NumericalString.new(value)
316
+ else
317
+ is_float ? value.to_f : value.to_i
318
+ end
319
+ else
320
+ value
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ def replace_unknown_values_with_nil(variables)
327
+ variables.each do |key,value|
328
+ variables[key] = nil if value == 'unknown'
329
+ end
330
+ end
331
+
332
+ def replace_yes_no_answers_with_booleans(variables)
333
+ variables.each do |key,value|
334
+ case value
335
+ when 'yes' : variables[key] = true
336
+ when 'no' : variables[key] = false
337
+ end
338
+ end
339
+ end
340
+
341
+ def coerce_request_into_uri_object(variables)
342
+ returning variables do
343
+ variables[:request] = URI.parse(variables[:request]) unless variables[:request].kind_of? URI
344
+ end
345
+ end
346
+
347
+ def coerce_type_of_number_into_symbol(variables)
348
+ returning variables do
349
+ variables[:type_of_calling_number] = Adhearsion::VoIP::Constants::Q931_TYPE_OF_NUMBER[variables.delete(:callington).to_i]
350
+ end
351
+ end
352
+
353
+ def decompose_uri_query_into_hash(variables)
354
+ returning variables do
355
+ if variables[:request].query
356
+ variables[:query] = variables[:request].query.split('&').inject({}) do |query_string_parameters, key_value_pair|
357
+ parameter_name, parameter_value = *key_value_pair.match(/(.+)=(.*)/).captures
358
+ query_string_parameters[parameter_name] = parameter_value
359
+ query_string_parameters
360
+ end
361
+ else
362
+ variables[:query] = {}
363
+ end
364
+ end
365
+ end
366
+
367
+ def override_variables_with_query_params(variables)
368
+ returning variables do
369
+ if variables[:query]
370
+ variables[:query].each do |key, value|
371
+ variables[key.to_sym] = value
372
+ end
373
+ end
374
+ end
375
+ end
376
+
377
+ def remove_dashes_from_context_name(variables)
378
+ returning variables do
379
+ variables[:context].gsub!('-', '_')
380
+ end
381
+ end
382
+
383
+ end
384
+ end
385
+
386
+ class Parser
387
+
388
+ class << self
389
+ def parse(*args, &block)
390
+ returning new(*args, &block) do |parser|
391
+ parser.parse
392
+ end
393
+ end
394
+
395
+ def coerce_variables(variables)
396
+ Coercions::COERCION_ORDER.inject(variables) do |tmp_variables, coercing_method_name|
397
+ Coercions.send(coercing_method_name, tmp_variables)
398
+ end
399
+ end
400
+
401
+ def separate_line_into_key_value_pair(line)
402
+ line.match(/^([^:]+):\s?(.+)/).captures
403
+ end
404
+ end
405
+
406
+ attr_reader :io, :variables, :lines
407
+ def initialize(io)
408
+ @io = io
409
+ @lines = []
410
+ end
411
+
412
+ def parse
413
+ extract_variable_lines_from_io
414
+ initialize_variables_as_hash_from_lines
415
+ @variables = self.class.coerce_variables(variables)
416
+ end
417
+
418
+ private
419
+
420
+ def initialize_variables_as_hash_from_lines
421
+ @variables = lines.inject({}) do |new_variables,line|
422
+ returning new_variables do
423
+ key, value = self.class.separate_line_into_key_value_pair line
424
+ new_variables[key] = value
425
+ end
426
+ end
427
+ end
428
+
429
+ def extract_variable_lines_from_io
430
+ while line = io.readline.chomp
431
+ break if line.empty?
432
+ @lines << line
433
+ end
434
+ end
435
+
436
+ end
437
+
438
+ end
439
+ end
440
+ end