1053 lines
43 KiB
PHP
1053 lines
43 KiB
PHP
<?php
|
|
|
|
class ApiController extends Zend_Controller_Action
|
|
{
|
|
|
|
public function init()
|
|
{
|
|
$ignoreAuth = array("live-info", "week-info");
|
|
|
|
$params = $this->getRequest()->getParams();
|
|
if (!in_array($params['action'], $ignoreAuth)) {
|
|
$this->checkAuth();
|
|
}
|
|
/* Initialize action controller here */
|
|
$context = $this->_helper->getHelper('contextSwitch');
|
|
$context->addActionContext('version' , 'json')
|
|
->addActionContext('recorded-shows' , 'json')
|
|
->addActionContext('calendar-init' , 'json')
|
|
->addActionContext('upload-file' , 'json')
|
|
->addActionContext('upload-recorded' , 'json')
|
|
->addActionContext('media-monitor-setup' , 'json')
|
|
->addActionContext('media-item-status' , 'json')
|
|
->addActionContext('reload-metadata' , 'json')
|
|
->addActionContext('list-all-files' , 'json')
|
|
->addActionContext('list-all-watched-dirs' , 'json')
|
|
->addActionContext('add-watched-dir' , 'json')
|
|
->addActionContext('remove-watched-dir' , 'json')
|
|
->addActionContext('set-storage-dir' , 'json')
|
|
->addActionContext('get-stream-setting' , 'json')
|
|
->addActionContext('status' , 'json')
|
|
->addActionContext('register-component' , 'json')
|
|
->addActionContext('update-liquidsoap-status' , 'json')
|
|
->addActionContext('live-chat' , 'json')
|
|
->addActionContext('update-file-system-mount' , 'json')
|
|
->addActionContext('handle-watched-dir-missing' , 'json')
|
|
->addActionContext('rabbitmq-do-push' , 'json')
|
|
->addActionContext('check-live-stream-auth' , 'json')
|
|
->addActionContext('update-source-status' , 'json')
|
|
->addActionContext('get-bootstrap-info' , 'json')
|
|
->addActionContext('get-files-without-replay-gain' , 'json')
|
|
->addActionContext('get-files-without-silan-value' , 'json')
|
|
->addActionContext('reload-metadata-group' , 'json')
|
|
->addActionContext('notify-webstream-data' , 'json')
|
|
->addActionContext('get-stream-parameters' , 'json')
|
|
->addActionContext('push-stream-stats' , 'json')
|
|
->addActionContext('update-stream-setting-table' , 'json')
|
|
->addActionContext('update-replay-gain-value' , 'json')
|
|
->addActionContext('update-cue-values-by-silan' , 'json')
|
|
->initContext();
|
|
}
|
|
|
|
public function checkAuth()
|
|
{
|
|
$CC_CONFIG = Config::getConfig();
|
|
$api_key = $this->_getParam('api_key');
|
|
|
|
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
|
|
is_null(Zend_Auth::getInstance()->getStorage()->read())) {
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
print _('You are not allowed to access this resource.');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function versionAction()
|
|
{
|
|
$this->_helper->json->sendJson( array(
|
|
"version" => Application_Model_Preference::GetAirtimeVersion()));
|
|
}
|
|
|
|
/**
|
|
* Sets up and send init values used in the Calendar.
|
|
* This is only being used by schedule.js at the moment.
|
|
*/
|
|
public function calendarInitAction()
|
|
{
|
|
if (is_null(Zend_Auth::getInstance()->getStorage()->read())) {
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
print _('You are not allowed to access this resource.');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->view->calendarInit = array(
|
|
"timestamp" => time(),
|
|
"timezoneOffset" => date("Z"),
|
|
"timeScale" => Application_Model_Preference::GetCalendarTimeScale(),
|
|
"timeInterval" => Application_Model_Preference::GetCalendarTimeInterval(),
|
|
"weekStartDay" => Application_Model_Preference::GetWeekStartDay()
|
|
);
|
|
|
|
$this->_helper->json->sendJson(array());
|
|
}
|
|
|
|
/**
|
|
* Allows remote client to download requested media file.
|
|
*
|
|
* @return void
|
|
*
|
|
*/
|
|
public function getMediaAction()
|
|
{
|
|
$fileId = $this->_getParam("file");
|
|
|
|
$media = Application_Model_StoredFile::RecallById($fileId);
|
|
if ($media != null) {
|
|
|
|
$filepath = $media->getFilePath();
|
|
// Make sure we don't have some wrong result beecause of caching
|
|
clearstatcache();
|
|
if (is_file($filepath)) {
|
|
$full_path = $media->getPropelOrm()->getDbFilepath();
|
|
|
|
$file_base_name = strrchr($full_path, '/');
|
|
/* If $full_path does not contain a '/', strrchr will return false,
|
|
* in which case we can use $full_path as the base name.
|
|
*/
|
|
if (!$file_base_name) {
|
|
$file_base_name = $full_path;
|
|
} else {
|
|
$file_base_name = substr($file_base_name, 1);
|
|
}
|
|
|
|
//Download user left clicks a track and selects Download.
|
|
if ("true" == $this->_getParam('download')) {
|
|
//path_info breaks up a file path into seperate pieces of informaiton.
|
|
//We just want the basename which is the file name with the path
|
|
//information stripped away. We are using Content-Disposition to specify
|
|
//to the browser what name the file should be saved as.
|
|
header('Content-Disposition: attachment; filename="'.$file_base_name.'"');
|
|
} else {
|
|
//user clicks play button for track and downloads it.
|
|
header('Content-Disposition: inline; filename="'.$file_base_name.'"');
|
|
}
|
|
|
|
$this->smartReadFile($filepath, $media->getPropelOrm()->getDbMime());
|
|
exit;
|
|
} else {
|
|
header ("HTTP/1.1 404 Not Found");
|
|
}
|
|
}
|
|
|
|
$this->_helper->json->sendJson(array());
|
|
}
|
|
|
|
/**
|
|
* Reads the requested portion of a file and sends its contents to the client with the appropriate headers.
|
|
*
|
|
* This HTTP_RANGE compatible read file function is necessary for allowing streaming media to be skipped around in.
|
|
*
|
|
* @param string $location
|
|
* @param string $mimeType
|
|
* @return void
|
|
*
|
|
* @link https://groups.google.com/d/msg/jplayer/nSM2UmnSKKA/Hu76jDZS4xcJ
|
|
* @link http://php.net/manual/en/function.readfile.php#86244
|
|
*/
|
|
public function smartReadFile($location, $mimeType = 'audio/mp3')
|
|
{
|
|
$size= filesize($location);
|
|
$time= date('r', filemtime($location));
|
|
|
|
$fm = @fopen($location, 'rb');
|
|
if (!$fm) {
|
|
header ("HTTP/1.1 505 Internal server error");
|
|
|
|
return;
|
|
}
|
|
|
|
$begin = 0;
|
|
$end = $size - 1;
|
|
|
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
|
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
|
|
$begin = intval($matches[1]);
|
|
if (!empty($matches[2])) {
|
|
$end = intval($matches[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
|
header('HTTP/1.1 206 Partial Content');
|
|
} else {
|
|
header('HTTP/1.1 200 OK');
|
|
}
|
|
header("Content-Type: $mimeType");
|
|
header('Cache-Control: public, must-revalidate, max-age=0');
|
|
header('Pragma: no-cache');
|
|
header('Accept-Ranges: bytes');
|
|
header('Content-Length:' . (($end - $begin) + 1));
|
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
|
header("Content-Range: bytes $begin-$end/$size");
|
|
}
|
|
header("Content-Transfer-Encoding: binary");
|
|
header("Last-Modified: $time");
|
|
|
|
//We can have multiple levels of output buffering. Need to
|
|
//keep looping until all have been disabled!!!
|
|
//http://www.php.net/manual/en/function.ob-end-flush.php
|
|
while (@ob_end_flush());
|
|
|
|
$cur = $begin;
|
|
fseek($fm, $begin, 0);
|
|
|
|
while (!feof($fm) && $cur <= $end && (connection_status() == 0)) {
|
|
echo fread($fm, min(1024 * 16, ($end - $cur) + 1));
|
|
$cur += 1024 * 16;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the currently playing show as well as upcoming shows.
|
|
* Number of shows returned and the time interval in which to
|
|
* get the next shows can be configured as GET parameters.
|
|
*
|
|
* TODO: in the future, make interval length a parameter instead of hardcode to 48
|
|
*
|
|
* Possible parameters:
|
|
* type - Can have values of "endofday" or "interval". If set to "endofday",
|
|
* the function will retrieve shows from now to end of day.
|
|
* If set to "interval", shows in the next 48 hours will be retrived.
|
|
* Default is "interval".
|
|
* limit - How many shows to retrieve
|
|
* Default is "5".
|
|
*/
|
|
public function liveInfoAction()
|
|
{
|
|
if (Application_Model_Preference::GetAllow3rdPartyApi()) {
|
|
// disable the view and the layout
|
|
$this->view->layout()->disableLayout();
|
|
$this->_helper->viewRenderer->setNoRender(true);
|
|
|
|
$date = new Application_Common_DateHelper;
|
|
$utcTimeNow = $date->getUtcTimestamp();
|
|
$utcTimeEnd = ""; // if empty, getNextShows will use interval instead of end of day
|
|
|
|
$request = $this->getRequest();
|
|
$type = $request->getParam('type');
|
|
/* This is some *extremely* lazy programming that needs to bi fixed. For some reason
|
|
* we are using two entirely different codepaths for very similar functionality (type = endofday
|
|
* vs type = interval). Needs to be fixed for 2.3 - MK */
|
|
if ($type == "endofday") {
|
|
$limit = $request->getParam('limit');
|
|
if ($limit == "" || !is_numeric($limit)) {
|
|
$limit = "5";
|
|
}
|
|
|
|
// make getNextShows use end of day
|
|
$utcTimeEnd = Application_Common_DateHelper::GetDayEndTimestampInUtc();
|
|
$result = array("env"=>APPLICATION_ENV,
|
|
"schedulerTime"=>gmdate("Y-m-d H:i:s"),
|
|
"currentShow"=>Application_Model_Show::getCurrentShow($utcTimeNow),
|
|
"nextShow"=>Application_Model_Show::getNextShows($utcTimeNow, $limit, $utcTimeEnd)
|
|
);
|
|
// XSS exploit prevention
|
|
foreach ($result["currentShow"] as &$current) {
|
|
$current["name"] = htmlspecialchars($current["name"]);
|
|
}
|
|
foreach ($result["nextShow"] as &$next) {
|
|
$next["name"] = htmlspecialchars($next["name"]);
|
|
}
|
|
|
|
Application_Model_Show::convertToLocalTimeZone($result["currentShow"],
|
|
array("starts", "ends", "start_timestamp", "end_timestamp"));
|
|
Application_Model_Show::convertToLocalTimeZone($result["nextShow"],
|
|
array("starts", "ends", "start_timestamp", "end_timestamp"));
|
|
} else {
|
|
$result = Application_Model_Schedule::GetPlayOrderRange();
|
|
|
|
// XSS exploit prevention
|
|
$result["previous"]["name"] = htmlspecialchars($result["previous"]["name"]);
|
|
$result["current"]["name"] = htmlspecialchars($result["current"]["name"]);
|
|
$result["next"]["name"] = htmlspecialchars($result["next"]["name"]);
|
|
foreach ($result["currentShow"] as &$current) {
|
|
$current["name"] = htmlspecialchars($current["name"]);
|
|
}
|
|
foreach ($result["nextShow"] as &$next) {
|
|
$next["name"] = htmlspecialchars($next["name"]);
|
|
}
|
|
|
|
//Convert from UTC to localtime for Web Browser.
|
|
Application_Model_Show::ConvertToLocalTimeZone($result["currentShow"],
|
|
array("starts", "ends", "start_timestamp", "end_timestamp"));
|
|
Application_Model_Show::ConvertToLocalTimeZone($result["nextShow"],
|
|
array("starts", "ends", "start_timestamp", "end_timestamp"));
|
|
}
|
|
|
|
//used by caller to determine if the airtime they are running or widgets in use is out of date.
|
|
$result['AIRTIME_API_VERSION'] = AIRTIME_API_VERSION;
|
|
header("Content-type: text/javascript");
|
|
|
|
// If a callback is not given, then just provide the raw JSON.
|
|
echo isset($_GET['callback']) ? $_GET['callback'].'('.json_encode($result).')' : json_encode($result);
|
|
} else {
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
print _('You are not allowed to access this resource. ');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function weekInfoAction()
|
|
{
|
|
if (Application_Model_Preference::GetAllow3rdPartyApi()) {
|
|
// disable the view and the layout
|
|
$this->view->layout()->disableLayout();
|
|
$this->_helper->viewRenderer->setNoRender(true);
|
|
|
|
$date = new Application_Common_DateHelper;
|
|
$dayStart = $date->getWeekStartDate();
|
|
$utcDayStart = Application_Common_DateHelper::ConvertToUtcDateTimeString($dayStart);
|
|
|
|
$dow = array("monday", "tuesday", "wednesday", "thursday", "friday",
|
|
"saturday", "sunday");
|
|
|
|
$result = array();
|
|
for ($i=0; $i<7; $i++) {
|
|
$utcDayEnd = Application_Common_DateHelper::GetDayEndTimestamp($utcDayStart);
|
|
$shows = Application_Model_Show::getNextShows($utcDayStart, "ALL", $utcDayEnd);
|
|
$utcDayStart = $utcDayEnd;
|
|
|
|
Application_Model_Show::convertToLocalTimeZone($shows,
|
|
array("starts", "ends", "start_timestamp",
|
|
"end_timestamp"));
|
|
|
|
$result[$dow[$i]] = $shows;
|
|
}
|
|
|
|
// XSS exploit prevention
|
|
foreach ($dow as $d) {
|
|
foreach ($result[$d] as &$show) {
|
|
$show["name"] = htmlspecialchars($show["name"]);
|
|
$show["url"] = htmlspecialchars($show["url"]);
|
|
}
|
|
}
|
|
|
|
//used by caller to determine if the airtime they are running or widgets in use is out of date.
|
|
$result['AIRTIME_API_VERSION'] = AIRTIME_API_VERSION;
|
|
header("Content-type: text/javascript");
|
|
// If a callback is not given, then just provide the raw JSON.
|
|
echo isset($_GET['callback']) ? $_GET['callback'].'('.json_encode($result).')' : json_encode($result);
|
|
} else {
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
print _('You are not allowed to access this resource. ');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
public function scheduleAction()
|
|
{
|
|
$data = Application_Model_Schedule::getSchedule();
|
|
header("Content-Type: application/json");
|
|
|
|
$data = json_encode($data, JSON_FORCE_OBJECT);
|
|
$this->_helper->json->sendJson($data, false, true);
|
|
}
|
|
|
|
public function notifyMediaItemStartPlayAction()
|
|
{
|
|
$media_id = $this->_getParam("media_id");
|
|
Logging::debug("Received notification of new media item start: $media_id");
|
|
Application_Model_Schedule::UpdateMediaPlayedStatus($media_id);
|
|
|
|
//set a 'last played' timestamp for media item
|
|
//needed for smart blocks
|
|
try {
|
|
$mediaType = Application_Model_Schedule::GetType($media_id);
|
|
if ($mediaType == 'file') {
|
|
$file_id = Application_Model_Schedule::GetFileId($media_id);
|
|
if (!is_null($file_id)) {
|
|
//we are dealing with a file not a stream
|
|
$file = Application_Model_StoredFile::RecallById($file_id);
|
|
$now = new DateTime("now", new DateTimeZone("UTC"));
|
|
$file->setLastPlayedTime($now);
|
|
}
|
|
} else {
|
|
// webstream
|
|
$stream_id = Application_Model_Schedule::GetStreamId($media_id);
|
|
if (!is_null($stream_id)) {
|
|
$webStream = new Application_Model_Webstream($stream_id);
|
|
$now = new DateTime("now", new DateTimeZone("UTC"));
|
|
$webStream->setLastPlayed($now);
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
Logging::info($e);
|
|
}
|
|
|
|
$this->_helper->json->sendJson(array("status"=>1, "message"=>""));
|
|
}
|
|
|
|
public function recordedShowsAction()
|
|
{
|
|
$today_timestamp = date("Y-m-d H:i:s");
|
|
$now = new DateTime($today_timestamp);
|
|
$end_timestamp = $now->add(new DateInterval("PT2H"));
|
|
$end_timestamp = $end_timestamp->format("Y-m-d H:i:s");
|
|
|
|
$this->view->shows =
|
|
Application_Model_Show::getShows(
|
|
Application_Common_DateHelper::ConvertToUtcDateTime($today_timestamp, date_default_timezone_get()),
|
|
Application_Common_DateHelper::ConvertToUtcDateTime($end_timestamp, date_default_timezone_get()),
|
|
$onlyRecord = true);
|
|
|
|
$this->view->is_recording = false;
|
|
$this->view->server_timezone = Application_Model_Preference::GetTimezone();
|
|
|
|
$rows = Application_Model_Show::getCurrentShow($today_timestamp);
|
|
Application_Model_Show::convertToLocalTimeZone($rows, array("starts", "ends", "start_timestamp", "end_timestamp"));
|
|
|
|
if (count($rows) > 0) {
|
|
$this->view->is_recording = ($rows[0]['record'] == 1);
|
|
}
|
|
}
|
|
|
|
public function uploadFileAction()
|
|
{
|
|
$upload_dir = ini_get("upload_tmp_dir");
|
|
$tempFilePath = Application_Model_StoredFile::uploadFile($upload_dir);
|
|
$tempFileName = basename($tempFilePath);
|
|
|
|
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
|
|
$result = Application_Model_StoredFile::copyFileToStor($upload_dir, $fileName, $tempFileName);
|
|
|
|
if (!is_null($result)) {
|
|
$this->_helper->json->sendJson(
|
|
array("jsonrpc" => "2.0", "error" => array("code" => $result['code'], "message" => $result['message']))
|
|
);
|
|
}
|
|
}
|
|
|
|
public function uploadRecordedAction()
|
|
{
|
|
$show_instance_id = $this->_getParam('showinstanceid');
|
|
$file_id = $this->_getParam('fileid');
|
|
$this->view->fileid = $file_id;
|
|
$this->view->showinstanceid = $show_instance_id;
|
|
$this->uploadRecordedActionParam($show_instance_id, $file_id);
|
|
}
|
|
|
|
// The paramterized version of the uploadRecordedAction controller.
|
|
// We want this controller's action to be invokable from other
|
|
// controllers instead being of only through http
|
|
public function uploadRecordedActionParam($show_instance_id, $file_id)
|
|
{
|
|
$showCanceled = false;
|
|
$file = Application_Model_StoredFile::RecallById($file_id);
|
|
//$show_instance = $this->_getParam('show_instance');
|
|
|
|
try {
|
|
$show_inst = new Application_Model_ShowInstance($show_instance_id);
|
|
$show_inst->setRecordedFile($file_id);
|
|
//$show_start_time = Application_Common_DateHelper::ConvertToLocalDateTimeString($show_inst->getShowInstanceStart());
|
|
|
|
} catch (Exception $e) {
|
|
//we've reached here probably because the show was
|
|
//cancelled, and therefore the show instance does not exist
|
|
//anymore (ShowInstance constructor threw this error). We've
|
|
//done all we can do (upload the file and put it in the
|
|
//library), now lets just return.
|
|
$showCanceled = true;
|
|
}
|
|
|
|
// TODO : the following is inefficient because it calls save on both
|
|
// fields
|
|
$file->setMetadataValue('MDATA_KEY_CREATOR', "Airtime Show Recorder");
|
|
$file->setMetadataValue('MDATA_KEY_TRACKNUMBER', $show_instance_id);
|
|
|
|
if (!$showCanceled && Application_Model_Preference::GetAutoUploadRecordedShowToSoundcloud()) {
|
|
$id = $file->getId();
|
|
Application_Model_Soundcloud::uploadSoundcloud($id);
|
|
}
|
|
}
|
|
|
|
public function mediaMonitorSetupAction()
|
|
{
|
|
$this->view->stor = Application_Model_MusicDir::getStorDir()->getDirectory();
|
|
|
|
$watchedDirs = Application_Model_MusicDir::getWatchedDirs();
|
|
$watchedDirsPath = array();
|
|
foreach ($watchedDirs as $wd) {
|
|
$watchedDirsPath[] = $wd->getDirectory();
|
|
}
|
|
$this->view->watched_dirs = $watchedDirsPath;
|
|
}
|
|
|
|
public function dispatchMetadata($md, $mode)
|
|
{
|
|
$return_hash = array();
|
|
Application_Model_Preference::SetImportTimestamp();
|
|
|
|
$con = Propel::getConnection(CcFilesPeer::DATABASE_NAME);
|
|
$con->beginTransaction();
|
|
try {
|
|
// create also modifies the file if it exists
|
|
if ($mode == "create") {
|
|
$filepath = $md['MDATA_KEY_FILEPATH'];
|
|
$filepath = Application_Common_OsPath::normpath($filepath);
|
|
$file = Application_Model_StoredFile::RecallByFilepath($filepath, $con);
|
|
if (is_null($file)) {
|
|
$file = Application_Model_StoredFile::Insert($md, $con);
|
|
} else {
|
|
// If the file already exists we will update and make sure that
|
|
// it's marked as 'exists'.
|
|
$file->setFileExistsFlag(true);
|
|
$file->setFileHiddenFlag(false);
|
|
$file->setMetadata($md);
|
|
}
|
|
if ($md['is_record'] != 0) {
|
|
$this->uploadRecordedActionParam($md['MDATA_KEY_TRACKNUMBER'], $file->getId());
|
|
}
|
|
|
|
} elseif ($mode == "modify") {
|
|
$filepath = $md['MDATA_KEY_FILEPATH'];
|
|
$file = Application_Model_StoredFile::RecallByFilepath($filepath, $con);
|
|
|
|
//File is not in database anymore.
|
|
if (is_null($file)) {
|
|
$return_hash['error'] = _("File does not exist in Airtime.");
|
|
|
|
return $return_hash;
|
|
}
|
|
//Updating a metadata change.
|
|
else {
|
|
$file->setMetadata($md);
|
|
}
|
|
} elseif ($mode == "moved") {
|
|
$file = Application_Model_StoredFile::RecallByFilepath(
|
|
$md['MDATA_KEY_ORIGINAL_PATH'], $con);
|
|
|
|
if (is_null($file)) {
|
|
$return_hash['error'] = _('File does not exist in Airtime');
|
|
} else {
|
|
$filepath = $md['MDATA_KEY_FILEPATH'];
|
|
//$filepath = str_replace("\\", "", $filepath);
|
|
$file->setFilePath($filepath);
|
|
}
|
|
} elseif ($mode == "delete") {
|
|
$filepath = $md['MDATA_KEY_FILEPATH'];
|
|
$filepath = str_replace("\\", "", $filepath);
|
|
$file = Application_Model_StoredFile::RecallByFilepath($filepath, $con);
|
|
|
|
if (is_null($file)) {
|
|
$return_hash['error'] = _("File doesn't exist in Airtime.");
|
|
Logging::warn("Attempt to delete file that doesn't exist.
|
|
Path: '$filepath'");
|
|
|
|
return $return_hash;
|
|
} else {
|
|
$file->deleteByMediaMonitor();
|
|
}
|
|
} elseif ($mode == "delete_dir") {
|
|
$filepath = $md['MDATA_KEY_FILEPATH'];
|
|
//$filepath = str_replace("\\", "", $filepath);
|
|
$files = Application_Model_StoredFile::RecallByPartialFilepath($filepath, $con);
|
|
|
|
foreach ($files as $file) {
|
|
$file->deleteByMediaMonitor();
|
|
}
|
|
$return_hash['success'] = 1;
|
|
|
|
return $return_hash;
|
|
}
|
|
|
|
$return_hash['fileid'] = is_null($file) ? '-1' : $file->getId();
|
|
$con->commit();
|
|
} catch (Exception $e) {
|
|
Logging::warn("rolling back");
|
|
Logging::warn($e->getMessage());
|
|
$con->rollback();
|
|
$return_hash['error'] = $e->getMessage();
|
|
}
|
|
return $return_hash;
|
|
}
|
|
|
|
public function reloadMetadataGroupAction()
|
|
{
|
|
// extract all file metadata params from the request.
|
|
// The value is a json encoded hash that has all the information related to this action
|
|
// The key(mdXXX) does not have any meaning as of yet but it could potentially correspond
|
|
// to some unique id.
|
|
$request = $this->getRequest();
|
|
$responses = array();
|
|
$params = $request->getParams();
|
|
$valid_modes = array('delete_dir', 'delete', 'moved', 'modify', 'create');
|
|
foreach ($params as $k => $raw_json) {
|
|
// Valid requests must start with mdXXX where XXX represents at
|
|
// least 1 digit
|
|
if ( !preg_match('/^md\d+$/', $k) ) { continue; }
|
|
$info_json = json_decode($raw_json, $assoc = true);
|
|
|
|
// Log invalid requests
|
|
if ( !array_key_exists('mode', $info_json) ) {
|
|
Logging::info("Received bad request(key=$k), no 'mode' parameter. Bad request is:");
|
|
Logging::info( $info_json );
|
|
array_push( $responses, array(
|
|
'error' => _("Bad request. no 'mode' parameter passed."),
|
|
'key' => $k));
|
|
continue;
|
|
} elseif ( !in_array($info_json['mode'], $valid_modes) ) {
|
|
// A request still has a chance of being invalid even if it
|
|
// exists but it's validated by $valid_modes array
|
|
$mode = $info_json['mode'];
|
|
Logging::info("Received bad request(key=$k). 'mode' parameter was invalid with value: '$mode'. Request:");
|
|
Logging::info( $info_json );
|
|
array_push( $responses, array(
|
|
'error' => _("Bad request. 'mode' parameter is invalid"),
|
|
'key' => $k,
|
|
'mode' => $mode ) );
|
|
continue;
|
|
}
|
|
// Removing 'mode' key from $info_json might not be necessary...
|
|
$mode = $info_json['mode'];
|
|
unset( $info_json['mode'] );
|
|
try {
|
|
$response = $this->dispatchMetadata($info_json, $mode);
|
|
} catch (Exception $e) {
|
|
Logging::warn($e->getMessage());
|
|
Logging::warn(gettype($e));
|
|
}
|
|
// We tack on the 'key' back to every request in case the would like to associate
|
|
// his requests with particular responses
|
|
$response['key'] = $k;
|
|
array_push($responses, $response);
|
|
}
|
|
$this->_helper->json->sendJson($responses);
|
|
}
|
|
|
|
public function listAllFilesAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$dir_id = $request->getParam('dir_id');
|
|
$all = $request->getParam('all');
|
|
|
|
$this->view->files =
|
|
Application_Model_StoredFile::listAllFiles($dir_id,$all);
|
|
}
|
|
|
|
public function listAllWatchedDirsAction()
|
|
{
|
|
$result = array();
|
|
|
|
$arrWatchedDirs = Application_Model_MusicDir::getWatchedDirs();
|
|
$storDir = Application_Model_MusicDir::getStorDir();
|
|
|
|
$result[$storDir->getId()] = $storDir->getDirectory();
|
|
|
|
foreach ($arrWatchedDirs as $watchedDir) {
|
|
$result[$watchedDir->getId()] = $watchedDir->getDirectory();
|
|
}
|
|
|
|
$this->view->dirs = $result;
|
|
}
|
|
|
|
public function addWatchedDirAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$path = base64_decode($request->getParam('path'));
|
|
|
|
$this->view->msg = Application_Model_MusicDir::addWatchedDir($path);
|
|
}
|
|
|
|
public function removeWatchedDirAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$path = base64_decode($request->getParam('path'));
|
|
|
|
$this->view->msg = Application_Model_MusicDir::removeWatchedDir($path);
|
|
}
|
|
|
|
public function setStorageDirAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$path = base64_decode($request->getParam('path'));
|
|
|
|
$this->view->msg = Application_Model_MusicDir::setStorDir($path);
|
|
}
|
|
|
|
public function getStreamSettingAction()
|
|
{
|
|
$info = Application_Model_StreamSetting::getStreamSetting();
|
|
$this->view->msg = $info;
|
|
}
|
|
|
|
public function statusAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$getDiskInfo = $request->getParam('diskinfo') == "true";
|
|
|
|
$status = array(
|
|
"platform"=>Application_Model_Systemstatus::GetPlatformInfo(),
|
|
"airtime_version"=>Application_Model_Preference::GetAirtimeVersion(),
|
|
"services"=>array(
|
|
"pypo"=>Application_Model_Systemstatus::GetPypoStatus(),
|
|
"liquidsoap"=>Application_Model_Systemstatus::GetLiquidsoapStatus(),
|
|
"media_monitor"=>Application_Model_Systemstatus::GetMediaMonitorStatus()
|
|
)
|
|
);
|
|
|
|
if ($getDiskInfo) {
|
|
$status["partitions"] = Application_Model_Systemstatus::GetDiskInfo();
|
|
}
|
|
|
|
$this->view->status = $status;
|
|
}
|
|
|
|
public function registerComponentAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
|
|
$component = $request->getParam('component');
|
|
$remoteAddr = Application_Model_ServiceRegister::GetRemoteIpAddr();
|
|
Logging::info("Registered Component: ".$component."@".$remoteAddr);
|
|
|
|
Application_Model_ServiceRegister::Register($component, $remoteAddr);
|
|
}
|
|
|
|
public function updateLiquidsoapStatusAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
|
|
$msg = $request->getParam('msg_post');
|
|
$stream_id = $request->getParam('stream_id');
|
|
$boot_time = $request->getParam('boot_time');
|
|
|
|
Application_Model_StreamSetting::setLiquidsoapError($stream_id, $msg, $boot_time);
|
|
}
|
|
|
|
public function updateSourceStatusAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
|
|
$sourcename = $request->getParam('sourcename');
|
|
$status = $request->getParam('status');
|
|
|
|
// on source disconnection sent msg to pypo to turn off the switch
|
|
// Added AutoTransition option
|
|
if ($status == "false" && Application_Model_Preference::GetAutoTransition()) {
|
|
$data = array("sourcename"=>$sourcename, "status"=>"off");
|
|
Application_Model_RabbitMq::SendMessageToPypo("switch_source", $data);
|
|
Application_Model_Preference::SetSourceSwitchStatus($sourcename, "off");
|
|
Application_Model_LiveLog::SetEndTime($sourcename == 'scheduled_play'?'S':'L',
|
|
new DateTime("now", new DateTimeZone('UTC')));
|
|
} elseif ($status == "true" && Application_Model_Preference::GetAutoSwitch()) {
|
|
$data = array("sourcename"=>$sourcename, "status"=>"on");
|
|
Application_Model_RabbitMq::SendMessageToPypo("switch_source", $data);
|
|
Application_Model_Preference::SetSourceSwitchStatus($sourcename, "on");
|
|
Application_Model_LiveLog::SetNewLogTime($sourcename == 'scheduled_play'?'S':'L',
|
|
new DateTime("now", new DateTimeZone('UTC')));
|
|
}
|
|
Application_Model_Preference::SetSourceStatus($sourcename, $status);
|
|
}
|
|
|
|
// handles addition/deletion of mount point which watched dirs reside
|
|
public function updateFileSystemMountAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
|
|
$params = $request->getParams();
|
|
$added_list = empty($params['added_dir'])?array():explode(',', $params['added_dir']);
|
|
$removed_list = empty($params['removed_dir'])?array():explode(',', $params['removed_dir']);
|
|
|
|
// get all watched dirs
|
|
$watched_dirs = Application_Model_MusicDir::getWatchedDirs(null, null);
|
|
|
|
foreach ($added_list as $ad) {
|
|
$ad .= '/';
|
|
foreach ($watched_dirs as $dir) {
|
|
$dirPath = $dir->getDirectory();
|
|
|
|
// if mount path itself was watched
|
|
if ($dirPath == $ad) {
|
|
Application_Model_MusicDir::addWatchedDir($dirPath, false);
|
|
} elseif (substr($dirPath, 0, strlen($ad)) === $ad && $dir->getExistsFlag() == false) {
|
|
// if dir contains any dir in removed_list( if watched dir resides on new mounted path )
|
|
Application_Model_MusicDir::addWatchedDir($dirPath, false);
|
|
} elseif (substr($ad, 0, strlen($dirPath)) === $dirPath) {
|
|
// is new mount point within the watched dir?
|
|
// pyinotify doesn't notify anyhing in this case, so we add this mount point as
|
|
// watched dir
|
|
// bypass nested loop check
|
|
Application_Model_MusicDir::addWatchedDir($ad, false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($removed_list as $rd) {
|
|
$rd .= '/';
|
|
foreach ($watched_dirs as $dir) {
|
|
$dirPath = $dir->getDirectory();
|
|
// if dir contains any dir in removed_list( if watched dir resides on new mounted path )
|
|
if (substr($dirPath, 0, strlen($rd)) === $rd && $dir->getExistsFlag() == true) {
|
|
Application_Model_MusicDir::removeWatchedDir($dirPath, false);
|
|
} elseif (substr($rd, 0, strlen($dirPath)) === $dirPath) {
|
|
// is new mount point within the watched dir?
|
|
// pyinotify doesn't notify anyhing in this case, so we walk through all files within
|
|
// this watched dir in DB and mark them deleted.
|
|
// In case of h) of use cases, due to pyinotify behaviour of noticing mounted dir, we need to
|
|
// compare agaisnt all files in cc_files table
|
|
|
|
$watchDir = Application_Model_MusicDir::getDirByPath($rd);
|
|
// get all the files that is under $dirPath
|
|
$files = Application_Model_StoredFile::listAllFiles(
|
|
$dir->getId(),$all=false, true);
|
|
foreach ($files as $f) {
|
|
// if the file is from this mount
|
|
if (substr($f->getFilePath(), 0, strlen($rd)) === $rd) {
|
|
$f->delete();
|
|
}
|
|
}
|
|
if ($watchDir) {
|
|
Application_Model_MusicDir::removeWatchedDir($rd, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// handles case where watched dir is missing
|
|
public function handleWatchedDirMissingAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
|
|
$dir = base64_decode($request->getParam('dir'));
|
|
Application_Model_MusicDir::removeWatchedDir($dir, false);
|
|
}
|
|
|
|
/* This action is for use by our dev scripts, that make
|
|
* a change to the database and we want rabbitmq to send
|
|
* out a message to pypo that a potential change has been made. */
|
|
public function rabbitmqDoPushAction()
|
|
{
|
|
Logging::info("Notifying RabbitMQ to send message to pypo");
|
|
|
|
Application_Model_RabbitMq::SendMessageToPypo("reset_liquidsoap_bootstrap", array());
|
|
Application_Model_RabbitMq::PushSchedule();
|
|
}
|
|
|
|
public function getBootstrapInfoAction()
|
|
{
|
|
$live_dj = Application_Model_Preference::GetSourceSwitchStatus('live_dj');
|
|
$master_dj = Application_Model_Preference::GetSourceSwitchStatus('master_dj');
|
|
$scheduled_play = Application_Model_Preference::GetSourceSwitchStatus('scheduled_play');
|
|
|
|
$res = array("live_dj"=>$live_dj, "master_dj"=>$master_dj, "scheduled_play"=>$scheduled_play);
|
|
$this->view->switch_status = $res;
|
|
$this->view->station_name = Application_Model_Preference::GetStationName();
|
|
$this->view->stream_label = Application_Model_Preference::GetStreamLabelFormat();
|
|
$this->view->transition_fade = Application_Model_Preference::GetDefaultTransitionFade();
|
|
}
|
|
|
|
/* This is used but Liquidsoap to check authentication of live streams*/
|
|
public function checkLiveStreamAuthAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
|
|
$username = $request->getParam('username');
|
|
$password = $request->getParam('password');
|
|
$djtype = $request->getParam('djtype');
|
|
|
|
if ($djtype == 'master') {
|
|
//check against master
|
|
if ($username == Application_Model_Preference::GetLiveStreamMasterUsername()
|
|
&& $password == Application_Model_Preference::GetLiveStreamMasterPassword()) {
|
|
$this->view->msg = true;
|
|
} else {
|
|
$this->view->msg = false;
|
|
}
|
|
} elseif ($djtype == "dj") {
|
|
//check against show dj auth
|
|
$showInfo = Application_Model_Show::getCurrentShow();
|
|
// there is current playing show
|
|
if (isset($showInfo[0]['id'])) {
|
|
$current_show_id = $showInfo[0]['id'];
|
|
$CcShow = CcShowQuery::create()->findPK($current_show_id);
|
|
|
|
// get custom pass info from the show
|
|
$custom_user = $CcShow->getDbLiveStreamUser();
|
|
$custom_pass = $CcShow->getDbLiveStreamPass();
|
|
|
|
// get hosts ids
|
|
$show = new Application_Model_Show($current_show_id);
|
|
$hosts_ids = $show->getHostsIds();
|
|
|
|
// check against hosts auth
|
|
if ($CcShow->getDbLiveStreamUsingAirtimeAuth()) {
|
|
foreach ($hosts_ids as $host) {
|
|
$h = new Application_Model_User($host['subjs_id']);
|
|
if ($username == $h->getLogin() && md5($password) == $h->getPassword()) {
|
|
$this->view->msg = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// check against custom auth
|
|
if ($CcShow->getDbLiveStreamUsingCustomAuth()) {
|
|
if ($username == $custom_user && $password == $custom_pass) {
|
|
$this->view->msg = true;
|
|
} else {
|
|
$this->view->msg = false;
|
|
}
|
|
} else {
|
|
$this->view->msg = false;
|
|
}
|
|
} else {
|
|
// no show is currently playing
|
|
$this->view->msg = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This action is for use by our dev scripts, that make
|
|
* a change to the database and we want rabbitmq to send
|
|
* out a message to pypo that a potential change has been made. */
|
|
public function getFilesWithoutReplayGainAction()
|
|
{
|
|
$dir_id = $this->_getParam('dir_id');
|
|
|
|
//connect to db and get get sql
|
|
$rows = Application_Model_StoredFile::listAllFiles2($dir_id, 100);
|
|
|
|
$this->_helper->json->sendJson($rows);
|
|
}
|
|
|
|
public function getFilesWithoutSilanValueAction()
|
|
{
|
|
//connect to db and get get sql
|
|
$rows = Application_Model_StoredFile::getAllFilesWithoutSilan();
|
|
|
|
$this->_helper->json->sendJson($rows);
|
|
}
|
|
|
|
public function updateReplayGainValueAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$data = json_decode($request->getParam('data'));
|
|
|
|
foreach ($data as $pair) {
|
|
list($id, $gain) = $pair;
|
|
// TODO : move this code into model -- RG
|
|
$file = Application_Model_StoredFile::RecallById($p_id = $id)->getPropelOrm();
|
|
$file->setDbReplayGain($gain);
|
|
$file->save();
|
|
}
|
|
|
|
$this->_helper->json->sendJson(array());
|
|
}
|
|
|
|
public function updateCueValuesBySilanAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$data = json_decode($request->getParam('data'), $assoc = true);
|
|
|
|
foreach ($data as $pair) {
|
|
list($id, $info) = $pair;
|
|
// TODO : move this code into model -- RG
|
|
$file = Application_Model_StoredFile::RecallById($p_id = $id)->getPropelOrm();
|
|
|
|
//What we are doing here is setting a more accurate length that was
|
|
//calculated with silan by actually scanning the entire file. This
|
|
//process takes a really long time, and so we only do it in the background
|
|
//after the file has already been imported -MK
|
|
$length = $file->getDbLength();
|
|
if (isset($info['length'])) {
|
|
$length = $info['length'];
|
|
//length decimal number in seconds. Need to convert it to format
|
|
//HH:mm:ss to get around silly PHP limitations.
|
|
$length = Application_Common_DateHelper::secondsToPlaylistTime($length);
|
|
|
|
$file->setDbLength($length);
|
|
}
|
|
|
|
$cuein = isset($info['cuein']) ? $info['cuein'] : 0;
|
|
$cueout = isset($info['cueout']) ? $info['cueout'] : $length;
|
|
|
|
$file->setDbCuein($cuein);
|
|
$file->setDbCueout($cueout);
|
|
$file->setDbSilanCheck(true);
|
|
$file->save();
|
|
}
|
|
|
|
$this->_helper->json->sendJson(array());
|
|
}
|
|
|
|
public function notifyWebstreamDataAction()
|
|
{
|
|
$request = $this->getRequest();
|
|
$data = $request->getParam("data");
|
|
$media_id = $request->getParam("media_id");
|
|
$data_arr = json_decode($data);
|
|
|
|
if (!is_null($media_id)) {
|
|
if (isset($data_arr->title) &&
|
|
strlen($data_arr->title) < 1024) {
|
|
|
|
$previous_metadata = CcWebstreamMetadataQuery::create()
|
|
->orderByDbStartTime('desc')
|
|
->filterByDbInstanceId($media_id)
|
|
->findOne();
|
|
|
|
$do_insert = true;
|
|
if ($previous_metadata) {
|
|
if ($previous_metadata->getDbLiquidsoapData() == $data_arr->title) {
|
|
Logging::debug("Duplicate found: ".$data_arr->title);
|
|
$do_insert = false;
|
|
}
|
|
}
|
|
|
|
if ($do_insert) {
|
|
$webstream_metadata = new CcWebstreamMetadata();
|
|
$webstream_metadata->setDbInstanceId($media_id);
|
|
$webstream_metadata->setDbStartTime(new DateTime("now", new DateTimeZone("UTC")));
|
|
$webstream_metadata->setDbLiquidsoapData($data_arr->title);
|
|
$webstream_metadata->save();
|
|
}
|
|
}
|
|
} else {
|
|
throw new Exception("Null value of media_id");
|
|
}
|
|
|
|
$this->view->response = $data;
|
|
$this->view->media_id = $media_id;
|
|
}
|
|
|
|
public function getStreamParametersAction() {
|
|
$streams = array("s1", "s2", "s3");
|
|
$stream_params = array();
|
|
foreach ($streams as $s) {
|
|
$stream_params[$s] =
|
|
Application_Model_StreamSetting::getStreamDataNormalized($s);
|
|
}
|
|
$this->view->stream_params = $stream_params;
|
|
}
|
|
|
|
public function pushStreamStatsAction() {
|
|
$request = $this->getRequest();
|
|
$data = json_decode($request->getParam("data"), true);
|
|
|
|
Application_Model_ListenerStat::insertDataPoints($data);
|
|
$this->view->data = $data;
|
|
}
|
|
|
|
public function updateStreamSettingTableAction() {
|
|
$request = $this->getRequest();
|
|
$data = json_decode($request->getParam("data"), true);
|
|
|
|
foreach ($data as $k=>$v) {
|
|
Application_Model_StreamSetting::SetListenerStatError($k, $v);
|
|
}
|
|
}
|
|
}
|