adhearsion 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/LICENSE +339 -0
  2. data/Rakefile +108 -0
  3. data/ahn +195 -0
  4. data/lib/adhearsion.rb +402 -0
  5. data/lib/constants.rb +20 -0
  6. data/lib/core_extensions.rb +157 -0
  7. data/lib/database_functions.rb +76 -0
  8. data/lib/rami.rb +822 -0
  9. data/lib/servlet_container.rb +146 -0
  10. data/new_projects/Rakefile +100 -0
  11. data/new_projects/config/adhearsion.sqlite3 +0 -0
  12. data/new_projects/config/adhearsion.yml +11 -0
  13. data/new_projects/config/database.rb +50 -0
  14. data/new_projects/config/database.yml +10 -0
  15. data/new_projects/config/helpers/drb_server.yml +43 -0
  16. data/new_projects/config/helpers/factorial.alien.c.yml +1 -0
  17. data/new_projects/config/helpers/manager_proxy.yml +7 -0
  18. data/new_projects/config/helpers/micromenus.yml +1 -0
  19. data/new_projects/config/helpers/micromenus/collab.rb +55 -0
  20. data/new_projects/config/helpers/micromenus/images/tux.bmp +0 -0
  21. data/new_projects/config/helpers/micromenus/javascripts/builder.js +131 -0
  22. data/new_projects/config/helpers/micromenus/javascripts/controls.js +834 -0
  23. data/new_projects/config/helpers/micromenus/javascripts/dragdrop.js +944 -0
  24. data/new_projects/config/helpers/micromenus/javascripts/effects.js +956 -0
  25. data/new_projects/config/helpers/micromenus/javascripts/prototype.js +2319 -0
  26. data/new_projects/config/helpers/micromenus/javascripts/scriptaculous.js +51 -0
  27. data/new_projects/config/helpers/micromenus/javascripts/slider.js +278 -0
  28. data/new_projects/config/helpers/micromenus/javascripts/unittest.js +557 -0
  29. data/new_projects/config/helpers/micromenus/stylesheets/firefox.css +10 -0
  30. data/new_projects/config/helpers/micromenus/stylesheets/firefox.xul.css +44 -0
  31. data/new_projects/config/helpers/weather.yml +1 -0
  32. data/new_projects/config/helpers/xbmc.yml +1 -0
  33. data/new_projects/config/migration.rb +53 -0
  34. data/new_projects/extensions.rb +56 -0
  35. data/new_projects/helpers/drb_server.rb +32 -0
  36. data/new_projects/helpers/factorial.alien.c +32 -0
  37. data/new_projects/helpers/manager_proxy.rb +43 -0
  38. data/new_projects/helpers/micromenus.rb +374 -0
  39. data/new_projects/helpers/oscar_wilde_quotes.rb +197 -0
  40. data/new_projects/helpers/weather.rb +85 -0
  41. data/new_projects/helpers/xbmc.rb +12 -0
  42. data/new_projects/logs/database.log +0 -0
  43. data/test/core_extensions_test.rb +26 -0
  44. data/test/dial_test.rb +43 -0
  45. data/test/stress_tests/test.rb +13 -0
  46. data/test/stress_tests/test.yml +13 -0
  47. data/test/test_micromenus.rb +0 -0
  48. metadata +131 -0
