Merge branch 'master' of dev.sourcefabric.org:airtime

This commit is contained in:
paul.baranowski 2011-03-25 15:26:43 -04:00
commit 2289f37b8a
33 changed files with 439 additions and 381 deletions

View File

@ -1,5 +1,5 @@
<?php <?php
define('AIRTIME_VERSION', '1.7.0 alpha'); define('AIRTIME_VERSION', '1.7.0-alpha');
define('AIRTIME_COPYRIGHT_DATE', '2010-2011'); define('AIRTIME_COPYRIGHT_DATE', '2010-2011');
define('AIRTIME_REST_VERSION', '1.1'); define('AIRTIME_REST_VERSION', '1.1');

View File

@ -6,8 +6,10 @@ class ApiController extends Zend_Controller_Action
public function init() public function init()
{ {
/* Initialize action controller here */ /* Initialize action controller here */
$ajaxContext = $this->_helper->getHelper('AjaxContext'); $context = $this->_helper->getHelper('contextSwitch');
$ajaxContext->addActionContext('version', 'json') $context->addActionContext('version', 'json')
->addActionContext('recorded-shows', 'json')
->addActionContext('upload-recorded', 'json')
->initContext(); ->initContext();
} }
@ -133,6 +135,7 @@ class ApiController extends Zend_Controller_Action
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key'); $api_key = $this->_getParam('api_key');
if(!in_array($api_key, $CC_CONFIG["apiKey"])) if(!in_array($api_key, $CC_CONFIG["apiKey"]))
{ {
header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.0 401 Unauthorized');
@ -142,13 +145,9 @@ class ApiController extends Zend_Controller_Action
PEAR::setErrorHandling(PEAR_ERROR_RETURN); PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$from = $this->_getParam("from"); $result = Schedule::GetScheduledPlaylists();
$to = $this->_getParam("to");
if (Schedule::ValidPypoTimeFormat($from) && Schedule::ValidPypoTimeFormat($to)) {
$result = Schedule::ExportRangeAsJson($from, $to);
echo json_encode($result); echo json_encode($result);
} }
}
public function notifyMediaItemStartPlayAction() public function notifyMediaItemStartPlayAction()
{ {
@ -216,5 +215,54 @@ class ApiController extends Zend_Controller_Action
exit; exit;
} }
} }
public function recordedShowsAction()
{
global $CC_CONFIG;
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$today_timestamp = date("Y-m-d H:i:s");
$this->view->shows = Show::getShows($today_timestamp, null, $excludeInstance=NULL, $onlyRecord=TRUE);
}
public function uploadRecordedAction()
{
global $CC_CONFIG;
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$upload_dir = ini_get("upload_tmp_dir");
$file = StoredFile::uploadFile($upload_dir);
if(Application_Model_Preference::GetDoSoundCloudUpload())
{
$soundcloud = new ATSoundcloud();
$soundcloud->uploadTrack($file->getRealFilePath(), $file->getName());
}
$show_instance = $this->_getParam('show_instance');
$show = new ShowInstance($show_instance);
$show->setRecordedFile($file->getId());
$this->view->id = $file->getId();
}
} }

View File

