#!/usr/bin/php
<?php

# cloudrec-upload: Copy / move a recorded file and properly rename it
# so that the cloud recording uploader could pick it up.

// Setup: add the following as a Recording Script:
//   /usr/share/ombutel/scripts/cloudrec-upload \
//   file=^{MIXMONITOR_FILENAME} \
//   time=^{CDR(start)} \
//   type=^{CDR(calltype)} \
//   src=^{CDR(src)} \
//   dst=^{CDR(dst)} \
//   source=^{CDR(source)} \
//   destination=^{CDR(destination)} \
//   id=^{CDR(uniqueid)} \
//   del=<y|n>
//
// Example of input:
// file=/var/spool/asterisk/monitor/1482742055.1521.wav \
//   "time=2016-12-26 10:47:35" \
//   type=2 \
//   src=0544571470 \
//   dst=2979 \
//   id=1511691053.77
// Required file name format:
// FileName: <Direction>_<Number>_<Ext>_<DateTime>_<Duration>_<id>
// INCOMING_9039041771_2979_2016-04-11T09:52:58_00:02:12.04_1511691053.77.wav
// OUTGOING_0523992414_2992_2016-04-11T08:55:19_00:00:08.04_1511691053.77.sav
// INCOMING_4994_2979_2016-04-11T11:52:13_00:02:19.72_1511691053.77.sav

$delimiter = '_';
$callcabinet_config = '/var/lib/cc-cloud-rec/CCconfig.txt';
$callcabinet_folder = '/var/spool/asterisk/cc-cloud-rec/';
$callcabinet_url = 'https://xorcom.callcabinet.com/Calls/SingleCall?ClientRef=';

$call = (object) [
	'file' => '',
	'time' => '',
	'type' => '',
	'src' => '',
	'source' => '',
	'dst' => '',
	'destination' => '',
	'uniqueid' => '',
	'id' => '',
];

try {
	parse_cmdline($argv, $call);
	// ignore empty files
	if (filesize($call->file) <= 44) { // WAV header size is 44
		@unlink($call->file);
		clear_recfile($call->uniqueid);
		return;
	}
	$settings = parse_cc_config($callcabinet_config);
	ksort($settings);
	$duration = get_duration_soxi($call->file);
	@mkdir($callcabinet_folder, 0775, true);
	convert($call);
	$new_filename = create_filename($call, $settings, $delimiter, $duration);
	save_file($call, $callcabinet_folder.$new_filename);
	update_cdr($call);
	echo "File {$callcabinet_folder}{$new_filename} sent\n";
} catch (Exception $e) {
	log_to_syslog($call, $e->getMessage());
}

exit();
###################  End of main ##########################

# Open a syslog connection, log and close connection.
# We will only need syslog once, if at all.
function log_to_syslog($call, $msg) {
	$file = $call->file;
	if (isset($call->orig_file)) {
		$file = $call->orig_file;
	}
	openlog('cloudrec-upload', LOG_PID, LOG_LOCAL1);
	syslog(LOG_WARNING, $msg." ($file)\n");
	closelog();
}

function parse_cmdline($argv, &$call) {
	foreach (array_slice($argv, 1) as $arg) {
		list($name, $val) = explode('=', $arg, 2);
		$call->$name = $val;
		if ($name == 'del') {
			$call->del = $val == 'y';
		} else if ($name == 'id') {
			$call->uniqueid = $val;
		}
	}
	foreach (['file', 'time', 'type', 'uniqueid'] as $key) {
		if ($call->$key == '') {
			throw new Exception("Missing command-line argument: $key=");
		}
	}
	# Remove potentially harmful charachters: Those are not likely
	# to be present. If they will, they may cause odd results:
	foreach (['time', 'src', 'dst', 'source', 'destination'] as $key) {
		preg_replace('_[\\{}<>~$;&|*?#`"\'/]+_', '', $call->$key);
	}
	# 'file': Allow '/'
	preg_replace('_[\\{}<>~$;&|*?#`"\']+_', '', $call->file);
	preg_replace('_[^\d.]+_', '', $call->uniqueid);
	preg_replace('_[^\d]+_', '', $call->type);

	if ($call->dst == 's') {
		$call->dst = '';
	}

	if (!file_exists($call->file)) {
		throw new Exception("Original recording does not exist ({$call->file})");
	}
	$call->id = $call->uniqueid;
}

function parse_cc_config($config_file) {
	global $delimiter;
	global $callcabinet_folder;

	if(!file_exists($config_file)) {
		throw new Exception("Callcabinet config file is not found ($config_file)");
	}
	if(!is_readable($config_file)) {
		throw new Exception("Callcabinet config file is not readable ($config_file)");
	}

	$configs = explode("\n", trim(file_get_contents($config_file)));
	foreach($configs as $config) {
		if(preg_match('/FileNameDelimiter:\s?<(.*)>/i', $config, $match)) {
			$delimiter = $match[1];
		} elseif(preg_match('/FileNamePos(\d+):\s?<(.*)>/i', $config, $match)) {
			list(, $index, $value) = $match;
			if(!preg_match('/None/i', $value)) {
				$settings[$index] = $value;
			}
		} elseif(preg_match('/Repository:\s?<(.*)>/i', $config, $match)) {
			if(strlen($match[1]) > 0) {
				$callcabinet_folder = "{$match[1]}/";
			}
		}
	}
	return $settings;
}