@@ -0,0 +1,402 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program 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
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'core_extensions'
19
+
20
+ module Asterisk
21
+
22
+ # The exec method allows any traditional Asterisk applications to be called
23
+ # within Adhearsion. The first argument should be the case-insensitive name
24
+ # of the application and any arguments needed by the application can simply
25
+ # by trailed onto the end of the method. For a full list of Asterisk's
26
+ # applications, see "this":http://www.voip-info.org/wiki/index.php?page=Asterisk+-+documentation+of+application+commands
27
+ def exec(app, *options)
28
+ result = rawr "EXEC #{app} " + (options * '|')
29
+ result = result[/-?\d+$/]
30
+ result == "-2" ? false : result
31
+ end
32
+
33
+ # This method of receiving user input uses the fantastic Asterisk
34
+ # Read() application, but its results are pretty inconsitent over
35
+ # AGI. This code has been kept here in case these bugs are fixed.
36
+ def old_input digits=nil, user_options=Hash.new('')
37
+ used_options = {:variable => String.random, :digits => digits}
38
+ used_options.default = ''
39
+ used_options.merge! user_options
40
+ args = []
41
+ %w(variable soundfile digits option attempts timeout).each do |x|
42
+ args << used_options[x.to_sym]
43
+ end
44
+ log "THESE ARE THE INPUT ARGS: " + args.inspect
45
+ exec :read, args
46
+ $OSCAR[:VARS] << args.first
47
+ x = get_variable(args.first).gsub(/[\(\)]/, "")
48
+ puts "RETURN: #{x.inspect}"
49
+ x.simplify
50
+ end
51
+
52
+ # Input is used to receive keypad input from the user, pausing until they
53
+ # have entered the desired number of digits (specified with the first
54
+ # parameter) or the timeout has been reached (specified as a hash argument
55
+ # with the key :timeout). By default, there is no timeout, waiting infinitely.
56
+ #
57
+ # If you desire a sound to be played other than a simple beep to instruct
58
+ # the callee to input data, pass the filename as an hash argument with either
59
+ # the :play or :file key.
60
+ #
61
+ # When called without any arguments (or a first argument of -1), the user is
62
+ # able to enter digits ad infinitum until they press the pound (#) key.
63
+ def input digits=nil, hash={}
64
+ timeout, file = (hash[:timeout] || -1), (hash[:play] || hash[:file] || 'beep')
65
+ result = rawr "GET DATA #{file} #{timeout} #{digits}"
66
+ result = result[/\=-?\d+/]
67
+ result ? result[1..-1] : false
68
+ end
69
+
70
+ # Does as you'd imagine: returns the amount of time the given block takes to
71
+ # execute. This is particularly useful in VoIP since billing and such often
72
+ # requires time keeping beyond the Call Detail Records. This method is also
73
+ # aliased to bill() as well.
74
+ def time
75
+ return 0 unless block_given?
76
+ start = Time.now
77
+ yield start
78
+ Time.now - start
79
+ end
80
+ alias bill time
81
+
82
+ # Requires an AMI connection! Given a block, everything contained
83
+ # within it will be recorded. If no arguments are given, a String
84
+ # for the filename will be generated from the current time and a
85
+ # sequence of random alphanumeric characters.
86
+ def record hash, &block
87
+ defaults = {:file => "#{String.random(5)}", :folder => nil,
88
+ :channel => Thread.current[:VARS]['channel'], :format => 'wav', :mix => '1'}
89
+ defaults = defaults.merge hash
90
+ #PBX.record
91
+ end
92
+
93
+ # Waits for single digit numpad key response from the user. If no timeout argument
94
+ # is given, the system will wait indefinitely. The argument is the desired time
95
+ # to wait fo0r the response in seconds. Feel free to use the ActiveSupport extensions
96
+ # for using this method like wait_for_digit(2.minutes) or something similar. When the
97
+ # timeout is encountered (or an error occurs receiving the input), the method
98
+ # returns nil. If the asterisk or pound key was pressed, a String is returned of that
99
+ # response. In all other cases, the Fixnum of the number pressed is returned.
100
+ def wait_for_digit timeout=-1
101
+ digit = rawr("WAIT FOR DIGIT #{timeout < 0 ? -1 : timeout / 1000.0}").match(/=(.*)$/)[1].to_i
102
+ return nil if digit <= 0 # If there was an error or timeout
103
+ return digit.chr if [32,45].include? digit
104
+ digit - ?0
105
+ end
106
+
107
+ # An abstracted remote mutator for setting Asterisk variables.
108
+ def set_variable(key,value) rawr "SET VARIABLE #{key} #{value}" end
109
+
110
+ # An abstracted remote accessor for retrieving Asterisk variables.
111
+ def get_variable(key, default=nil)
112
+ result = rawr "GET VARIABLE #{key}"
113
+ result[0..12] == "200 result=0" ? default : result[13..-1]
114
+ end
115
+
116
+ def dial who, *options
117
+ #group = who.group if who.respond_to :group
118
+ exec :dial, properize(who)
119
+ end
120
+
121
+ # The stream_file() method comes right over from the AGI protocol.
122
+ # Its use is very similar to the Background() application from
123
+ # Asterisk's extensions.conf language. A file is played in the
124
+ # background while optionally waiting for input. In the event
125
+ # the second argument is given, Asterisk will listen for that
126
+ # sequence of digits and return control to Adhearsion, informing
127
+ # us of the digits the user pressed. These digits are returned
128
+ # as a String. If you would also like to know the ending position
129
+ # at which the streaming stopped, see stream_file_with_offset().
130
+ def stream_file file, digits='', offset=0
131
+ stream_file_with_offset(file, digits, offset).first
132
+ end
133
+
134
+ # Similar to stream_file(), but returning an array of length 2
135
+ # instead. If supplied a first argument, the return value will
136
+ # be digits pressed by the user to end
137
+ def stream_file_with_offset file, digits='', offset=0
138
+ response = rawr "STREAM FILE #{file} #{digits} #{offset}"
139
+ return nil unless response.starts_with? '200'
140
+
141
+ result_index = response.index(?=) + 1
142
+ spacer_index = response.rindex(' ')
143
+ endpos_index = spacer_index + 8 # " endpos=".length == 8
144
+
145
+ result = response[result_index...spacer_index].to_i
146
+ endpos = response[endpos_index..-1].to_i
147
+
148
+ return [nil,nil] if (result.zero? && endpos.zero?) || result == -1
149
+
150
+ [result, endpos]
151
+ end
152
+
153
+ # Festival is pretty buggy (and pretty inefficient). In theory,
154
+ # this should speak out any text supplied to it.
155
+ def speak text
156
+ text.gsub!('\n').gsub!('\r').strip!
157
+ exec :festival, "'#{text}'"
158
+ end
159
+
160
+ # Since voicemail is used so frequently, extra effort has been made to
161
+ # make it syntactically sweet. voicemail() takes the mailbox number of
162
+ # a user as its first or second argument and the mailbox type as its
163
+ # first or second argument (the order doesn't matter). If a specific
164
+ # voicemail context is necessary, trail it to the end of the method
165
+ # with the ":at => 'contextname'" syntax. The mailbox type can be
166
+ # :busy, :unavailable, or :normal, though if no type is given, the
167
+ # type :normal is assumed.
168
+ #
169
+ # Usage: voicemail :busy, 4544
170
+ # or: voicemail 4544
171
+ # or: voicemail 4544, :unavailable, :at => 'codemecca'
172
+ def voicemail msg_or_who=nil, who_or_msg=nil, hash={}
173
+ opts = [msg_or_who, who_or_msg]
174
+ type = opts & [:normal, :busy, :unavailable]
175
+ box = opts - type
176
+ box.last <<= "@#{hash[:at]}}" if hash[:at]
177
+ exec :Voicemail, *(type + box)
178
+ end
179
+
180
+ # Execute VoiceMailMain behind the scenes to check a specified
181
+ # user's voicemail inbox or, if no arguments are passed, prompt
182
+ # the user for their mailbox number.
183
+ #
184
+ # Usage: check_voicemail 'jay', :at => 'default', :args => 's'
185
+ # or : check_voicemail
186
+ # or : check_voicemail 'hubbard'
187
+ def check_voicemail name=nil, hash={}
188
+ args = [ [name, hash[:at]].compact * '@', hash[:args]]
189
+ exec :VoiceMailMain, args.compact
190
+ end
191
+
192
+ # Play is a long-overdue easy way to play audio files in a dialplan with Asterisk. It can take a
193
+ # single String of the filename (defaults to /var/lib/asterisk/sounds) just as you
194
+ # would give it to Playback(), or it can take any number of trailed on Strings.
195
+ # If you pass it an Array, it will traverse the array, playing each of the items
196
+ # in order. If you're doing this, make your life *incredibly* easier by using the
197
+ # fantastic Ruby Array literal for Arrays of Strings: %w(). Within this, one
198
+ # specifies separate, simple Strings by placing whitespace bewtween them. Since
199
+ # no Asterisk files contain whitespace, this works _very_ well! Example:
200
+ #
201
+ # play %w(a-connect-charge-of 22 cents-per-minute will-apply)
202
+ #
203
+ # Note here that numbers can be play()ed as well. By convention, play() will call
204
+ # SayNumber() on these indices instead of Playback().
205
+ def play *files
206
+ files.flatten!
207
+ files.each do |f|
208
+ if f.simplify.is_a? Fixnum then exec :saynumber, f
209
+ else exec :playback, f
210
+ end
211
+ end
212
+ end
213
+
214
+ # The rawr() method is the main way of receiving a raw response from the Asterisk
215
+ # server. When no argument is given, it will immediately ask for a response,
216
+ # returning that String. When an argument +is+ given, it will first send that
217
+ # command and then return the response that command generated. Everything is
218
+ # chomp()ed before returned. Pun here totally intended.
219
+ def rawr(what=nil)
220
+ begin
221
+ putc what if what
222
+ PBX.io.gets.chomp!
223
+ rescue => e
224
+ #log "Socket no longer available for communication. Exceptions will likely occur."
225
+ end
226
+ end
227
+
228
+ # Simply print()s the command over the AGI IO socket.
229
+ def putc(what) PBX.io.print what end
230
+
231
+ # The magical method that handles how objects passed to dial() are converted to their
232
+ # corresponding Asterisk-recognizable technology/extension identifier. Likely wouldn't
233
+ # be used much outside of dial().
234
+ def properize who
235
+ possible_methods = [:users, :members, :user, :member].select { |pm| who.respond_to? pm } # These are the convention methods for Group-like objects
236
+ who = who.send possible_methods.first if possible_methods.any? # If 'who' has any of the possible_methods, replace 'who' with the return value of that method
237
+ return unless who
238
+ who = [who] unless who.kind_of?(Enumerable) && !who.kind_of?(String) # In case we can't perform collection algorithms on 'who', let's encapsulate it in an Array
239
+ # If the first thing in the Enumerable responds to a User-like convention, then set 'who' equal to the extension accessor of those objects
240
+ who.map! { |p| p.send possible_methods.first }.compact! if (possible_methods = [:extension, :extensions].select { |pm| who.first.respond_to? pm }).any?
241
+ # Now replace each item in the Enumerable with its form converted to extension (assuming it's not already in that format)
242
+ who.map! do |ext|
243
+ if ext.kind_of?(String) && ext.index(?/) then ext
244
+ else
245
+ ext = "1#{ext}" if ext.is_national_number? && ext.to_s[0] != ?1
246
+ "SIP/#{ext}"
247
+ end
248
+ end
249
+ who *= '&' # Finally, join() anything left in the Array with an '&'
250
+ end
251
+
252
+ # Returns the status of the last dial(). Possible dial
253
+ # statuses include :answer, :busy, :noanswer, :cancel,
254
+ # :congestion, and :chanunavail. If :cancel is
255
+ # returned, the caller hung up before the callee picked
256
+ # up. If :congestion is returned, the dialed extension
257
+ # probably doesn't exist. If :chanunavail, the callee
258
+ # phone may not be registered.
259
+ def last_dial_status
260
+ get_variable(:DIALSTATUS).downcase.to_sym
261
+ end
262
+
263
+ # Answer the channel. Adhearsion is configured by default to automatically do this
264
+ # when a call comes in.
265
+ def answer() rawr 'ANSWER' end
266
+ # Hangs up the channel. Adhearsion is configured by default to matically do this
267
+ # when a context completes execution
268
+ def hangup() rawr 'HANGUP' end
269
+ # Direct translation of the Asterisk NoOp() application. Used primarily for
270
+ # viewing debug information in the Asterisk CLI.
271
+ def noop(*options) rawr "NOOP #{options}" end
272
+
273
+ # The SIP/ZAP/IAX/IAX2/Zap methods allow for cleaner addressing of particular
274
+ # extensions, abstracting Asterisk's representation. If not given any
275
+ # argument, this methods simply return a symbol identifying their
276
+ # appropriate protocol.
277
+
278
+ # See the SIP class
279
+ def SIP(ext=nil) ext ? "SIP/#{ext}" : :SIP end
280
+ # See the ZAP class
281
+ def ZAP(ext=nil) ext ? "Zap/#{ext}" : :ZAP end
282
+ # See the IAX class
283
+ def IAX(ext=nil) ext ? "IAX2/#{ext}" : :IAX end
284
+ alias IAX2 IAX
285
+ alias Zap ZAP
286
+
287
+ # For the completely selfish purpose of sugaring the syntax, two tiny holder classes
288
+ # are created to allow the synonym of SIP(1404) to be SIP[1404]. Both functions,
289
+ # when given an argument, return a String that Asterisk understands representing that
290
+ # particular channel.
291
+
292
+ # Syntax sugar for declaring SIP devices. Use: SIP/123 or SIP[123]
293
+ class SIP
294
+ def self.[](arg=nil) SIP(arg) end
295
+ def self./(arg=nil) SIP(arg) end
296
+ end
297
+ # Syntax sugar for declaring IAX devices. Use: IAX/123 or IAX[123]
298
+ class IAX
299
+ def self.[](arg=nil) IAX(arg) end
300
+ def self./(arg=nil) IAX(arg) end
301
+ end
302
+
303
+ # Syntax sugar for declaring ZAP devices. Use: ZAP/123 or ZAP[123]
304
+ class ZAP
305
+ def self.[](arg=nil) ZAP(arg) end
306
+ def self./(arg=nil) ZAP(arg) end
307
+ end
308
+
309
+ # For users who may try to use the (proper) IAX2 form. See IAX for more info.
310
+ class IAX2 < IAX; end
311
+
312
+ # For users who may try to use the (common) "Zap" form. See ZAP for more info.
313
+ class Zap < ZAP; end
314
+ end
315
+
316
+ # The PBX object is an object manifestation of the PBX with which Adhearsion will be
317
+ # associated. Helpers often open up this class and add methods to it. Note: when doing
318
+ # this in your own helpers, all methods will need to be class (a.k.a. static) methods
319
+ # since no actual instance of this object is passed around.
320
+ class PBX
321
+ def PBX.io() Thread.current[:io] end
322
+ end
323
+
324
+ # The Contexts class is a blank slate in which the extensions.rb file is evaluated.
325
+ # Its method_missing() simply takes a block and, given the name of attempted method,
326
+ # meta_def()s a new accessor method in Contexts with this name that returns the block
327
+ # given. This is how contexts can be included anywhere in extensions.rb using the
328
+ # easy "+context_name" syntax.
329
+ class Contexts
330
+
331
+ (instance_methods - %w(__send__ __id__ define_method instance_eval)).each do |m|
332
+ undef_method m
333
+ end
334
+
335
+ def method_missing name, *args, &block
336
+ super(name, *args, &block) unless block
337
+ Thread.current[:container].run_inside do
338
+ meta_def name do
339
+ block
340
+ end
341
+ end
342
+ end
343
+
344
+ # This Container object is a container in which each context is executed (not instantiated).
345
+ # It extends the Asterisk module from adhearsion.rb and thus inherits all of the functionality
346
+ # declared there. This class can be monkey patched if necessary.
347
+ class Container
348
+ include Asterisk
349
+ def initialize
350
+ class << self
351
+ def metaclass; class << self; self; end; end
352
+ def meta_eval &blk; metaclass.instance_eval(&blk); end
353
+ def meta_def name, &blk
354
+ meta_eval { define_method name, &blk }
355
+ end
356
+ end
357
+ end
358
+
359
+ def method_missing name, *args, &block
360
+ Kernel.method_missing name, *args, &block
361
+ end
362
+
363
+ def run_inside &code
364
+ instance_eval(&code)
365
+ end
366
+
367
+ def eval_inside code
368
+ run_inside do
369
+ eval code
370
+ end
371
+ end
372
+ end
373
+ end
374
+
375
+ # An Exception thrown when the directory supplied in the constructor of a
376
+ # new RailsApp object is invalid.
377
+ class InvalidRailsDirectory < Exception;end
378
+
379
+ # When instantiated with an absolute location to a Rails app, the new RailsApp will perform
380
+ # a number of useful observations about the files and directories available, such as loading
381
+ # the database configuration and listing all of the models. All observations are made accessible
382
+ # through attribute accessors.
383
+ class RailsApp
384
+ def initialize path=Dir.pwd
385
+ update! path
386
+ end
387
+
388
+ # Performs the observations on the Rails app once more.
389
+ def update! path
390
+ @path = path
391
+ @database_config_file = File.join @path, 'config', 'database.yml'
392
+ if File.readable? @database_config_file
393
+ @database_config = YAML.load_file @database_config_file
394
+ else raise InvalidRailsDirectory.new("Database config file #{@database_config_file} not found!")
395
+ end
396
+ @models_folder = File.join path, 'app', 'models'
397
+ @models_files = Dir[ File.join(@models_folder, '*.rb') ]
398
+ @time_updated = Time.now
399
+ end
400
+ attr_reader :database_config_file, :database_config, :path, :models_folder, :models_files,
401
+ :models_names, :time_updated
402
+ end
@@ -0,0 +1,20 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program 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
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ # Please help adjust these if they may be inaccurate!
19
+ LOCAL_NUMBER = /^[1-9]\d{6}$/
20
+ US_NUMBER = /^1?[1-9]\d{2})?[1-9]\d{6}$/
@@ -0,0 +1,157 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program 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
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'active_support'
19
+
20
+ class Object
21
+
22
+ # For dealing with data that may be more difficult to manage than it should be.
23
+ # Strings with simplify() called on them return integers if they appear to be
24
+ # integers. Arrays with only one item in it return ary.first.simplify.
25
+ def simplify
26
+ if is_a? String
27
+ return Integer(self) if self =~ /^\d+$/
28
+ elsif is_a? Array
29
+ return first.simplify if size == 1
30
+ end
31
+ self
32
+ end
33
+ def is_a_group?
34
+ # TODO: check if it responds to the conventions.
35
+ end
36
+ def is_a_user?
37
+ # TODO: check if it responds to the conventions.
38
+ end
39
+
40
+ def is_local_number?
41
+ to_s =~ LOCAL_NUMBER
42
+ end
43
+
44
+ def is_national_number?
45
+ to_s =~ national_number
46
+ end
47
+
48
+ def mutex() @mutex ||= Mutex.new end
49
+ def synchronize() mutex.synchronize { yield self } end
50
+
51
+ private
52
+
53
+ $BEFORE_CALL, $BEFORE_CALL_HIGH, $BEFORE_CALL_LOW = [],[],[]
54
+ def before_call priority=:normal, &block
55
+ name = priority == :normal ? "BEFORE_CALL" : "BEFORE_CALL_#{priority.to_s.upcase}"
56
+ eval("$#{name}") << block
57
+ end
58
+
59
+ $AFTER_CALL, $AFTER_CALL_HIGH, $AFTER_CALL_LOW = [],[],[]
60
+ def after_call priority=:normal, &block
61
+ name = priority == :normal ? "AFTER_CALL" : "AFTER_CALL_#{priority.to_s.upcase}"
62
+ eval("$#{name}") << block
63
+ end
64
+ end
65
+
66
+ class String
67
+
68
+ def String.random_char
69
+ case r = rand(62)
70
+ when 0...10 then r.to_s
71
+ when 10...36 then (r+55).chr
72
+ when 36...62 then (r+61).chr
73
+ end
74
+ end
75
+
76
+ # Handy way to generate random strings of an arbitrary lengths.
77
+ def String.random len=8
78
+ str = ''
79
+ len.times do str << String.random_char end
80
+ str
81
+ end
82
+ def nameify() downcase.gsub(/[^\w]/, '') end
83
+ def nameify!() replace nameify end
84
+ end
85
+
86
+ class Proc
87
+ def +@
88
+ Thread.current[:container].run_inside(&self)
89
+ true # Allows "and" operator
90
+ end
91
+
92
+ def ~@
93
+ raise ControlPassingException.new(self)
94
+ end
95
+ end
96
+
97
+
98
+ # A ControlPassingException is used internally to stop execution of one
99
+ # dialplan context and begin execution of another proc instead. It is
100
+ # most notably used by the ~@ unary operator that can be called on a
101
+ # context name within a dialplan to transfer control entirely to that
102
+ # particular context. The serve() method in the servlet_container actually
103
+ # rescues these exceptions specifically and then does +e.target to execute
104
+ # that code.
105
+ class ControlPassingException < Exception
106
+ def initialize target
107
+ @target = target
108
+ super
109
+ end
110
+ attr_reader :target
111
+ end
112
+
113
+ #class Regexp
114
+ # alias replaced_triple_equals ===
115
+ # # Allows regular expressions to be compared against numbers in case statements.
116
+ # def === other
117
+ # # This feature is so valuable to the dialplan DSL that a little bit
118
+ # # of hackery is justified.
119
+ #
120
+ # if caller.find {|i| i.ends_with? ":in `run_inside'" } # Test if running in the DSL
121
+ # return replaced_triple_equals(other.to_s) if other.kind_of? Numeric
122
+ # end
123
+ # replaced_triple_equals other
124
+ # end
125
+ #end
126
+
127
+ class Fixnum
128
+
129
+ # One ring is approximately six seconds.
130
+ def rings() self*6 end
131
+ alias second seconds
132
+ alias digit seconds
133
+ alias digits seconds
134
+ def =~ other
135
+ to_s =~ other
136
+ end
137
+
138
+ # Used by __case__ statements. For Adhearsion's purposes, passing
139
+ # a Fixnum to a case statement will convert both the Fixnum operand
140
+ # and other operand to Strings and then return their === equivalency.
141
+ def === other
142
+ to_s === other.to_s
143
+ end
144
+ end
145
+
146
+ class Hash
147
+ def method_missing name, *args
148
+ if name.to_s[-1] == ?= then self[name.to_s.chop.to_sym] = args.first
149
+ else self[name] || self[name.to_s]
150
+ end
151
+ end
152
+ end
153
+
154
+ class Array
155
+ def first=(value) self[0] = value end
156
+ def last=(value) self[-1] = value end
157
+ end