ragi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +12 -0
  2. data/RAGI Overview_files/filelist.xml +5 -0
  3. data/RAGI Overview_files/image001.gif +0 -0
  4. data/README +114 -0
  5. data/call_connection.rb +515 -0
  6. data/call_handler.rb +362 -0
  7. data/call_initiate.rb +188 -0
  8. data/call_server.rb +147 -0
  9. data/config.rb +68 -0
  10. data/example-call-audio.mp3 +0 -0
  11. data/ragi.gempsec +23 -0
  12. data/sample-apps/simon/simon_handler.rb +94 -0
  13. data/sample-apps/simon/sounds/README +9 -0
  14. data/sample-apps/simon/sounds/simon-1.gsm +0 -0
  15. data/sample-apps/simon/sounds/simon-2.gsm +0 -0
  16. data/sample-apps/simon/sounds/simon-3.gsm +0 -0
  17. data/sample-apps/simon/sounds/simon-4.gsm +0 -0
  18. data/sample-apps/simon/sounds/simon-5.gsm +0 -0
  19. data/sample-apps/simon/sounds/simon-6.gsm +0 -0
  20. data/sample-apps/simon/sounds/simon-7.gsm +0 -0
  21. data/sample-apps/simon/sounds/simon-8.gsm +0 -0
  22. data/sample-apps/simon/sounds/simon-9.gsm +0 -0
  23. data/sample-apps/simon/sounds/simon-again.gsm +0 -0
  24. data/sample-apps/simon/sounds/simon-beep-again.gsm +0 -0
  25. data/sample-apps/simon/sounds/simon-beep-beep.gsm +0 -0
  26. data/sample-apps/simon/sounds/simon-beep-gameover.gsm +0 -0
  27. data/sample-apps/simon/sounds/simon-beep-high.gsm +0 -0
  28. data/sample-apps/simon/sounds/simon-beep-low.gsm +0 -0
  29. data/sample-apps/simon/sounds/simon-beep-medium.gsm +0 -0
  30. data/sample-apps/simon/sounds/simon-beep-score.gsm +0 -0
  31. data/sample-apps/simon/sounds/simon-beep-welcome.gsm +0 -0
  32. data/sample-apps/simon/sounds/simon-beep.gsm +0 -0
  33. data/sample-apps/simon/sounds/simon-gameover.gsm +0 -0
  34. data/sample-apps/simon/sounds/simon-goodbye.gsm +0 -0
  35. data/sample-apps/simon/sounds/simon-high.gsm +0 -0
  36. data/sample-apps/simon/sounds/simon-low.gsm +0 -0
  37. data/sample-apps/simon/sounds/simon-medium.gsm +0 -0
  38. data/sample-apps/simon/sounds/simon-score.gsm +0 -0
  39. data/sample-apps/simon/sounds/simon-welcome.gsm +0 -0
  40. data/start_ragi.rb +41 -0
  41. data/test.rb +109 -0
  42. metadata +77 -0