function create_filename($call, $settings, $delimiter, $duration) {
	$incoming_call = ($call->type == 2 || $call->type == 5);

	foreach($settings as $index => $setting) {
		switch($setting) {
			case "Direction": {
				$value = 'OUTGOING';
				if ($incoming_call) {
					$value = "INCOMING";
				}
				$settings[$index] = $value;
				break;
			}
			case "Number": {
				$value = coalesce($call->dst, $call->destination, '0000');
				if ($incoming_call) {
					$value = coalesce($call->src, $call->source, '0000');
				}
				$settings[$index] = $value;
				break;
			}
			case "Ext": {
				$value = coalesce($call->src, $call->source, '0000');
				if ($incoming_call) {
					$value = coalesce($call->dst, $call->destination, '0000');
				}
				$settings[$index] = $value;
				break;
			}
			case "DateTime": { //format YYYY-MM-\DDTHH:MM:SS
				$settings[$index] = format_date($call->time);
				break;
			}
			case "Duration": {
				$settings[$index] = $duration;
				break;
			}
			case "CustomerInternalRef": {
				$settings[$index] = $call->id;
				break;
			}
		}
	}
	return implode($delimiter, $settings) . '.WAV';
}

# If file is not already a WAV file, converts the file to .WAV and sets
# $call->file to the new name.
#
# In both cases: returns the duration.
function convert($call) {
	$suffix = pathinfo($call->file, PATHINFO_EXTENSION);
	$call->orig_file = $call->file;
	if ($suffix == 'WAV') {
		// do nothing
	} else if ($suffix == 'gsm') {
		exec("sox {$call->file} {$call->file}.WAV", $stdout, $errno);
		if ($errno != 0) {
			throw new Exception("Failed adding a WAV header ({$call->file})");
		}
		$call->file = "{$call->file}.WAV";
	} else {
		exec("sox -V {$call->file} --encoding gsm {$call->file}.WAV 2>&1", $stdout, $errno);
		if ($errno != 0) {
			throw new Exception("Failed encoding wav file as gsm ({$call->file})");
		}
		$call->file = "{$call->file}.WAV";
	}
}

function get_duration_soxi($fname) {
	exec("soxi -d $fname", $stdout, $errno);
	if ($errno != 0) {
		throw new Exception("Unable to obtain duration of audio file ($fname)");
	}
	list($duration_str) = $stdout;
	$duration = parse_duration($duration_str);
	return $duration;
}

# Save file to the new localtion, optionally delete the original.
function save_file($call, $new_filename) {
	$file_converted = $call->file != $call->orig_file;

	if (!$call->del && !$file_converted) {
		// File needs to be copied. Copy it locally so the move
		// to the CC directory will be atomic.
		$new_name = "{$call->file}.WAV";
		if (!copy($call->file, $new_name)) {
			throw new Exception("Unable to duplicate {$call->file} to $new_name");
		}
		$call->orig_file = $call->file;
		$call->file = $new_name;
	}

	if (!rename($call->file, $new_filename)) {
		throw new Exception("Unable to move file {$call->file} to $new_filename");
	}

	if ($call->del && $file_converted) {
		@unlink($call->orig_file);
	}
}

function update_cdr($call) {
	// FIXME: CDR record may not have yet been written!
	global $callcabinet_url;

	$db = connect_to_db();
	if ($call->del) {
		$stmt = $db->prepare("update cdr set recfile = '', recfile_cloud = ?, uniqueid = ? where uniqueid = ?");
	} else {
		$stmt = $db->prepare("update cdr set recfile_cloud = ?, uniqueid = ? where uniqueid = ?");
	}
	$new_url = "{$callcabinet_url}{$call->id}";

	if (!$stmt->execute([$new_url, $call->id, $call->uniqueid])) {
		throw new Exception("Failed updating CDR for call {$call->uniqueid}: " .
			implode(', ', $stmt->errorInfo()));
	}
}

function clear_recfile($uniqueid) {
	$db = connect_to_db();
	$stmt = $db->prepare("update cdr set recfile = '' where uniqueid = ?");
	if (!$stmt->execute([$uniqueid])) {
		throw new Exception("Failed updating CDR for call {$uniqueid}: " .
			implode(', ', $stmt->errorInfo()));
	}
}

function connect_to_db() {
	$host = "127.0.0.1";
	$dbname = "asterisk";
	$user = "asterisk";
	$pass = "asterisk";
	return new PDO("mysql:host=$host;dbname=".$dbname, $user, $pass);
}

function parse_duration($duration_str) {
	list($hours, $minutes, $seconds) = explode(':', $duration_str);
	return $hours * 3600 + $minutes * 60 + $seconds;
}

function format_date($full_time) {
	list($date, $time) = explode(' ', $full_time, 2);
	return "${date}T${time}";
}

function coalesce() {
	foreach (func_get_args() as $arg) {
		if ($arg != '') {
			return $arg;
		}
	}
}

?>
