ragi 1.0.0

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.
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