@@ -0,0 +1,362 @@
1
+ #
2
+ # RAGI - Ruby classes for implementing an AGI server for Asterisk
3
+ # The BSD License for RAGI follows.
4
+ #
5
+ # Copyright (c) 2005, SNAPVINE LLC (www.snapvine.com)
6
+ # All rights reserved.
7
+
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # this list of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice,
14
+ # this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ # * Neither the name of SNAPVINE nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
24
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'uri'
32
+ require 'ragi/call_connection'
33
+
34
+ module RAGI
35
+
36
+ # == Overview
37
+ #
38
+ # This class is the base class for all RAGI call handler objects. To create
39
+ # a call handler, derive a class from this class with the suffix "Handler"
40
+ # and place it in the file app/handlers/<name>_handler.rb.
41
+ #
42
+ # == Actions
43
+ #
44
+ # All public
45
+ # methods on the class are by default exposed as actions to the callserver.
46
+ # An action can be called by passing a URL like
47
+ #
48
+ # agi://<server>/<handler>/<action>
49
+ #
50
+ # to asterisk. If the action is not specified, then the call server will
51
+ # choose one of the two following default actions:
52
+ #
53
+ # dialout - for connections initiated by the call server
54
+ # dialup - for connections initiated from outside
55
+ #
56
+ # == Helpers
57
+ #
58
+ # Helpers are modules that can be loaded as mixins into the call handlers.
59
+ # As mixins the implementation of these modules will have access to all the
60
+ # state of the call handler including the session, params, connection, etc.
61
+ #
62
+ # To include a helper, declare it with a line like this in your call handler:
63
+ #
64
+ # helper :FooBar
65
+ #
66
+ # The call handler will mixin a module named FooBarHelper, loading the file
67
+ # foo_bar_helper.rb if necessary. The standard location for such helper
68
+ # files is app/helper.
69
+ #
70
+ # == Parameters
71
+ #
72
+ # There are a variety of ways that parameters can be passed to a call
73
+ # handler. Each of these variables is available through the params hash.
74
+ # To retrieve a value from the params hash:
75
+ #
76
+ # @params['Foo']
77
+ #
78
+ # For a call coming in over RAGI you can retrieve AGI session variables.
79
+ # These are the variables passed to teh connection when it is established.
80
+ # For example, to reference the caller ID:
81
+ #
82
+ # @params[RAGI::CALLERID]
83
+ #
84
+ # You can also retrieve query parameters from the AGI URL. For instance, if
85
+ # the AGI URL is something like /foo/bar?widget=gizbot, you can retrieve the
86
+ # widget value:
87
+ #
88
+ # if @params[:widget] == 'gizbot' then
89
+ #
90
+ # If the call was initiated through RAGI::place_call, you can access the
91
+ # parameters passed to the initiate call. For instance, if you initiate call
92
+ # looks like this:
93
+ #
94
+ # RAGI.place_call('2065551212', '/foo/bar',
95
+ # :hash_params => { :param => 123,
96
+ # "foo" => "bar" })
97
+ #
98
+ # You can reference these parameters like this:
99
+ #
100
+ # if @params[:param] == 123 && @params["foo"] == "bar" then
101
+ #
102
+ # Finally, when you redirect to another handler or process a subhandler, all
103
+ # the parameters for your call handler are passed along with any additional
104
+ # parameters you can specify. For instance, this redirect call:
105
+ #
106
+ # redirect(:handler => :foo_bar,
107
+ # :param => 123,
108
+ # "foo" => "bar")
109
+ #
110
+ # You can reference these parameters like this:
111
+ #
112
+ # if @params[:param] == 123 && @params["foo"] == "bar" then
113
+ #
114
+ # == Sessions
115
+ #
116
+ # A session object is created for each RAGI connection. The session object
117
+ # is simply a has object that allows you to store objects in memory during
118
+ # the call. Unlike the web, session objects are not maintained across
119
+ # connection. You can place objects in the functions using the hash object:
120
+ #
121
+ # @session[:foo] = :bar
122
+ #
123
+ # Later you can retrieve the contents using the hash object:
124
+ #
125
+ # if(@session[:foo] == :bar) then
126
+ #
127
+ # == Redirection
128
+ #
129
+ # A call handler can redirect to another call handler. When you redirect,
130
+ # controll of the call is passed to the other call handler when you exit
131
+ # your call handler. The session, parameters and connection objects remain
132
+ # the same. You can specify handlers & actions for the redirection. If the
133
+ # handler is not specified then the existing handler is reused. In addition,
134
+ # you can specify additional parameters to be added for the redirected call
135
+ # handler.
136
+ #
137
+ # redirect(:handler => :foo,
138
+ # :action => :bar,
139
+ # :param => 123,
140
+ # "foo" => "bar")
141
+ #
142
+ # Handlers & actions can be referenced by symbol or string. Handlers can
143
+ # also reference an explicit class or a class instance.
144
+
145
+ class CallHandler
146
+ attr_accessor :connection, :session, :redirect_route
147
+
148
+ #-----------------------------------------------------------------------------
149
+ # PUBLIC INSTANCE METHODS
150
+ #-----------------------------------------------------------------------------
151
+
152
+ # call this method to redirect to another handler. after calling this
153
+ # method you should return to the caller. You will not receive
154
+ # control of the call back when this handler is complete.
155
+
156
+ def redirect(options)
157
+ @redirect_route = @route.dup.update(options)
158
+ end
159
+
160
+ # call this method to call another handler from within your handler. when
161
+ # the other handler has finished control will be returned to the caller.
162
+
163
+ def process(options)
164
+ route = @route.dup.update(options)
165
+
166
+ if CallHandler.match_class(self.class, route[:handler]) then
167
+ RAGI::LOGGER.warn("Warning, creating new instance of handler #{route[:handler]}.")
168
+ end
169
+
170
+ CallHandler.process(route, @connection, self.session)
171
+ end
172
+
173
+ #-----------------------------------------------------------------------------
174
+ # PUBLIC CLASS METHODS
175
+ #-----------------------------------------------------------------------------
176
+
177
+ # call this method to attach helpers to your call handler. Helpers are
178
+ # defined and located in much the same way as helpers on
179
+ # ActiveController objects.
180
+
181
+ def self.helper(*args)
182
+ args.flatten.each do |arg|
183
+ case arg
184
+ when Module
185
+ add_template_helper(arg)
186
+ when String, Symbol
187
+ file_name = arg.to_s.underscore + '_helper'
188
+ class_name = file_name.camelize
189
+
190
+ begin
191
+ require_dependency("helpers/#{file_name}")
192
+ rescue LoadError => load_error
193
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
194
+ msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
195
+ raise LoadError.new(msg).copy_blame!(load_error)
196
+ end
197
+
198
+ add_template_helper(class_name.constantize)
199
+ else
200
+ raise ArgumentError, 'helper expects String, Symbol, or Module argument'
201
+ end
202
+ end
203
+ end
204
+
205
+ # Based on the specified URN, determine the handler & action to use for
206
+ # the incoming call.
207
+
208
+ def self.route(path)
209
+ route = {}
210
+
211
+ uri = URI.parse(path)
212
+
213
+ path = uri.path
214
+ path = path[1..-1] if path[0] = '/'
215
+
216
+ # todo: deal with paths like foo/bar/1
217
+ if path.index '/' then
218
+ route[:handler] = path[0..path.index('/')-1]
219
+ route[:action] = path[path.index('/')+1..-1]
220
+ else
221
+ route[:handler] = path
222
+ route[:action] = :dialup
223
+ # todo: check the connection object to see if this is a dialout connection
224
+ end
225
+ # todo: extract uri.query into parameters on the route
226
+ route
227
+ end
228
+
229
+ # Based on the specified route, load and initialize a handler. we then
230
+ # call the appropriate action based on the route. if the handler species
231
+ # that we should redirect, then do that.
232
+
233
+ def self.process(route, connection, session = {})
234
+ handler = nil
235
+
236
+ while route do
237
+
238
+ # check to see if the handler actually needs to be created
239
+
240
+ if !handler ||
241
+ !match_class(handler.class, route[:handler]) then
242
+
243
+ # to create the handler we either create an instance of the
244
+ # referenced class or we have to load a dependency and create the
245
+ # class referenced.
246
+
247
+ case route[:handler]
248
+ when Class
249
+ puts "running ragi class"
250
+ handler = route[:handler].new
251
+ when String, Symbol
252
+ puts "running ragi string lookup"
253
+ file_name = route[:handler].to_s.underscore + '_handler'
254
+ puts "filename: #{file_name}"
255
+ class_name = file_name.camelize
256
+
257
+ begin
258
+ require_dependency("handlers/#{file_name}")
259
+ rescue LoadError => load_error
260
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
261
+ msg = (requiree == file_name) ? "Missing handler file handlers/#{file_name}.rb" : "Can't load file: #{requiree}"
262
+ raise LoadError.new(msg).copy_blame!(load_error)
263
+ end
264
+
265
+ handler = class_name.constantize.new
266
+ else
267
+ # todo: test support for passing handler as an instance
268
+ handler = route[:handler]
269
+ end
270
+ handler.init_session(route, connection, session)
271
+ end
272
+
273
+ handler.send(route[:action])
274
+
275
+ # if a redirection route was set, then we should loop
276
+ route = handler.redirect_route
277
+ handler.redirect_route = nil
278
+ end
279
+ end
280
+
281
+ #-----------------------------------------------------------------------------
282
+ # CONNECTION HELPERS
283
+ #-----------------------------------------------------------------------------
284
+
285
+ # We mirror all the methods of the callConnection object
286
+ def method_missing(meth, *attrs, &block)
287
+ if @connection.respond_to?(meth)
288
+ @connection.__send__(meth, *attrs, &block)
289
+ else
290
+ super
291
+ end
292
+ end
293
+
294
+ #-----------------------------------------------------------------------------
295
+ # INTERNAL METHODS
296
+ #-----------------------------------------------------------------------------
297
+
298
+ # InitSession is called by the framework to initialize a new call handler
299
+ # as it gets constructed. This method should never be called from outside
300
+ # the framework.
301
+
302
+ def init_session(route, connection, session = {} )
303
+ @route = route
304
+ @connection = connection
305
+ @session = session
306
+ @redirect_route = nil
307
+ @call_status = connection.get_call_status
308
+
309
+ # Load parameters from the connection
310
+ @params = connection.params.dup
311
+
312
+ # Extract and add the hash data
313
+ hashData = connection.get_hash_data
314
+ if hashData then
315
+ hashData.each do |name, val|
316
+ @params[name] = val
317
+ end
318
+ end
319
+
320
+ # Add parameters from the route object
321
+ route.reject { |name, val| [:handler, :action].include? name }.each do |name, val|
322
+ @params[name] = val
323
+ end
324
+ end
325
+
326
+ # Add the template module into us
327
+
328
+ def self.add_template_helper(mod)
329
+ self.class_eval "include #{mod}"
330
+ end
331
+
332
+ # Normalizes the class name passed in. For strings and symbols this will
333
+ # add the '_handler' string if it is not already present then camelize the
334
+ # string ('foo_handler' => 'FooHandler'). For classes this will simply
335
+ # ask the class it's name
336
+
337
+ def self.normalize_class_name(obj)
338
+ case obj
339
+ when Class
340
+ obj.to_s
341
+ when String, Symbol
342
+ obj = obj.to_s
343
+ if !obj.downcase.include? 'handler' then
344
+ obj = obj.to_s.underscore + '_handler'
345
+ end
346
+ obj.camelize
347
+ end
348
+ end
349
+
350
+ # Compare two normalized classes to see if they are the same. The following
351
+ # should all return as equivalent:
352
+ #
353
+ # FooBarHelper
354
+ # "FooBar"
355
+ # :foo_bar
356
+ # "foo_bar"
357
+
358
+ def self.match_class(a,b)
359
+ normalize_class_name(a) == normalize_class_name(b)
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,188 @@
1
+ #
2
+ # RAGI - Ruby classes for implementing an AGI server for Asterisk
3
+ # The BSD License for RAGI follows.
4
+ #
5
+ # Copyright (c) 2005, SNAPVINE LLC (www.snapvine.com)
6
+ # All rights reserved.
7
+
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # this list of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice,
14
+ # this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ # * Neither the name of SNAPVINE nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
24
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # Class: callInitiate.rb
32
+ # This class provides a convenient way to place outbound calls through Asterisk.
33
+ # When answered, calls initiated with callInitiate are redirected back to a
34
+ # RAGI callHandler for processing
35
+ #
36
+
37
+ require 'cgi'
38
+ require 'yaml'
39
+
40
+ module RAGI
41
+ class UsageError < StandardError; end
42
+ class CmdNotFoundError < StandardError; end
43
+ class ApplicationError < StandardError; end
44
+
45
+ DEFAULT_CALL_OPTIONS = {
46
+ :caller_id => "10",
47
+ :max_retries => 0,
48
+ :retry_time => 5,
49
+ :wait_time => 45
50
+ }
51
+
52
+ class << self
53
+
54
+ # The place_call method allows the caller to initiate a call to a given
55
+ # phonenumber, handling it with the specified URN. In addition, the caller
56
+ # can specify additional parameters:
57
+ #
58
+ # :caller_id - a string containing the caller id to use, e.g.
59
+ # "8005551212". There is no name, just a number.
60
+ #
61
+ # :hash_params - a hash containing parameters that will be made available
62
+ # to the call handler
63
+ #
64
+ # :call_date - specify the time the call should occur, specify nil
65
+ # to make the call occur immediately
66
+ #
67
+ # :unique_id - optional uniqueID useful if you need to delete a
68
+ # scheduled call later.
69
+ #
70
+ # :max_retries - how many times to retry if the call doesn't go through
71
+ #
72
+ # :retry_time - time to wait between tries in seconds
73
+ #
74
+ # :wait_time - time to wait in seconds for the call to answer,
75
+ # inclusive of time spent connecting to the PSTN
76
+ # termination provider.
77
+
78
+ def place_call(phone_number, urn, options = {})
79
+ options = DEFAULT_CALL_OPTIONS.clone.update(options)
80
+
81
+ options[:agi_server] ||= RAGI::globalConfig["agiServer"]
82
+
83
+ RAGI::CallInitiate.place_call(phone_number,
84
+ options[:caller_id],
85
+ urn,
86
+ options[:hash_params],
87
+ options[:call_date],
88
+ options[:unique_id],
89
+ options[:max_retries],
90
+ options[:retry_time],
91
+ options[:wait_time],
92
+ options[:set_vars])
93
+ end
94
+ end
95
+
96
+ class CallInitiate
97
+
98
+ # This function is called by RAGI.place_call to actually do the work.
99
+ def self.place_call(phoneNumber, callerID, urn, hashData, callDate, uniqueID, maxRetries, retryTime, waitTime, extraChannelVars)
100
+
101
+ placeCallNow = false
102
+ if (callDate == nil)
103
+ placeCallNow = true
104
+ callDate = Time.now
105
+ end
106
+
107
+ if (urn[0..0] != '/') then
108
+ raise ApplicationError, "Relative URNs cannot be used (found #{urn})"
109
+ end
110
+
111
+ agiServer = RAGI::globalConfig["agiServer"]
112
+ RAGI.LOGGER.debug("Initiating call with agi server: #{agiServer}")
113
+
114
+ fileName = getfilename(phoneNumber, callDate, uniqueID)
115
+
116
+ wakeupFile = File.join(RAGI::globalConfig["wakeupCallPath"], fileName.to_s)
117
+ outgoingFile = File.join(RAGI::globalConfig["outgoingCallPath"], fileName.to_s)
118
+ callfile = File.new(fileName.to_s, "w+")
119
+
120
+ s = ""
121
+ s << ";This file was generated by RAGI's callInitiate class\r\n"
122
+ s << ";File generated date: #{Time.now.strftime('%m-%d-%Y at %H:%M -- %A')}\r\n"
123
+ s << ";Call date: #{Time.new.strftime('%m-%d-%Y at %H:%M -- %A')}\r\n\r\n"
124
+ s << "Channel: Local/outbound@dialout\r\n"
125
+ s << "Callerid: <#{callerID}>\r\n"
126
+ s << "MaxRetries: #{maxRetries}\r\n"
127
+ s << "RetryTime: #{retryTime}\r\n"
128
+ s << "WaitTime: #{waitTime}\r\n"
129
+ s << "Context: dialout\r\n"
130
+ s << ";magic extension for outbound calls via RAGI callInitiate\r\n"
131
+ s << "Extension: outbound-handler\r\n"
132
+ s << "Priority: 1\r\n"
133
+
134
+ #put in the fundamental call handling variables
135
+ s << "SetVar: CallInitiate_phonenumber=#{phoneNumber}\r\n"
136
+ s << "SetVar: CallInitiate_callerid=#{callerID}\r\n"
137
+
138
+ s << "\r\nSetVar: AGI_URL=#{urn}\r\n"
139
+ s << "\r\nSetVar: AGI_SERVER=#{agiServer}\r\n"
140
+
141
+ if (extraChannelVars)
142
+ extraChannelVars.each do |name, value|
143
+ s << "\r\nSetVar: #{name}=#{value}\r\n"
144
+ end
145
+ end
146
+
147
+ marshalData = CGI.escape(YAML.dump(hashData))
148
+
149
+ s << "\r\nSetVar: CallInitiate_hashdata=#{marshalData}\r\n\r\n"
150
+
151
+ callfile.print(s)
152
+
153
+ #Note: asterisk call files need these terminating line feeds or else it crashes.
154
+ callfile.print("\r\n\r\n\r\n")
155
+ callfile.close
156
+
157
+ # Setup permissions so that asterisk can read this thing no matter what...
158
+ File.chmod(0777, fileName)
159
+ if (placeCallNow == true)
160
+ FileUtils.mv fileName, outgoingFile #place call now
161
+ # todo Can't rely on the file still being there. Asterisk may have grabbed it
162
+ File.chmod(0777, outgoingFile)
163
+ else
164
+ FileUtils.mv fileName, wakeupFile #schedule call for later
165
+ File.chmod(0777, wakeupFile)
166
+ end
167
+ end
168
+
169
+ def self.delete_call(phoneNumber, callDate, uniqueID)
170
+ #assert: callDate is not nil
171
+ fileName = getfilename(phoneNumber, callDate, uniqueID)
172
+ wakeupFile = File.join(RAGI::globalConfig["wakeupCallPath"], fileName)
173
+ FileUtils.rm wakeupFile, :force => true
174
+ end
175
+
176
+ # Returns the hashtable representation of str if string is encoded by hashEncode
177
+ def self.decode_call_params(hashStr)
178
+ if hashStr then
179
+ YAML.load(CGI.unescape(hashStr))
180
+ end
181
+ end
182
+
183
+ private
184
+ def self.getfilename(phoneNumber, callDate, uniqueID)
185
+ "#{callDate.strftime('%H%M')}.#{phoneNumber}.#{uniqueID}.call"
186
+ end
187
+ end
188
+ end