jicksta-adhearsion 0.7.999
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.
- data/CHANGELOG +6 -0
- data/EVENTS +11 -0
- data/LICENSE +456 -0
- data/README.txt +5 -0
- data/Rakefile +120 -0
- data/adhearsion.gemspec +146 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +87 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +23 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +53 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/component_manager.rb +208 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/tasks.rb +16 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +440 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +218 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/adhearsion.rb +37 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- data/lib/theatre.rb +151 -0
- metadata +177 -0
@@ -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
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
module VoIP
|
3
|
+
module CallRouting
|
4
|
+
class Rule
|
5
|
+
attr_reader :patterns, :providers, :options
|
6
|
+
|
7
|
+
def initialize(*args, &block)
|
8
|
+
@options = args.pop
|
9
|
+
@patterns = Array(args)
|
10
|
+
@providers = Array(options[:to])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RuleSet < Array
|
15
|
+
def [](index)
|
16
|
+
case index
|
17
|
+
when String
|
18
|
+
detect do |rule|
|
19
|
+
rule.patterns.any? do |pattern|
|
20
|
+
index =~ pattern
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Router
|
30
|
+
class << self
|
31
|
+
attr_accessor :rules
|
32
|
+
|
33
|
+
def define(&block)
|
34
|
+
returning new do |router|
|
35
|
+
router.define(&block)
|
36
|
+
rules.concat router.rules
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def calculate_route_for(end_point)
|
41
|
+
if rule = rules[end_point.to_s]
|
42
|
+
rule.providers
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
self.rules ||= RuleSet.new
|
48
|
+
|
49
|
+
attr_reader :rules
|
50
|
+
def initialize
|
51
|
+
@rules = []
|
52
|
+
end
|
53
|
+
|
54
|
+
def define(&block)
|
55
|
+
instance_eval(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def route(*args, &block)
|
59
|
+
rules << Rule.new(*args, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Adhearsion, open source collaboration framework
|
2
|
+
# Copyright (C) 2006,2007,2008 Jay Phillips
|
3
|
+
#
|
4
|
+
# This library is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
6
|
+
# License as published by the Free Software Foundation; either
|
7
|
+
# version 2.1 of the License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
module Adhearsion
|
19
|
+
module VoIP
|
20
|
+
|
21
|
+
# Please help adjust these if they may be inaccurate!
|
22
|
+
module Constants
|
23
|
+
US_LOCAL_NUMBER = /^[1-9]\d{6}$/
|
24
|
+
US_NATIONAL_NUMBER = /^1?[1-9]\d{2}[1-9]\d{6}$/
|
25
|
+
ISN = /^\d+\*\d+$/ # See http://freenum.org
|
26
|
+
SIP_URI = /^sip:[\w\._%+-]+@[\w\.-]+\.[a-zA-Z]{2,4}$/
|
27
|
+
|
28
|
+
# Type of Number definitions given over PRI. Taken from the Q.931-ITU spec, page 68.
|
29
|
+
Q931_TYPE_OF_NUMBER = Hash.new(:unknown).merge 0b001 => :international,
|
30
|
+
0b010 => :national,
|
31
|
+
0b011 => :network_specific,
|
32
|
+
0b100 => :subscriber,
|
33
|
+
0b110 => :abbreviated_number
|
34
|
+
end
|
35
|
+
|
36
|
+
include Constants
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
module VoIP
|
3
|
+
module Conveniences
|
4
|
+
|
5
|
+
# Compiles the provided Asterisk dialplan pattern into a Ruby regular
|
6
|
+
# expression. For more usage of Asterisk's pattern syntax, see
|
7
|
+
# http://www.voip-info.org/wiki/view/Asterisk+Dialplan+Patterns
|
8
|
+
def _(pattern)
|
9
|
+
# Uncomment the following code fragment for complete compatibility.
|
10
|
+
# The fragment handles the seldom-used hyphen number spacer with no
|
11
|
+
# meaning.
|
12
|
+
Regexp.new '^' << pattern.# gsub(/(?!\[[\w+-]+)-(?![\w-]+\])/,'').
|
13
|
+
gsub('X', '[0-9]').gsub('Z', '[1-9]').gsub('N','[2-9]').
|
14
|
+
gsub('.','.+').gsub('!','.*') << '$'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|