Sunday, June 22, 2008

Lazy Rails Development with Shell Function Shortcuts

I love screencasts, and on every screencast I see that texmate editor I'm so jealous of. I work at a startup so it'll be a while before we'll dish the cash out for a mac, as much as I'd like one, its not in the cards right now.

Once of the things I like the most about that editor is the ability to seemlessly jump between different files in your rails app. I find myself doing certain commands _constantly.

One tip is to create shell functions for your most commonly used commands

ruby script/server #rss
mongrel_rails start -d -e development -p 3000 #mrd_start
mongrel_rails stop #mrd_stop
vim app/controller/some_controller.rb #rvim c some
vim app/models/post.rb #rvim m post
vim app/helper/posts_helper.rb #rvim h posts
vim app/views/posts/_form.rhtml #rvim v posts/_form.rhtml
mongrel_rails cluster::stop #mrc stop
mongrel_rails cluster::start #mrc start

Here is the code (feedback appreciated)

function rss
command ruby script/server
function mrd_start
command mongrel_rails start -d -e development -p 3000
function mrd_stop
command mongrel_rails stop
function mrd_restart
command mongrel_rails stop&&mongrel_rails start -d -e development -p 3000
function mrc_start
command mongrel_rails cluster::start

function mrc_stop
command mongrel_rails cluster::stop
function mrc_restart
command mongrel_rails cluster::stop&&mongrel_rails cluster::start

function rvim
case "$1" in
command vim app/controllers/$2_controller.rb
command vim app/views/$2
command vim app/models/$2.rb
command app/helpers/$2_helper.rb


Hope this helps!

Thursday, June 19, 2008

Recording Transport with FreePBX

If you have ever used freepbx in production, you know that there are many cases where its not adequate as is for the needs of a call-center. Chances are you got nowhere by going to get help in #freepbx on freenode. You probably went to #asterisk on freenode where people soon figured out you were using freepbx and then began ignoring you (They hate that). Well, I've had to conquer some difficult requests for features with our PBX and often I've had to make custom dialplans for them. I'll post a few of them.

We were testing out a new outbound sales campaign that involved qualifying a lead and sending it to a third party. We needed to monitor the third party and make recordings, but they were on a traditional PBX. I thought of using DISA (Direct Inward System Access) but could not find any tutorials or articles on how to setup recording with DISA. I had to do four things:
  1. Figure out how recording was done on the agent extensions
  2. Create a new outbound dial plan pattern and import the recording snippet into the new plan.
  3. Create some method of communicating the call to a webserver with a CRM on it.
  4. Create a method of automatically transporting the call to the webserver upon completion

I figured that creating a new dialplan would be the easiest, since DISA provides a dialtone from inside the system.

grep -R recording /etc/asterisk

I noticed several macro entries in extensions.conf. The one that was of particular interest was

./extensions.conf:exten => s,7,Macro(record-enable,${MACRO_EXTEN},${RecordMethod})

It looks like record enable takes the extension to record under. In several other places, RecordMethod is set to OUT or IN. After looking at the structure of files in /var/spool/asterisk/monitor. I determined that these variables are probably to help determine the filename of the recording.

I created a new dialplan in extensions_custom.conf

exten => _*123NXXNXXXXXX,1,Answer()
exten => _*123NXXNXXXXXX,3,Macro(record-enable,000, OUT)
exten => _*123NXXNXXXXXX,4,Dial(SIP/icall/${EXTEN:4},,g)
exten => _*123NXXNXXXXXX,5,System(curl${EXTEN:4}~${CALLFILENAME})
exten => _*123NXXNXXXXXX,6,Hangup

The dialplan matches a pattern (indicated by _) starting with *123 followed by an area code and 7 digit number. First the call is answered, recording is enabled. The dial is executed. EXTEN:4 removes the first 4 digits from the dial string *1234178575309=4178575309. The missing parameter between the first and last arguments of dial is for the timeout which i want to default to infinite. g is a flag that tells asterisk to continue through the dialplan after the callee hangs up. That means execute priority 5 and 6 after the person being called hangs up.

Before I added Priority 5, I had to figure out how I was going to pass the call information to a second server and get it downloaded. I did asterisk -r and executed "dialplan reload". I dialed the *123 pattern from a softphone and watched to see what variables were being set. I saw that CALLFILENAME was being set. I had used curl previously to send data about calls to another server.

Priority 5: With callfilename set, I call RecordLog.php on my webserver with args=The number dialer~the callfilename. Why did I do it like that? Its because asterisk kept getting confused when I had an ampersand separating multiple variables for the qs. Asterisk would pass the command to bash, and bash would interpret the ampersand as & in bash (background the current command and execute something else). In retrospect, now that I'm writing this I could have quoted the url.


$output=shell_exec("curl -k $url > /var/www/web1/web/recordings/$filename.wav");

We open a call_index.htm file for append writing in a password protected recordings directory. We parse the qs and find the cid (caller id) and filename. We write a link to the htm file. We execute a curl command to retrieve the call with that filename from asterisk and pipe the output directly into a wav file.

download.php(Modified from /var/www/html/recordings/misc/audio.php)

if (isset($_GET["call"])) {

if (!is_file($path)) { die("404 File not found!"); }

// Gather relevent info about file
$size = filesize($path);
$name = basename($path);
$extension = strtolower(substr(strrchr($name,"."),1));

// This will set the Content-Type to the appropriate setting for the file
$ctype ='';
switch( $extension ) {
case "mp3": $ctype="audio/mpeg"; break;
case "wav": $ctype="audio/x-wav"; break;
case "Wav": $ctype="audio/x-wav"; break;
case "WAV": $ctype="audio/x-wav"; break;
case "gsm": $ctype="audio/x-gsm"; break;

// not downloadable
default: die("404 File not found!"); break ;

// need to check if file is mislabeled or a liar.
$fp=fopen($path, "rb");
if ($size && $ctype && $fp) {
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: wav file");
header("Content-Type: " . $ctype);
header("Content-Disposition: attachment; filename=" . $name);
header("Content-Transfer-Encoding: binary");
header("Content-length: " . $size);

I know there are some dangerous security flaws in this code. I rationalize it like this, this is not accessible except from in our network as our PBX is not exposed directly to the internet. THe original audio.php had some pesky crypt function interfering with things.

I hope this was helpful to someone