@ -11,143 +11,6 @@ class PluploadController extends Zend_Controller_Action
->initContext(); ->initContext();
} }
public function upload($targetDir)
{
// HTTP headers for no cache etc
header('Content-type: text/plain; charset=UTF-8');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
// Settings
//$targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload";
$cleanupTargetDir = false; // Remove old files
$maxFileAge = 60 * 60; // Temp file age in seconds
// 5 minutes execution time
@set_time_limit(5 * 60);
// usleep(5000);
// Get parameters
$chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0;
$chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0;
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
// Clean the fileName for security reasons
//$fileName = preg_replace('/[^\w\._]+/', '', $fileName);
// Create target dir
if (!file_exists($targetDir))
@mkdir($targetDir);
// Remove old temp files
if (is_dir($targetDir) && ($dir = opendir($targetDir))) {
while (($file = readdir($dir)) !== false) {
$filePath = $targetDir . DIRECTORY_SEPARATOR . $file;
// Remove temp files if they are older than the max age
if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge))
@unlink($filePath);
}
closedir($dir);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
// Look for the content type header
if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
$contentType = $_SERVER["HTTP_CONTENT_TYPE"];
if (isset($_SERVER["CONTENT_TYPE"]))
$contentType = $_SERVER["CONTENT_TYPE"];
if (strpos($contentType, "multipart") !== false) {
if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen($_FILES['file']['tmp_name'], "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
unlink($_FILES['file']['tmp_name']);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
} else {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen("php://input", "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
$audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName;
$md5 = md5_file($audio_file);
$duplicate = StoredFile::RecallByMd5($md5);
if ($duplicate) {
if (PEAR::isError($duplicate)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}');
}
else {
$duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE);
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}');
}
}
$metadata = Metadata::LoadFromFile($audio_file);
if (PEAR::isError($metadata)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}');
}
// #2196 no id tag -> use the original filename
if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) {
$metadata[UI_MDATA_KEY_TITLE] = basename($audio_file);
$metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file);
}
// setMetadataBatch doesnt like these values
unset($metadata['audio']);
unset($metadata['playtime_seconds']);
$values = array(
"filename" => basename($audio_file),
"filepath" => $audio_file,
"filetype" => "audioclip",
"mime" => $metadata[UI_MDATA_KEY_FORMAT],
"md5" => $md5
);
$storedFile = StoredFile::Insert($values);
if (PEAR::isError($storedFile)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}');
}
$storedFile->setMetadataBatch($metadata);
return $storedFile;
}
public function indexAction() public function indexAction()
{ {
@ -157,30 +20,11 @@ class PluploadController extends Zend_Controller_Action
public function uploadAction() public function uploadAction()
{ {
$upload_dir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; $upload_dir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload";
$file = $this->upload($upload_dir); $file = StoredFile::uploadFile($upload_dir);
die('{"jsonrpc" : "2.0", "id" : '.$file->getId().' }'); die('{"jsonrpc" : "2.0", "id" : '.$file->getId().' }');
} }
public function uploadRecordedAction()
{
$upload_dir = ini_get("upload_tmp_dir");
$file = $this->upload($upload_dir);
if(Application_Model_Preference::GetDoSoundCloudUpload())
{
$soundcloud = new ATSoundcloud();
$soundcloud->uploadTrack($file->getRealFilePath(), $file->getName());
}
$show_instance = $this->_getParam('show_instance');
$show = new ShowInstance($show_instance);
$show->setRecordedFile($file->getId());
die('{"jsonrpc" : "2.0", "id" : '.$file->getId().'}');
}
public function pluploadAction() public function pluploadAction()
{ {
$this->view->headScript()->appendFile('/js/plupload/plupload.full.min.js','text/javascript'); $this->view->headScript()->appendFile('/js/plupload/plupload.full.min.js','text/javascript');

View File

@ -1,28 +0,0 @@
<?php
class RecorderController extends Zend_Controller_Action
{
public function init()
{
$ajaxContext = $this->_helper->getHelper('contextSwitch');
$ajaxContext->addActionContext('get-show-schedule', 'json')
->addActionContext('get-uploaded-file', 'json')
->initContext();
}
public function indexAction()
{
// action body
}
public function getShowScheduleAction()
{
$today_timestamp = date("Y-m-d H:i:s");
$this->view->shows = Show::getShows($today_timestamp, null, $excludeInstance=NULL, $onlyRecord=TRUE);
}
}

View File

@ -24,6 +24,8 @@ class UserController extends Zend_Controller_Action
$request = $this->getRequest(); $request = $this->getRequest();
$form = new Application_Form_AddUser(); $form = new Application_Form_AddUser();
$this->view->successMessage = "";
if ($request->isPost()) { if ($request->isPost()) {
if ($form->isValid($request->getPost())) { if ($form->isValid($request->getPost())) {
@ -42,6 +44,12 @@ class UserController extends Zend_Controller_Action
$user->save(); $user->save();
$form->reset(); $form->reset();
if (strlen($formdata['user_id']) == 0){
$this->view->successMessage = "<div class='success'>User added successfully!</div>";
} else {
$this->view->successMessage = "<div class='success'>User updated successfully!</div>";
}
} }
} }
} }

View File

@ -110,7 +110,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{ {
$controller = strtolower($request->getControllerName()); $controller = strtolower($request->getControllerName());
if ($controller == 'api' || $controller == 'recorder' || $controller == 'plupload' && $request->getActionName() == 'upload-recorded'){ if ($controller == 'api'){
$this->setRoleName("G"); $this->setRoleName("G");
} }

View File

@ -72,9 +72,9 @@ class Application_Form_AddUser extends Zend_Form
$select->setAttrib('class', 'input_select'); $select->setAttrib('class', 'input_select');
$select->setAttrib('style', 'width: 40%'); $select->setAttrib('style', 'width: 40%');
$select->setMultiOptions(array( $select->setMultiOptions(array(
"G" => "guest", "G" => "Guest",
"H" => "host", "H" => "Host",
"A" => "admin" "A" => "Admin"
)); ));
$select->setRequired(true); $select->setRequired(true);
$this->addElement($select); $this->addElement($select);

View File

@ -52,7 +52,7 @@ class Application_Form_Preferences extends Zend_Form
//SoundCloud Username //SoundCloud Username
$this->addElement('text', 'SoundCloudUser', array( $this->addElement('text', 'SoundCloudUser', array(
'class' => 'input_text', 'class' => 'input_text',
'label' => 'SoundCloud Username:', 'label' => 'SoundCloud Email:',
'required' => false, 'required' => false,
'filters' => array('StringTrim'), 'filters' => array('StringTrim'),
'value' => Application_Model_Preference::GetSoundCloudUser() 'value' => Application_Model_Preference::GetSoundCloudUser()

View File

@ -31,7 +31,7 @@ class RabbitMq
$EXCHANGE = 'airtime-schedule'; $EXCHANGE = 'airtime-schedule';
$channel->exchange_declare($EXCHANGE, 'direct', false, true); $channel->exchange_declare($EXCHANGE, 'direct', false, true);
$data = json_encode(Schedule::ExportRangeAsJson()); $data = json_encode(Schedule::GetScheduledPlaylists());
$msg = new AMQPMessage($data, array('content_type' => 'text/plain')); $msg = new AMQPMessage($data, array('content_type' => 'text/plain'));
$channel->basic_publish($msg, $EXCHANGE); $channel->basic_publish($msg, $EXCHANGE);

View File

@ -354,13 +354,14 @@ class Schedule {
* @return array * @return array
* Returns empty array if nothing found * Returns empty array if nothing found
*/ */
public static function GetItems($p_fromDateTime, $p_toDateTime, $p_playlistsOnly = true)
public static function GetItems($p_currentDateTime, $p_toDateTime, $p_playlistsOnly = true)
{ {
global $CC_CONFIG, $CC_DBC; global $CC_CONFIG, $CC_DBC;
$rows = array(); $rows = array();
if (!$p_playlistsOnly) { if (!$p_playlistsOnly) {
$sql = "SELECT * FROM ".$CC_CONFIG["scheduleTable"] $sql = "SELECT * FROM ".$CC_CONFIG["scheduleTable"]
." WHERE (starts >= TIMESTAMP '$p_fromDateTime') " ." WHERE (starts >= TIMESTAMP '$p_currentDateTime') "
." AND (ends <= TIMESTAMP '$p_toDateTime')"; ." AND (ends <= TIMESTAMP '$p_toDateTime')";
$rows = $CC_DBC->GetAll($sql); $rows = $CC_DBC->GetAll($sql);
foreach ($rows as &$row) { foreach ($rows as &$row) {
@ -388,7 +389,7 @@ class Schedule {
." ON st.instance_id = si.id" ." ON st.instance_id = si.id"
." LEFT JOIN $CC_CONFIG[showTable] as sh" ." LEFT JOIN $CC_CONFIG[showTable] as sh"
." ON si.show_id = sh.id" ." ON si.show_id = sh.id"
." WHERE (st.starts >= TIMESTAMP '$p_fromDateTime')" ." WHERE (st.ends >= TIMESTAMP '$p_currentDateTime')"
." AND (st.ends <= TIMESTAMP '$p_toDateTime')" ." AND (st.ends <= TIMESTAMP '$p_toDateTime')"
//next line makes sure that we aren't returning items that //next line makes sure that we aren't returning items that
//are past the show's scheduled timeslot. //are past the show's scheduled timeslot.
@ -627,24 +628,16 @@ class Schedule {
* @param string $p_toDateTime * @param string $p_toDateTime
* In the format "YYYY-MM-DD-HH-mm-SS" * In the format "YYYY-MM-DD-HH-mm-SS"
*/ */
public static function ExportRangeAsJson($p_fromDateTime = null , $p_toDateTime = null) public static function GetScheduledPlaylists()
{ {
global $CC_CONFIG, $CC_DBC; global $CC_CONFIG, $CC_DBC;
if (is_null($p_fromDateTime)) {
$t1 = new DateTime(); $t1 = new DateTime();
$t1->sub(new DateInterval("PT24H"));
$range_start = $t1->format("Y-m-d H:i:s"); $range_start = $t1->format("Y-m-d H:i:s");
} else {
$range_start = Schedule::PypoTimeToAirtimeTime($p_fromDateTime);
}
if (is_null($p_fromDateTime)) {
$t2 = new DateTime(); $t2 = new DateTime();
$t2->add(new DateInterval("PT24H")); $t2->add(new DateInterval("PT24H"));
$range_end = $t2->format("Y-m-d H:i:s"); $range_end = $t2->format("Y-m-d H:i:s");
} else {
$range_end = Schedule::PypoTimeToAirtimeTime($p_toDateTime);
}
// Scheduler wants everything in a playlist // Scheduler wants everything in a playlist
$data = Schedule::GetItems($range_start, $range_end, true); $data = Schedule::GetItems($range_start, $range_end, true);
@ -720,25 +713,5 @@ class Schedule {
return $result; return $result;
} }
/**
* Remove all items from the schedule in the given range.
*
* @param string $p_start
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
* @param string $p_end
* In the format YYYY-MM-DD HH:MM:SS.nnnnnn
*/
public static function RemoveItemsInRange($p_start, $p_end)
{
$items = Schedule::GetItems($p_start, $p_end);
foreach ($items as $item) {
$scheduleGroup = new ScheduleGroup($item["group_id"]);
$scheduleGroup->remove();
}
RabbitMq::PushSchedule();
}
} }

View File

@ -1647,5 +1647,143 @@ class StoredFile {
return array("sEcho" => intval($data["sEcho"]), "iTotalDisplayRecords" => $totalDisplayRows, "iTotalRecords" => $totalRows, "aaData" => $results); return array("sEcho" => intval($data["sEcho"]), "iTotalDisplayRecords" => $totalDisplayRows, "iTotalRecords" => $totalRows, "aaData" => $results);
} }
public static function uploadFile($targetDir) {
// HTTP headers for no cache etc
header('Content-type: text/plain; charset=UTF-8');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
// Settings
//$targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload";
$cleanupTargetDir = false; // Remove old files
$maxFileAge = 60 * 60; // Temp file age in seconds
// 5 minutes execution time
@set_time_limit(5 * 60);
// usleep(5000);
// Get parameters
$chunk = isset($_REQUEST["chunk"]) ? $_REQUEST["chunk"] : 0;
$chunks = isset($_REQUEST["chunks"]) ? $_REQUEST["chunks"] : 0;
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
// Clean the fileName for security reasons
//$fileName = preg_replace('/[^\w\._]+/', '', $fileName);
// Create target dir
if (!file_exists($targetDir))
@mkdir($targetDir);
// Remove old temp files
if (is_dir($targetDir) && ($dir = opendir($targetDir))) {
while (($file = readdir($dir)) !== false) {
$filePath = $targetDir . DIRECTORY_SEPARATOR . $file;
// Remove temp files if they are older than the max age
if (preg_match('/\.tmp$/', $file) && (filemtime($filePath) < time() - $maxFileAge))
@unlink($filePath);
}
closedir($dir);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
// Look for the content type header
if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
$contentType = $_SERVER["HTTP_CONTENT_TYPE"];
if (isset($_SERVER["CONTENT_TYPE"]))
$contentType = $_SERVER["CONTENT_TYPE"];
if (strpos($contentType, "multipart") !== false) {
if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen($_FILES['file']['tmp_name'], "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
unlink($_FILES['file']['tmp_name']);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
} else {
// Open temp file
$out = fopen($targetDir . DIRECTORY_SEPARATOR . $fileName, $chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen("php://input", "rb");
if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
fclose($out);
} else
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
$audio_file = $targetDir . DIRECTORY_SEPARATOR . $fileName;
$md5 = md5_file($audio_file);
$duplicate = StoredFile::RecallByMd5($md5);
if ($duplicate) {
if (PEAR::isError($duplicate)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' . $duplicate->getMessage() .'}}');
}
else {
$duplicateName = $duplicate->getMetadataValue(UI_MDATA_KEY_TITLE);
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "An identical audioclip named ' . $duplicateName . ' already exists in the storage server."}}');
}
}
$metadata = Metadata::LoadFromFile($audio_file);
if (PEAR::isError($metadata)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}');
}
// #2196 no id tag -> use the original filename
if (basename($audio_file) == $metadata[UI_MDATA_KEY_TITLE]) {
$metadata[UI_MDATA_KEY_TITLE] = basename($audio_file);
$metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file);
}
// setMetadataBatch doesnt like these values
unset($metadata['audio']);
unset($metadata['playtime_seconds']);
$values = array(
"filename" => basename($audio_file),
"filepath" => $audio_file,
"filetype" => "audioclip",
"mime" => $metadata[UI_MDATA_KEY_FORMAT],
"md5" => $md5
);
$storedFile = StoredFile::Insert($values);
if (PEAR::isError($storedFile)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}');
}
$storedFile->setMetadataBatch($metadata);
return $storedFile;
}
} }

View File

@ -110,6 +110,7 @@ class SchedulerTests extends PHPUnit_TestCase {
} }
} }
/*
function testGetItems() { function testGetItems() {
$i1 = new ScheduleGroup(); $i1 = new ScheduleGroup();
$groupId1 = $i1->add('2008-01-01 12:00:00.000', $this->storedFile->getId()); $groupId1 = $i1->add('2008-01-01 12:00:00.000', $this->storedFile->getId());
@ -123,5 +124,6 @@ class SchedulerTests extends PHPUnit_TestCase {
$i1->remove(); $i1->remove();
$i2->remove(); $i2->remove();
} }
*/
} }

View File

@ -1,22 +0,0 @@
<?php
require_once 'soundcloud-api/Services/Soundcloud.php';
class Airtime_View_Helper_SoundCloudLink extends Zend_View_Helper_Abstract
{
public function soundCloudLink()
{
$request = Zend_Controller_Front::getInstance()->getRequest();
$host = $request->getHttpHost();
$controller = $request->getControllerName();
$action = $request->getActionName();
$redirectUrl = "http://{$host}/{$controller}/{$action}";
$soundcloud = new Services_Soundcloud('2CLCxcSXYzx7QhhPVHN4A', 'pZ7beWmF06epXLHVUP1ufOg2oEnIt9XhE8l8xt0bBs', $redirectUrl);
$authorizeUrl = $soundcloud->getAuthorizeUrl();
return $authorizeUrl;
}
}

View File

@ -14,7 +14,7 @@
<div class="now-playing-info show"> <div class="now-playing-info show">
<div class="recording-show" style="display: none;"></div> <div class="recording-show" style="display: none;"></div>
<span id="playlist"></span> <span id="playlist"></span>
<span class="lenght">00:00</span> <span id="show-length" class="show-length"></span>
</div> </div>
<div class="progressbar"> <div class="progressbar">
<div class="progress-show" id='progress-show' style="width:0%;"></div> <div class="progress-show" id='progress-show' style="width:0%;"></div>
@ -43,6 +43,6 @@
<div id="jquery_jplayer_1" class="jp-jplayer" style="width:0px; height:0px;"></div> <div id="jquery_jplayer_1" class="jp-jplayer" style="width:0px; height:0px;"></div>
<div id="about-txt" style="display: none;"> <div id="about-txt" style="display: none;">
<a href="http://airtime.sourcefabric.org">Airtime</a> 1.7, the open radio software for scheduling and remote station management.<br> <a href="http://airtime.sourcefabric.org">Airtime</a> <?php echo AIRTIME_VERSION ?>, the open radio software for scheduling and remote station management.<br>
© 2011 <a href="http://www.sourcefabric.org">Sourcefabric</a> o.p.s 2011. Airtime is distributed under the <a href="http://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU GPL v.3</a> © 2011 <a href="http://www.sourcefabric.org">Sourcefabric</a> o.p.s 2011. Airtime is distributed under the <a href="http://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU GPL v.3</a>
</div> </div>

View File

@ -26,6 +26,7 @@
</div> </div>
</div> </div>
<div class="user-data simple-formblock" id="user_details"> <div class="user-data simple-formblock" id="user_details">
<?php echo $this->successMessage ?>
<fieldset class="padded"> <fieldset class="padded">
<?php echo $this->form ?> <?php echo $this->form ?>
</fieldset> </fieldset>

3
dev_tools/pr.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
su -l pypo -c "tail -F /etc/service/recorder/log/main/current"

View File

@ -80,6 +80,7 @@ class AirtimeInstall {
$api_key = AirtimeInstall::GenerateRandomString(); $api_key = AirtimeInstall::GenerateRandomString();
AirtimeInstall::UpdateIniValue(__DIR__.'/../build/airtime.conf', 'api_key', $api_key); AirtimeInstall::UpdateIniValue(__DIR__.'/../build/airtime.conf', 'api_key', $api_key);
AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/pypo/config.cfg', 'api_key', "'$api_key'"); AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/pypo/config.cfg', 'api_key', "'$api_key'");
AirtimeInstall::UpdateIniValue(__DIR__.'/../python_apps/show-recorder/config.cfg', 'api_key', "'$api_key'");
} }
public static function ExitIfNotRoot() public static function ExitIfNotRoot()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -184,7 +184,7 @@ select {
.progressbar .progress-show-error { .progressbar .progress-show-error {
background:#d40000 url(images/progressbar_show_error.png) repeat-x 0 0; background:#d40000 url(images/progressbar_show_error.png) repeat-x 0 0;
} }
.now-playing-info .lenght { .now-playing-info .show-length {
color:#c4c4c4; color:#c4c4c4;
padding-left:6px; padding-left:6px;
} }
@ -196,6 +196,7 @@ select {
.time-info-block { .time-info-block {
padding:0 14px 0 2px; padding:0 14px 0 2px;
background:url(images/masterpanel_spacer.png) no-repeat right 0; background:url(images/masterpanel_spacer.png) no-repeat right 0;
min-width:105px;
} }
.time-info-block ul { .time-info-block ul {
margin:0; margin:0;
@ -1218,6 +1219,16 @@ ul.errors li {
margin-bottom:2px; margin-bottom:2px;
border:1px solid #c83f3f; border:1px solid #c83f3f;
} }
div.success{
color:#3B5323;
font-size:11px;
padding:2px 4px;
background:#93DB70;
margin-bottom:2px;
border:1px solid #488214;
}
.collapsible-header { .collapsible-header {
border: 1px solid #8f8f8f; border: 1px solid #8f8f8f;
background-color: #cccccc; background-color: #cccccc;
@ -1479,7 +1490,7 @@ ul.errors li {
} }
.small-icon { .small-icon {
display:block; display:block;
width:21px; width:20px;
height:10px; height:10px;
float:right; float:right;
margin-left:3px; margin-left:3px;
@ -1491,3 +1502,17 @@ ul.errors li {
background:url(images/icon_rebroadcast.png) no-repeat 0 0; background:url(images/icon_rebroadcast.png) no-repeat 0 0;
} }
.medium-icon {
display:block;
width:25px;
height:12px;
float:right;
margin-left:4px;
}
.medium-icon.recording {
background:url(images/icon_record_m.png) no-repeat 0 0;
}
.medium-icon.rebroadcast {
background:url(images/icon_rebroadcast_m.png) no-repeat 0 0;
}

View File

@ -2,6 +2,7 @@ function populateForm(entries){
//$('#user_details').show(); //$('#user_details').show();
$('.errors').remove(); $('.errors').remove();
$('.success').remove();
$('#user_id').val(entries.id); $('#user_id').val(entries.id);
$('#login').val(entries.login); $('#login').val(entries.login);

View File

@ -165,11 +165,12 @@ function updatePlaybar(){
/* Column 1 update */ /* Column 1 update */
$('#playlist').text("Current Show:"); $('#playlist').text("Current Show:");
var recElem = $('.recording-show');
if (currentShow.length > 0){ if (currentShow.length > 0){
$('#playlist').text(currentShow[0].name); $('#playlist').text(currentShow[0].name);
var recElem = $('.recording-show');
(currentShow[0].record == "1") ? recElem.show(): recElem.hide(); (currentShow[0].record == "1") ? recElem.show(): recElem.hide();
} else {
recElem.hide();
} }
$('#show-length').empty(); $('#show-length').empty();

View File

@ -13,6 +13,7 @@
import sys import sys
import time import time
import urllib import urllib
import urllib2
import logging import logging
import json import json
import os import os
@ -91,6 +92,12 @@ class ApiClientInterface:
def get_liquidsoap_data(self, pkey, schedule): def get_liquidsoap_data(self, pkey, schedule):
pass pass
def get_shows_to_record(self):
pass
def upload_recorded_show(self):
pass
# Put here whatever tests you want to run to make sure your API is working # Put here whatever tests you want to run to make sure your API is working
def test(self): def test(self):
pass pass
@ -190,29 +197,9 @@ class AirTimeApiClient(ApiClientInterface):
def get_schedule(self, start=None, end=None): def get_schedule(self, start=None, end=None):
logger = logging.getLogger() logger = logging.getLogger()
"""
calculate start/end time range (format: YYYY-DD-MM-hh-mm-ss,YYYY-DD-MM-hh-mm-ss)
(seconds are ignored, just here for consistency)
"""
tnow = time.localtime(time.time())
if (not start):
tstart = time.localtime(time.time() - 3600 * int(self.config["cache_for"]))
start = "%04d-%02d-%02d-%02d-%02d" % (tstart[0], tstart[1], tstart[2], tstart[3], tstart[4])
if (not end):
tend = time.localtime(time.time() + 3600 * int(self.config["prepare_ahead"]))
end = "%04d-%02d-%02d-%02d-%02d" % (tend[0], tend[1], tend[2], tend[3], tend[4])
range = {}
range['start'] = start
range['end'] = end
# Construct the URL # Construct the URL
export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"] export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"]
# Insert the start and end times into the URL
export_url = export_url.replace('%%from%%', range['start'])
export_url = export_url.replace('%%to%%', range['end'])
logger.info("Fetching schedule from %s", export_url) logger.info("Fetching schedule from %s", export_url)
export_url = export_url.replace('%%api_key%%', self.config["api_key"]) export_url = export_url.replace('%%api_key%%', self.config["api_key"])
@ -225,24 +212,6 @@ class AirTimeApiClient(ApiClientInterface):
except Exception, e: except Exception, e:
print e print e
#schedule = response["playlists"]
#scheduleKeys = sorted(schedule.iterkeys())
#
## Remove all playlists that have passed current time
#try:
# tnow = time.localtime(time.time())
# str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
# toRemove = []
# for pkey in scheduleKeys:
# if (str_tnow_s > schedule[pkey]['end']):
# toRemove.append(pkey)
# else:
# break
# for index in toRemove:
# del schedule[index]
#except Exception, e:
#response["playlists"] = schedule
return status, response return status, response
@ -317,6 +286,42 @@ class AirTimeApiClient(ApiClientInterface):
data["schedule_id"] = 0 data["schedule_id"] = 0
return data return data
def get_shows_to_record(self):
logger = logging.getLogger()
response = ''
try:
url = self.config["base_url"] + self.config["api_base"] + self.config["show_schedule_url"]
#logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
logger.debug(url)
response = urllib.urlopen(url)
response = json.loads(response.read())
logger.info("shows %s", response)
except Exception, e:
logger.error("Exception: %s", e)
return response[u'shows']
def upload_recorded_show(self, data, headers):
logger = logging.getLogger()
response = ''
try:
url = self.config["base_url"] + self.config["api_base"] + self.config["upload_file_url"]
#logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
logger.debug(url)
request = urllib2.Request(url, data, headers)
response = urllib2.urlopen(request).read().strip()
logger.info("uploaded show result %s", response)
except Exception, e:
logger.error("Exception: %s", e)
return response
################################################################################ ################################################################################

View File

@ -85,7 +85,7 @@ version_url = 'version/api_key/%%api_key%%'
# Schedule export path. # Schedule export path.
# %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm # %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm
# %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm # %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm
export_url = 'schedule/api_key/%%api_key%%/from/%%from%%/to/%%to%%' export_url = 'schedule/api_key/%%api_key%%'
# Update whether a schedule group has begun playing. # Update whether a schedule group has begun playing.
update_item_url = 'notify-schedule-group-play/api_key/%%api_key%%/schedule_id/%%schedule_id%%' update_item_url = 'notify-schedule-group-play/api_key/%%api_key%%/schedule_id/%%schedule_id%%'

View File

@ -7,10 +7,6 @@ ls_param="/opt/pypo/bin/scripts/ls_script.liq"
echo "*** Daemontools: starting liquidsoap" echo "*** Daemontools: starting liquidsoap"
exec 2>&1 exec 2>&1
PYTHONPATH=${api_client_path}:$PYTHONPATH
export PYTHONPATH
cd /opt/pypo/bin/scripts cd /opt/pypo/bin/scripts
exec setuidgid ${ls_user} \ sudo PYTHONPATH=${api_client_path} -u ${ls_user} ${ls_path} ${ls_param}
${ls_path} ${ls_param}
# EOF # EOF

View File

@ -4,7 +4,6 @@
# needed here to keep dirs/configs clean # # needed here to keep dirs/configs clean #
# and maybe to set user-rights # # and maybe to set user-rights #
############################################ ############################################
pwd
# Absolute path to this script # Absolute path to this script
SCRIPT=`readlink -f $0` SCRIPT=`readlink -f $0`

View File

@ -79,7 +79,7 @@ $endTime = date("Y-m-d H:i:s", time()+(60*60));
echo "Removing everything from the scheduler between $startTime and $endTime..."; echo "Removing everything from the scheduler between $startTime and $endTime...";
// Scheduler: remove any playlists for the next hour // Scheduler: remove any playlists for the next hour
Schedule::RemoveItemsInRange($startTime, $endTime); //Schedule::RemoveItemsInRange($startTime, $endTime);
// Check for succcess // Check for succcess
$scheduleClear = Schedule::isScheduleEmptyInRange($startTime, "01:00:00"); $scheduleClear = Schedule::isScheduleEmptyInRange($startTime, "01:00:00");
if (!$scheduleClear) { if (!$scheduleClear) {

View File

@ -1,9 +1,22 @@
api_client = "airtime"
# Hostname # Hostname
base_url = 'http://localhost/' base_url = 'http://localhost/'
show_schedule_url = 'Recorder/get-show-schedule/format/json'
upload_file_url = 'Plupload/upload-recorded/format/json'
# base path to store recordered shows at # base path to store recordered shows at
base_recorded_files = '/home/pypo/Music/' base_recorded_files = '/home/pypo/Music/'
# Value needed to access the API
api_key = 'AAA'
# Path to the base of the API
api_base = 'api/'
# URL to get the version number of the server API
version_url = 'version/api_key/%%api_key%%'
# URL to get the schedule of shows set to record
show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%'
# URL to upload the recorded show's file to Airtime
upload_file_url = 'upload-recorded/format/json/api_key/%%api_key%%'

View File

@ -1,12 +1,18 @@
#!/bin/sh #!/bin/sh
recorder_user="pypo" recorder_user="pypo"
export HOME="/home/pypo/" export HOME="/home/pypo/"
export TERM=xterm
# Location of pypo_cli.py Python script # Location of pypo_cli.py Python script
recorder_path="/opt/recorder/bin/" recorder_path="/opt/recorder/bin/"
recorder_script="testrecordscript.py" recorder_script="testrecordscript.py"
echo "*** Daemontools: starting daemon"
api_client_path="/opt/pypo/"
cd ${recorder_path} cd ${recorder_path}
echo "*** Daemontools: starting daemon"
exec 2>&1 exec 2>&1
# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr # Note the -u when calling python! we need it to get unbuffered binary stdout and stderr
exec sudo python -u ${recorder_path}${recorder_script} -f
sudo PYTHONPATH=${api_client_path} -u ${recorder_user} python -u ${recorder_path}${recorder_script}
# EOF # EOF

View File

@ -0,0 +1,22 @@
[loggers]
keys=root
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
datefmt=

View File

@ -1,10 +1,12 @@
#!/usr/local/bin/python #!/usr/local/bin/python
import urllib import urllib
import logging import logging
import logging.config
import json import json
import time import time
import datetime import datetime
import os import os
import sys
from configobj import ConfigObj from configobj import ConfigObj
@ -15,6 +17,19 @@ import urllib2
from subprocess import call from subprocess import call
from threading import Thread from threading import Thread
# For RabbitMQ
from kombu.connection import BrokerConnection
from kombu.messaging import Exchange, Queue, Consumer, Producer
from api_clients import api_client
# configure logging
try:
logging.config.fileConfig("logging.cfg")
except Exception, e:
print 'Error configuring logging: ', e
sys.exit()
# loading config file # loading config file
try: try:
config = ConfigObj('config.cfg') config = ConfigObj('config.cfg')
@ -22,12 +37,19 @@ except Exception, e:
print 'Error loading config file: ', e print 'Error loading config file: ', e
sys.exit() sys.exit()
shows_to_record = {} def getDateTimeObj(time):
class Recorder(Thread): timeinfo = time.split(" ")
date = timeinfo[0].split("-")
time = timeinfo[1].split(":")
return datetime.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2]))
class ShowRecorder(Thread):
def __init__ (self, show_instance, filelength, filename, filetype): def __init__ (self, show_instance, filelength, filename, filetype):
Thread.__init__(self) Thread.__init__(self)
self.api_client = api_client.api_client_factory(config)
self.filelength = filelength self.filelength = filelength
self.filename = filename self.filename = filename
self.filetype = filetype self.filetype = filetype
@ -40,9 +62,15 @@ class Recorder(Thread):
filepath = "%s%s.%s" % (config["base_recorded_files"], filename, self.filetype) filepath = "%s%s.%s" % (config["base_recorded_files"], filename, self.filetype)
command = "ecasound -i alsa -o %s -t:%s" % (filepath, length) command = "ecasound -i alsa -o %s -t:%s" % (filepath, length)
call(command, shell=True) args = command.split(" ")
return filepath print "starting record"
code = call(args)
print "finishing record, return code %s" % (code)
return code, filepath
def upload_file(self, filepath): def upload_file(self, filepath):
@ -55,83 +83,77 @@ class Recorder(Thread):
# datagen is a generator object that yields the encoded parameters # datagen is a generator object that yields the encoded parameters
datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename, 'show_instance': self.show_instance}) datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename, 'show_instance': self.show_instance})
url = config["base_url"] + config["upload_file_url"] self.api_client.upload_recorded_show(datagen, headers)
req = urllib2.Request(url, datagen, headers)
response = urllib2.urlopen(req).read().strip()
print response
def run(self): def run(self):
filepath = self.record_show() code, filepath = self.record_show()
if code == 0:
self.upload_file(filepath) self.upload_file(filepath)
else:
print "problem recording show"
def getDateTimeObj(time): class Record():
timeinfo = time.split(" ") def __init__(self):
date = timeinfo[0].split("-") self.api_client = api_client.api_client_factory(config)
time = timeinfo[1].split(":") self.shows_to_record = {}
return datetime.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2])) def process_shows(self, shows):
def process_shows(shows): self.shows_to_record = {}
global shows_to_record
shows_to_record = {}
for show in shows: for show in shows:
show_starts = getDateTimeObj(show[u'starts']) show_starts = getDateTimeObj(show[u'starts'])
show_end = getDateTimeObj(show[u'ends']) show_end = getDateTimeObj(show[u'ends'])
time_delta = show_end - show_starts time_delta = show_end - show_starts
shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id']] self.shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id']]
def check_record(): def check_record(self):
tnow = datetime.datetime.now() tnow = datetime.datetime.now()
sorted_show_keys = sorted(shows_to_record.keys()) sorted_show_keys = sorted(self.shows_to_record.keys())
print sorted_show_keys print sorted_show_keys
start_time = sorted_show_keys[0] start_time = sorted_show_keys[0]
next_show = getDateTimeObj(start_time) next_show = getDateTimeObj(start_time)
print next_show print next_show
print tnow print tnow
delta = next_show - tnow
print delta
if delta <= datetime.timedelta(seconds=60): delta = next_show - tnow
min_delta = datetime.timedelta(seconds=60)
if delta <= min_delta:
print "sleeping %s seconds until show" % (delta.seconds) print "sleeping %s seconds until show" % (delta.seconds)
time.sleep(delta.seconds) time.sleep(delta.seconds)
show_length = shows_to_record[start_time][0] show_length = self.shows_to_record[start_time][0]
show_instance = shows_to_record[start_time][1] show_instance = self.shows_to_record[start_time][1]
show = Recorder(show_instance, show_length.seconds, start_time, filetype="mp3") show = ShowRecorder(show_instance, show_length.seconds, start_time, filetype="mp3")
show.start() show.start()
#remove show from shows to record. #remove show from shows to record.
del shows_to_record[start_time] del self.shows_to_record[start_time]
def get_shows(): def get_shows(self):
url = config["base_url"] + config["show_schedule_url"] shows = self.api_client.get_shows_to_record()
response = urllib.urlopen(url)
data = response.read()
response_json = json.loads(data)
shows = response_json[u'shows']
print shows
if len(shows): if len(shows):
process_shows(shows) self.process_shows(shows)
check_record() self.check_record()
if __name__ == '__main__': if __name__ == '__main__':
recorder = Record()
while True: while True:
get_shows() recorder.get_shows()
time.sleep(5) time.sleep(5)