ApiController.php 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504
  1. <?php
  2. class ApiController extends Zend_Controller_Action
  3. {
  4. public function init()
  5. {
  6. $ignoreAuth = array("live-info",
  7. "live-info-v2",
  8. "week-info",
  9. "station-metadata",
  10. "station-logo",
  11. "show-history-feed",
  12. "item-history-feed",
  13. "shows",
  14. "show-tracks",
  15. "show-schedules"
  16. );
  17. $params = $this->getRequest()->getParams();
  18. if (!in_array($params['action'], $ignoreAuth)) {
  19. $this->checkAuth();
  20. }
  21. /* Initialize action controller here */
  22. $context = $this->_helper->getHelper('contextSwitch');
  23. $context->addActionContext('version' , 'json')
  24. ->addActionContext('recorded-shows' , 'json')
  25. ->addActionContext('calendar-init' , 'json')
  26. ->addActionContext('upload-file' , 'json')
  27. ->addActionContext('upload-recorded' , 'json')
  28. ->addActionContext('media-monitor-setup' , 'json')
  29. ->addActionContext('media-item-status' , 'json')
  30. ->addActionContext('reload-metadata' , 'json')
  31. ->addActionContext('list-all-files' , 'json')
  32. ->addActionContext('list-all-watched-dirs' , 'json')
  33. ->addActionContext('add-watched-dir' , 'json')
  34. ->addActionContext('remove-watched-dir' , 'json')
  35. ->addActionContext('set-storage-dir' , 'json')
  36. ->addActionContext('get-stream-setting' , 'json')
  37. ->addActionContext('status' , 'json')
  38. ->addActionContext('register-component' , 'json')
  39. ->addActionContext('update-liquidsoap-status' , 'json')
  40. ->addActionContext('live-chat' , 'json')
  41. ->addActionContext('update-file-system-mount' , 'json')
  42. ->addActionContext('handle-watched-dir-missing' , 'json')
  43. ->addActionContext('rabbitmq-do-push' , 'json')
  44. ->addActionContext('check-live-stream-auth' , 'json')
  45. ->addActionContext('update-source-status' , 'json')
  46. ->addActionContext('get-bootstrap-info' , 'json')
  47. ->addActionContext('get-files-without-replay-gain' , 'json')
  48. ->addActionContext('get-files-without-silan-value' , 'json')
  49. ->addActionContext('reload-metadata-group' , 'json')
  50. ->addActionContext('notify-webstream-data' , 'json')
  51. ->addActionContext('get-stream-parameters' , 'json')
  52. ->addActionContext('push-stream-stats' , 'json')
  53. ->addActionContext('update-stream-setting-table' , 'json')
  54. ->addActionContext('update-replay-gain-value' , 'json')
  55. ->addActionContext('update-cue-values-by-silan' , 'json')
  56. ->initContext();
  57. }
  58. public function checkAuth()
  59. {
  60. $CC_CONFIG = Config::getConfig();
  61. $api_key = $this->_getParam('api_key');
  62. if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
  63. is_null(Zend_Auth::getInstance()->getStorage()->read())) {
  64. header('HTTP/1.0 401 Unauthorized');
  65. print _('You are not allowed to access this resource.');
  66. exit;
  67. }
  68. }
  69. public function versionAction()
  70. {
  71. $this->_helper->json->sendJson( array(
  72. "airtime_version" => Application_Model_Preference::GetAirtimeVersion(),
  73. "api_version" => AIRTIME_API_VERSION));
  74. }
  75. /**
  76. * Allows remote client to download requested media file.
  77. *
  78. * @return void
  79. *
  80. */
  81. public function getMediaAction()
  82. {
  83. $fileId = $this->_getParam("file");
  84. $media = Application_Model_StoredFile::RecallById($fileId);
  85. if ($media != null) {
  86. $filepath = $media->getFilePath();
  87. // Make sure we don't have some wrong result beecause of caching
  88. clearstatcache();
  89. if (is_file($filepath)) {
  90. $full_path = $media->getPropelOrm()->getDbFilepath();
  91. $file_base_name = strrchr($full_path, '/');
  92. /* If $full_path does not contain a '/', strrchr will return false,
  93. * in which case we can use $full_path as the base name.
  94. */
  95. if (!$file_base_name) {
  96. $file_base_name = $full_path;
  97. } else {
  98. $file_base_name = substr($file_base_name, 1);
  99. }
  100. //Download user left clicks a track and selects Download.
  101. if ("true" == $this->_getParam('download')) {
  102. //path_info breaks up a file path into seperate pieces of informaiton.
  103. //We just want the basename which is the file name with the path
  104. //information stripped away. We are using Content-Disposition to specify
  105. //to the browser what name the file should be saved as.
  106. header('Content-Disposition: attachment; filename="'.$file_base_name.'"');
  107. } else {
  108. //user clicks play button for track and downloads it.
  109. header('Content-Disposition: inline; filename="'.$file_base_name.'"');
  110. }
  111. $this->smartReadFile($filepath, $media->getPropelOrm()->getDbMime());
  112. exit;
  113. } else {
  114. header ("HTTP/1.1 404 Not Found");
  115. }
  116. }
  117. $this->_helper->json->sendJson(array());
  118. }
  119. /**
  120. * Reads the requested portion of a file and sends its contents to the client with the appropriate headers.
  121. *
  122. * This HTTP_RANGE compatible read file function is necessary for allowing streaming media to be skipped around in.
  123. *
  124. * @param string $location
  125. * @param string $mimeType
  126. * @return void
  127. *
  128. * @link https://groups.google.com/d/msg/jplayer/nSM2UmnSKKA/Hu76jDZS4xcJ
  129. * @link http://php.net/manual/en/function.readfile.php#86244
  130. */
  131. public function smartReadFile($location, $mimeType = 'audio/mp3')
  132. {
  133. $size= filesize($location);
  134. $time= date('r', filemtime($location));
  135. $fm = @fopen($location, 'rb');
  136. if (!$fm) {
  137. header ("HTTP/1.1 505 Internal server error");
  138. return;
  139. }
  140. $begin = 0;
  141. $end = $size - 1;
  142. if (isset($_SERVER['HTTP_RANGE'])) {
  143. if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
  144. $begin = intval($matches[1]);
  145. if (!empty($matches[2])) {
  146. $end = intval($matches[2]);
  147. }
  148. }
  149. }
  150. if (isset($_SERVER['HTTP_RANGE'])) {
  151. header('HTTP/1.1 206 Partial Content');
  152. } else {
  153. header('HTTP/1.1 200 OK');
  154. }
  155. header("Content-Type: $mimeType");
  156. header('Cache-Control: public, must-revalidate, max-age=0');
  157. header('Pragma: no-cache');
  158. header('Accept-Ranges: bytes');
  159. header('Content-Length:' . (($end - $begin) + 1));
  160. if (isset($_SERVER['HTTP_RANGE'])) {
  161. header("Content-Range: bytes $begin-$end/$size");
  162. }
  163. header("Content-Transfer-Encoding: binary");
  164. header("Last-Modified: $time");
  165. //We can have multiple levels of output buffering. Need to
  166. //keep looping until all have been disabled!!!
  167. //http://www.php.net/manual/en/function.ob-end-flush.php
  168. while (@ob_end_flush());
  169. $cur = $begin;
  170. fseek($fm, $begin, 0);
  171. while (!feof($fm) && $cur <= $end && (connection_status() == 0)) {
  172. echo fread($fm, min(1024 * 16, ($end - $cur) + 1));
  173. $cur += 1024 * 16;
  174. }
  175. }
  176. //Used by the SaaS monitoring
  177. public function onAirLightAction()
  178. {
  179. $this->view->layout()->disableLayout();
  180. $this->_helper->viewRenderer->setNoRender(true);
  181. $result = array();
  182. $result["on_air_light"] = false;
  183. $result["on_air_light_expected_status"] = false;
  184. $result["station_down"] = false;
  185. $range = Application_Model_Schedule::GetPlayOrderRange();
  186. $isItemCurrentlyScheduled = !is_null($range["current"]) && count($range["currentShow"]) > 0 ? true : false;
  187. $isCurrentItemPlaying = $range["current"]["media_item_played"] ? true : false;
  188. if ($isItemCurrentlyScheduled ||
  189. Application_Model_Preference::GetSourceSwitchStatus("live_dj") == "on" ||
  190. Application_Model_Preference::GetSourceSwitchStatus("master_dj") == "on")
  191. {
  192. $result["on_air_light_expected_status"] = true;
  193. }
  194. if (($isItemCurrentlyScheduled && $isCurrentItemPlaying) ||
  195. Application_Model_Preference::GetSourceSwitchStatus("live_dj") == "on" ||
  196. Application_Model_Preference::GetSourceSwitchStatus("master_dj") == "on")
  197. {
  198. $result["on_air_light"] = true;
  199. }
  200. if ($result["on_air_light_expected_status"] != $result["on_air_light"]) {
  201. $result["station_down"] = true;
  202. }
  203. echo isset($_GET['callback']) ? $_GET['callback'].'('.json_encode($result).')' : json_encode($result);
  204. }
  205. /**
  206. * Retrieve the currently playing show as well as upcoming shows.
  207. * Number of shows returned and the time interval in which to
  208. * get the next shows can be configured as GET parameters.
  209. *
  210. * TODO: in the future, make interval length a parameter instead of hardcode to 48
  211. *
  212. * Possible parameters:
  213. * type - Can have values of "endofday" or "interval". If set to "endofday",
  214. * the function will retrieve shows from now to end of day.
  215. * If set to "interval", shows in the next 48 hours will be retrived.
  216. * Default is "interval".
  217. * limit - How many shows to retrieve
  218. * Default is "5".
  219. */
  220. public function liveInfoAction()
  221. {
  222. if (Application_Model_Preference::GetAllow3rdPartyApi()) {
  223. // disable the view and the layout
  224. $this->view->layout()->disableLayout();
  225. $this->_helper->viewRenderer->setNoRender(true);
  226. $request = $this->getRequest();
  227. $utcTimeNow = gmdate("Y-m-d H:i:s");
  228. $utcTimeEnd = ""; // if empty, getNextShows will use interval instead of end of day
  229. // default to the station timezone
  230. $timezone = Application_Model_Preference::GetDefaultTimezone();
  231. $userDefinedTimezone = strtolower($request->getParam('timezone'));
  232. $upcase = false; // only upcase the timezone abbreviations
  233. $this->checkTimezone($userDefinedTimezone, $timezone, $upcase);
  234. $type = $request->getParam('type');
  235. $limit = $request->getParam('limit');
  236. if ($limit == "" || !is_numeric($limit)) {
  237. $limit = "5";
  238. }
  239. /* This is some *extremely* lazy programming that needs to be fixed. For some reason
  240. * we are using two entirely different codepaths for very similar functionality (type = endofday
  241. * vs type = interval). Needs to be fixed for 2.3 - MK */
  242. if ($type == "endofday") {
  243. // make getNextShows use end of day
  244. $end = Application_Common_DateHelper::getTodayStationEndDateTime();
  245. $end->setTimezone(new DateTimeZone("UTC"));
  246. $utcTimeEnd = $end->format("Y-m-d H:i:s");
  247. $result = array(
  248. "env" => APPLICATION_ENV,
  249. "schedulerTime" => $utcTimeNow,
  250. "currentShow" => Application_Model_Show::getCurrentShow($utcTimeNow),
  251. "nextShow" => Application_Model_Show::getNextShows($utcTimeNow, $limit, $utcTimeEnd)
  252. );
  253. } else {
  254. $result = Application_Model_Schedule::GetPlayOrderRangeOld($limit);
  255. }
  256. // XSS exploit prevention
  257. $this->convertSpecialChars($result, array("name", "url"));
  258. // apply user-defined timezone, or default to station
  259. Application_Common_DateHelper::convertTimestampsToTimezone(
  260. $result['currentShow'],
  261. array("starts", "ends", "start_timestamp","end_timestamp"),
  262. $timezone
  263. );
  264. Application_Common_DateHelper::convertTimestampsToTimezone(
  265. $result['nextShow'],
  266. array("starts", "ends", "start_timestamp","end_timestamp"),
  267. $timezone
  268. );
  269. //Convert the UTC scheduler time ("now") to the user-defined timezone.
  270. $result["schedulerTime"] = Application_Common_DateHelper::UTCStringToTimezoneString($result["schedulerTime"], $timezone);
  271. $result["timezone"] = $upcase ? strtoupper($timezone) : $timezone;
  272. $result["timezoneOffset"] = Application_Common_DateHelper::getTimezoneOffset($timezone);
  273. // used by caller to determine if the airtime they are running or widgets in use is out of date.
  274. $result['AIRTIME_API_VERSION'] = AIRTIME_API_VERSION;
  275. header("Content-Type: application/json");
  276. if (version_compare(phpversion(), '5.4.0', '<')) {
  277. $js = json_encode($result);
  278. } else {
  279. $js = json_encode($result, JSON_PRETTY_PRINT);
  280. }
  281. // If a callback is not given, then just provide the raw JSON.
  282. echo isset($_GET['callback']) ? $_GET['callback'].'('.$js.')' : $js;
  283. } else {
  284. header('HTTP/1.0 401 Unauthorized');
  285. print _('You are not allowed to access this resource. ');
  286. exit;
  287. }
  288. }
  289. /**
  290. * Retrieve the currently playing show as well as upcoming shows.
  291. * Number of shows returned and the time interval in which to
  292. * get the next shows can be configured as GET parameters.
  293. *
  294. * Possible parameters:
  295. * days - How many days to retrieve.
  296. * Default is 2 (today + tomorrow).
  297. * shows - How many shows to retrieve
  298. * Default is 5.
  299. * timezone - The timezone to send the times in
  300. * Defaults to the station timezone
  301. */
  302. public function liveInfoV2Action()
  303. {
  304. if (Application_Model_Preference::GetAllow3rdPartyApi()) {
  305. // disable the view and the layout
  306. $this->view->layout()->disableLayout();
  307. $this->_helper->viewRenderer->setNoRender(true);
  308. $request = $this->getRequest();
  309. $utcTimeNow = gmdate("Y-m-d H:i:s");
  310. $utcTimeEnd = ""; // if empty, getNextShows will use interval instead of end of day
  311. // default to the station timezone
  312. $timezone = Application_Model_Preference::GetDefaultTimezone();
  313. $userDefinedTimezone = strtolower($request->getParam('timezone'));
  314. $upcase = false; // only upcase the timezone abbreviations
  315. $this->checkTimezone($userDefinedTimezone, $timezone, $upcase);
  316. $daysToRetrieve = $request->getParam('days');
  317. $showsToRetrieve = $request->getParam('shows');
  318. if ($daysToRetrieve == "" || !is_numeric($daysToRetrieve)) {
  319. $daysToRetrieve = "2";
  320. }
  321. if ($showsToRetrieve == "" || !is_numeric($showsToRetrieve)) {
  322. $showsToRetrieve = "5";
  323. }
  324. // set the end time to the day's start n days from now.
  325. // days=1 will return shows until the end of the current day,
  326. // days=2 will return shows until the end of tomorrow, etc.
  327. $end = Application_Common_DateHelper::getEndDateTime($timezone, $daysToRetrieve);
  328. $end->setTimezone(new DateTimeZone("UTC"));
  329. $utcTimeEnd = $end->format("Y-m-d H:i:s");
  330. $result = Application_Model_Schedule::GetPlayOrderRange($utcTimeEnd, $showsToRetrieve);
  331. // XSS exploit prevention
  332. $this->convertSpecialChars($result, array("name", "url"));
  333. // apply user-defined timezone, or default to station
  334. $this->applyLiveTimezoneAdjustments($result, $timezone, $upcase);
  335. // used by caller to determine if the airtime they are running or widgets in use is out of date.
  336. $result["station"]["AIRTIME_API_VERSION"] = AIRTIME_API_VERSION;
  337. header("Content-Type: application/json");
  338. if (version_compare(phpversion(), '5.4.0', '<')) {
  339. $js = json_encode($result);
  340. } else {
  341. $js = json_encode($result, JSON_PRETTY_PRINT);
  342. }
  343. // If a callback is not given, then just provide the raw JSON.
  344. echo isset($_GET['callback']) ? $_GET['callback'].'('.$js.')' : $js;
  345. } else {
  346. header('HTTP/1.0 401 Unauthorized');
  347. print _('You are not allowed to access this resource. ');
  348. exit;
  349. }
  350. }
  351. /**
  352. * Check that the value for the timezone the user gave is valid.
  353. * If it is, override the default (station) timezone.
  354. * If it's an abbreviation (pst, edt) we upcase the output.
  355. *
  356. * @param string $userDefinedTimezone the requested timezone value
  357. * @param string $timezone the default timezone
  358. * @param boolean $upcase whether the timezone output should be upcased
  359. */
  360. private function checkTimezone($userDefinedTimezone, &$timezone, &$upcase)
  361. {
  362. $delimiter = "/";
  363. // if the user passes in a timezone in standard form ("Continent/City")
  364. // we need to fix the downcased string by upcasing each word delimited by a /
  365. if (strpos($userDefinedTimezone, $delimiter) !== false) {
  366. $userDefinedTimezone = implode($delimiter, array_map('ucfirst', explode($delimiter, $userDefinedTimezone)));
  367. }
  368. // if the timezone defined by the user exists, use that
  369. if (array_key_exists($userDefinedTimezone, timezone_abbreviations_list())) {
  370. $timezone = $userDefinedTimezone;
  371. $upcase = true;
  372. } else if (in_array($userDefinedTimezone, timezone_identifiers_list())) {
  373. $timezone = $userDefinedTimezone;
  374. }
  375. }
  376. /**
  377. * If the user passed in a timezone parameter, adjust timezone-dependent
  378. * variables in the result to reflect the given timezone.
  379. *
  380. * @param object $result reference to the object to send back to the user
  381. * @param string $timezone the user's timezone parameter value
  382. * @param boolean $upcase whether the timezone output should be upcased
  383. */
  384. private function applyLiveTimezoneAdjustments(&$result, $timezone, $upcase)
  385. {
  386. Application_Common_DateHelper::convertTimestampsToTimezone(
  387. $result,
  388. array("starts", "ends", "start_timestamp","end_timestamp"),
  389. $timezone
  390. );
  391. //Convert the UTC scheduler time ("now") to the user-defined timezone.
  392. $result["station"]["schedulerTime"] = Application_Common_DateHelper::UTCStringToTimezoneString($result["station"]["schedulerTime"], $timezone);
  393. $result["station"]["timezone"] = $upcase ? strtoupper($timezone) : $timezone;
  394. }
  395. public function weekInfoAction()
  396. {
  397. if (Application_Model_Preference::GetAllow3rdPartyApi()) {
  398. // disable the view and the layout
  399. $this->view->layout()->disableLayout();
  400. $this->_helper->viewRenderer->setNoRender(true);
  401. //weekStart is in station time.
  402. $weekStartDateTime = Application_Common_DateHelper::getWeekStartDateTime();
  403. $dow = array("monday", "tuesday", "wednesday", "thursday", "friday",
  404. "saturday", "sunday", "nextmonday", "nexttuesday", "nextwednesday",
  405. "nextthursday", "nextfriday", "nextsaturday", "nextsunday");
  406. $result = array();
  407. // default to the station timezone
  408. $timezone = Application_Model_Preference::GetDefaultTimezone();
  409. $userDefinedTimezone = strtolower($this->getRequest()->getParam("timezone"));
  410. // if the timezone defined by the user exists, use that
  411. if (array_key_exists($userDefinedTimezone, timezone_abbreviations_list())) {
  412. $timezone = $userDefinedTimezone;
  413. }
  414. $utcTimezone = new DateTimeZone("UTC");
  415. $weekStartDateTime->setTimezone($utcTimezone);
  416. $utcDayStart = $weekStartDateTime->format("Y-m-d H:i:s");
  417. for ($i = 0; $i < 14; $i++) {
  418. //have to be in station timezone when adding 1 day for daylight savings.
  419. $weekStartDateTime->setTimezone(new DateTimeZone($timezone));
  420. $weekStartDateTime->add(new DateInterval('P1D'));
  421. //convert back to UTC to get the actual timestamp used for search.
  422. $weekStartDateTime->setTimezone($utcTimezone);
  423. $utcDayEnd = $weekStartDateTime->format("Y-m-d H:i:s");
  424. $shows = Application_Model_Show::getNextShows($utcDayStart, "ALL", $utcDayEnd);
  425. $utcDayStart = $utcDayEnd;
  426. // convert to user-defined timezone, or default to station
  427. Application_Common_DateHelper::convertTimestampsToTimezone(
  428. $shows,
  429. array("starts", "ends", "start_timestamp","end_timestamp"),
  430. $timezone
  431. );
  432. $result[$dow[$i]] = $shows;
  433. }
  434. // XSS exploit prevention
  435. $this->convertSpecialChars($result, array("name", "url"));
  436. //used by caller to determine if the airtime they are running or widgets in use is out of date.
  437. $result['AIRTIME_API_VERSION'] = AIRTIME_API_VERSION;
  438. header("Content-type: text/javascript");
  439. $js = json_encode($result, JSON_PRETTY_PRINT);
  440. // If a callback is not given, then just provide the raw JSON.
  441. echo isset($_GET['callback']) ? $_GET['callback'].'('.$js.')' : $js;
  442. } else {
  443. header('HTTP/1.0 401 Unauthorized');
  444. print _('You are not allowed to access this resource. ');
  445. exit;
  446. }
  447. }
  448. public function scheduleAction()
  449. {
  450. $this->view->layout()->disableLayout();
  451. $this->_helper->viewRenderer->setNoRender(true);
  452. header("Content-Type: application/json");
  453. $data = Application_Model_Schedule::getSchedule();
  454. echo json_encode($data, JSON_FORCE_OBJECT);
  455. }
  456. public function notifyMediaItemStartPlayAction()
  457. {
  458. $media_id = $this->_getParam("media_id");
  459. Logging::debug("Received notification of new media item start: $media_id");
  460. Application_Model_Schedule::UpdateMediaPlayedStatus($media_id);
  461. try {
  462. $historyService = new Application_Service_HistoryService();
  463. $historyService->insertPlayedItem($media_id);
  464. //set a 'last played' timestamp for media item
  465. //needed for smart blocks
  466. $mediaType = Application_Model_Schedule::GetType($media_id);
  467. if ($mediaType == 'file') {
  468. $file_id = Application_Model_Schedule::GetFileId($media_id);
  469. if (!is_null($file_id)) {
  470. //we are dealing with a file not a stream
  471. $file = Application_Model_StoredFile::RecallById($file_id);
  472. $now = new DateTime("now", new DateTimeZone("UTC"));
  473. $file->setLastPlayedTime($now);
  474. }
  475. } else {
  476. // webstream
  477. $stream_id = Application_Model_Schedule::GetStreamId($media_id);
  478. if (!is_null($stream_id)) {
  479. $webStream = new Application_Model_Webstream($stream_id);
  480. $now = new DateTime("now", new DateTimeZone("UTC"));
  481. $webStream->setLastPlayed($now);
  482. }
  483. }
  484. } catch (Exception $e) {
  485. Logging::info($e);
  486. }
  487. $this->_helper->json->sendJson(array("status"=>1, "message"=>""));
  488. }
  489. /**
  490. * Go through a given array and sanitize any potentially exploitable fields
  491. * by passing them through htmlspecialchars
  492. *
  493. * @param unknown $arr the array to sanitize
  494. * @param unknown $keys indexes of values to be sanitized
  495. */
  496. private function convertSpecialChars(&$arr, $keys)
  497. {
  498. foreach ($arr as &$a) {
  499. if (is_array($a)) {
  500. foreach ($keys as &$key) {
  501. if (array_key_exists($key, $a)) {
  502. $a[$key] = htmlspecialchars($a[$key]);
  503. }
  504. }
  505. $this->convertSpecialChars($a, $keys);
  506. }
  507. }
  508. }
  509. /**
  510. * API endpoint to provide station metadata
  511. */
  512. public function stationMetadataAction()
  513. {
  514. if (Application_Model_Preference::GetAllow3rdPartyApi()) {
  515. // disable the view and the layout
  516. $this->view->layout()->disableLayout();
  517. $this->_helper->viewRenderer->setNoRender(true);
  518. $CC_CONFIG = Config::getConfig();
  519. $baseDir = Application_Common_OsPath::formatDirectoryWithDirectorySeparators($CC_CONFIG['baseDir']);
  520. $path = 'http://'.$_SERVER['HTTP_HOST'].$baseDir."api/station-logo";
  521. $result["name"] = Application_Model_Preference::GetStationName();
  522. $result["logo"] = $path;
  523. $result["description"] = Application_Model_Preference::GetStationDescription();
  524. $result["timezone"] = Application_Model_Preference::GetDefaultTimezone();
  525. $result["locale"] = Application_Model_Preference::GetDefaultLocale();
  526. // used by caller to determine if the airtime they are running or widgets in use is out of date.
  527. $result['AIRTIME_API_VERSION'] = AIRTIME_API_VERSION;
  528. header("Content-type: text/javascript");
  529. $js = json_encode($result, JSON_PRETTY_PRINT);
  530. // If a callback is not given, then just provide the raw JSON.
  531. echo isset($_GET['callback']) ? $_GET['callback'].'('.$js.')' : $js;
  532. } else {
  533. header('HTTP/1.0 401 Unauthorized');
  534. print _('You are not allowed to access this resource. ');
  535. exit;
  536. }
  537. }
  538. /**
  539. * API endpoint to display the current station logo
  540. */
  541. public function stationLogoAction()
  542. {
  543. if (Application_Model_Preference::GetAllow3rdPartyApi()) {
  544. // disable the view and the layout
  545. $this->view->layout()->disableLayout();
  546. $this->_helper->viewRenderer->setNoRender(true);
  547. $logo = Application_Model_Preference::GetStationLogo();
  548. // if there's no logo, just die - redirects to a 404
  549. if (!$logo || $logo === '') {
  550. return;
  551. }
  552. // we're passing this as an image instead of using it in a data uri, so decode it
  553. $blob = base64_decode($logo);
  554. // use finfo to get the mimetype from the decoded blob
  555. $f = finfo_open();
  556. $mime_type = finfo_buffer($f, $blob, FILEINFO_MIME_TYPE);
  557. finfo_close($f);
  558. header("Content-type: " . $mime_type);
  559. echo $blob;
  560. } else {
  561. header('HTTP/1.0 401 Unauthorized');
  562. print _('You are not allowed to access this resource. ');
  563. exit;
  564. }
  565. }
  566. public function recordedShowsAction()
  567. {
  568. $utcTimezone = new DateTimeZone("UTC");
  569. $nowDateTime = new DateTime("now", $utcTimezone);
  570. $endDateTime = clone $nowDateTime;
  571. $endDateTime = $endDateTime->add(new DateInterval("PT2H"));
  572. $this->view->shows =
  573. Application_Model_Show::getShows(
  574. $nowDateTime,
  575. $endDateTime,
  576. $onlyRecord = true);
  577. $this->view->is_recording = false;
  578. $this->view->server_timezone = Application_Model_Preference::GetDefaultTimezone();
  579. $rows = Application_Model_Show::getCurrentShow();
  580. if (count($rows) > 0) {
  581. $this->view->is_recording = ($rows[0]['record'] == 1);
  582. }
  583. }
  584. public function uploadFileAction()
  585. {
  586. $upload_dir = ini_get("upload_tmp_dir");
  587. $tempFilePath = Application_Model_StoredFile::uploadFile($upload_dir);
  588. $tempFileName = basename($tempFilePath);
  589. $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';
  590. $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $fileName, $tempFileName);
  591. if (!is_null($result)) {
  592. $this->_helper->json->sendJson(
  593. array("jsonrpc" => "2.0", "error" => array("code" => $result['code'], "message" => $result['message']))
  594. );
  595. }
  596. }
  597. public function uploadRecordedAction()
  598. {
  599. $show_instance_id = $this->_getParam('showinstanceid');
  600. $file_id = $this->_getParam('fileid');
  601. $this->view->fileid = $file_id;
  602. $this->view->showinstanceid = $show_instance_id;
  603. $this->uploadRecordedActionParam($show_instance_id, $file_id);
  604. }
  605. // The paramterized version of the uploadRecordedAction controller.
  606. // We want this controller's action to be invokable from other
  607. // controllers instead being of only through http
  608. public function uploadRecordedActionParam($show_instance_id, $file_id)
  609. {
  610. $showCanceled = false;
  611. $file = Application_Model_StoredFile::RecallById($file_id);
  612. //$show_instance = $this->_getParam('show_instance');
  613. try {
  614. $show_inst = new Application_Model_ShowInstance($show_instance_id);
  615. $show_inst->setRecordedFile($file_id);
  616. }
  617. catch (Exception $e) {
  618. //we've reached here probably because the show was
  619. //cancelled, and therefore the show instance does not exist
  620. //anymore (ShowInstance constructor threw this error). We've
  621. //done all we can do (upload the file and put it in the
  622. //library), now lets just return.
  623. $showCanceled = true;
  624. }
  625. // TODO : the following is inefficient because it calls save on both
  626. // fields
  627. $file->setMetadataValue('MDATA_KEY_CREATOR', "Airtime Show Recorder");
  628. $file->setMetadataValue('MDATA_KEY_TRACKNUMBER', $show_instance_id);
  629. if (!$showCanceled && Application_Model_Preference::GetAutoUploadRecordedShowToSoundcloud()) {
  630. $id = $file->getId();
  631. Application_Model_Soundcloud::uploadSoundcloud($id);
  632. }
  633. }
  634. public function mediaMonitorSetupAction()
  635. {
  636. $this->view->stor = Application_Model_MusicDir::getStorDir()->getDirectory();
  637. $watchedDirs = Application_Model_MusicDir::getWatchedDirs();
  638. $watchedDirsPath = array();
  639. foreach ($watchedDirs as $wd) {
  640. $watchedDirsPath[] = $wd->getDirectory();
  641. }
  642. $this->view->watched_dirs = $watchedDirsPath;
  643. }
  644. public function dispatchMetadata($md, $mode)
  645. {
  646. $return_hash = array();
  647. Application_Model_Preference::SetImportTimestamp();
  648. $con = Propel::getConnection(CcFilesPeer::DATABASE_NAME);
  649. $con->beginTransaction();
  650. try {
  651. // create also modifies the file if it exists
  652. if ($mode == "create") {
  653. $filepath = $md['MDATA_KEY_FILEPATH'];
  654. $filepath = Application_Common_OsPath::normpath($filepath);
  655. $file = Application_Model_StoredFile::RecallByFilepath($filepath, $con);
  656. if (is_null($file)) {
  657. $file = Application_Model_StoredFile::Insert($md, $con);
  658. } else {
  659. // If the file already exists we will update and make sure that
  660. // it's marked as 'exists'.
  661. $file->setFileExistsFlag(true);
  662. $file->setFileHiddenFlag(false);
  663. $file->setMetadata($md);
  664. }
  665. if ($md['is_record'] != 0) {
  666. $this->uploadRecordedActionParam($md['MDATA_KEY_TRACKNUMBER'], $file->getId());
  667. }
  668. } elseif ($mode == "modify") {
  669. $filepath = $md['MDATA_KEY_FILEPATH'];
  670. $file = Application_Model_StoredFile::RecallByFilepath($filepath, $con);
  671. //File is not in database anymore.
  672. if (is_null($file)) {
  673. $return_hash['error'] = sprintf(_("File does not exist in %s"), PRODUCT_NAME);
  674. }
  675. //Updating a metadata change.
  676. else {
  677. //CC-5207 - restart media-monitor causes it to reevaluate all
  678. //files in watched directories, and reset their cue-in/cue-out
  679. //values. Since media-monitor has nothing to do with cue points
  680. //let's unset it here. Note that on mode == "create", we still
  681. //want media-monitor sending info about cue_out which by default
  682. //will be equal to length of track until silan can take over.
  683. unset($md['MDATA_KEY_CUE_IN']);
  684. unset($md['MDATA_KEY_CUE_OUT']);
  685. $file->setMetadata($md);
  686. }
  687. } elseif ($mode == "moved") {
  688. $file = Application_Model_StoredFile::RecallByFilepath(
  689. $md['MDATA_KEY_ORIGINAL_PATH'], $con);
  690. if (is_null($file)) {
  691. $return_hash['error'] = sprintf(_('File does not exist in %s'), PRODUCT_NAME);
  692. } else {
  693. $filepath = $md['MDATA_KEY_FILEPATH'];
  694. //$filepath = str_replace("\\", "", $filepath);
  695. $file->setFilePath($filepath);
  696. }
  697. } elseif ($mode == "delete") {
  698. $filepath = $md['MDATA_KEY_FILEPATH'];
  699. $filepath = str_replace("\\", "", $filepath);
  700. $file = Application_Model_StoredFile::RecallByFilepath($filepath, $con);
  701. if (is_null($file)) {
  702. $return_hash['error'] = sprintf(_('File does not exist in %s'), PRODUCT_NAME);
  703. Logging::warn("Attempt to delete file that doesn't exist.
  704. Path: '$filepath'");
  705. } else {
  706. $file->deleteByMediaMonitor();
  707. }
  708. } elseif ($mode == "delete_dir") {
  709. $filepath = $md['MDATA_KEY_FILEPATH'];
  710. //$filepath = str_replace("\\", "", $filepath);
  711. $files = Application_Model_StoredFile::RecallByPartialFilepath($filepath, $con);
  712. foreach ($files as $file) {
  713. $file->deleteByMediaMonitor();
  714. }
  715. $return_hash['success'] = 1;
  716. }
  717. if (!isset($return_hash['error'])) {
  718. $return_hash['fileid'] = is_null($file) ? '-1' : $file->getId();
  719. }
  720. $con->commit();
  721. } catch (Exception $e) {
  722. Logging::warn("rolling back");
  723. Logging::warn($e->getMessage());
  724. $con->rollback();
  725. $return_hash['error'] = $e->getMessage();
  726. }
  727. return $return_hash;
  728. }
  729. public function reloadMetadataGroupAction()
  730. {
  731. // extract all file metadata params from the request.
  732. // The value is a json encoded hash that has all the information related to this action
  733. // The key(mdXXX) does not have any meaning as of yet but it could potentially correspond
  734. // to some unique id.
  735. $request = $this->getRequest();
  736. $responses = array();
  737. $params = $request->getParams();
  738. $valid_modes = array('delete_dir', 'delete', 'moved', 'modify', 'create');
  739. foreach ($params as $k => $raw_json) {
  740. // Valid requests must start with mdXXX where XXX represents at
  741. // least 1 digit
  742. if ( !preg_match('/^md\d+$/', $k) ) { continue; }
  743. $info_json = json_decode($raw_json, $assoc = true);
  744. // Log invalid requests
  745. if ( !array_key_exists('mode', $info_json) ) {
  746. Logging::info("Received bad request(key=$k), no 'mode' parameter. Bad request is:");
  747. Logging::info( $info_json );
  748. array_push( $responses, array(
  749. 'error' => _("Bad request. no 'mode' parameter passed."),
  750. 'key' => $k));
  751. continue;
  752. } elseif ( !in_array($info_json['mode'], $valid_modes) ) {
  753. // A request still has a chance of being invalid even if it
  754. // exists but it's validated by $valid_modes array
  755. $mode = $info_json['mode'];
  756. Logging::info("Received bad request(key=$k). 'mode' parameter was invalid with value: '$mode'. Request:");
  757. Logging::info( $info_json );
  758. array_push( $responses, array(
  759. 'error' => _("Bad request. 'mode' parameter is invalid"),
  760. 'key' => $k,
  761. 'mode' => $mode ) );
  762. continue;
  763. }
  764. // Removing 'mode' key from $info_json might not be necessary...
  765. $mode = $info_json['mode'];
  766. unset( $info_json['mode'] );
  767. try {
  768. $response = $this->dispatchMetadata($info_json, $mode);
  769. } catch (Exception $e) {
  770. Logging::warn($e->getMessage());
  771. Logging::warn(gettype($e));
  772. }
  773. // We tack on the 'key' back to every request in case the would like to associate
  774. // his requests with particular responses
  775. $response['key'] = $k;
  776. array_push($responses, $response);
  777. }
  778. $this->_helper->json->sendJson($responses);
  779. }
  780. public function listAllFilesAction()
  781. {
  782. $request = $this->getRequest();
  783. $dir_id = $request->getParam('dir_id');
  784. $all = $request->getParam('all');
  785. $this->view->files =
  786. Application_Model_StoredFile::listAllFiles($dir_id, $all);
  787. }
  788. public function listAllWatchedDirsAction()
  789. {
  790. $result = array();
  791. $arrWatchedDirs = Application_Model_MusicDir::getWatchedDirs();
  792. $storDir = Application_Model_MusicDir::getStorDir();
  793. $result[$storDir->getId()] = $storDir->getDirectory();
  794. foreach ($arrWatchedDirs as $watchedDir) {
  795. $result[$watchedDir->getId()] = $watchedDir->getDirectory();
  796. }
  797. $this->view->dirs = $result;
  798. }
  799. public function addWatchedDirAction()
  800. {
  801. $request = $this->getRequest();
  802. $path = base64_decode($request->getParam('path'));
  803. $this->view->msg = Application_Model_MusicDir::addWatchedDir($path);
  804. }
  805. public function removeWatchedDirAction()
  806. {
  807. $request = $this->getRequest();
  808. $path = base64_decode($request->getParam('path'));
  809. $this->view->msg = Application_Model_MusicDir::removeWatchedDir($path);
  810. }
  811. public function setStorageDirAction()
  812. {
  813. $request = $this->getRequest();
  814. $path = base64_decode($request->getParam('path'));
  815. $this->view->msg = Application_Model_MusicDir::setStorDir($path);
  816. }
  817. public function getStreamSettingAction()
  818. {
  819. $info = Application_Model_StreamSetting::getStreamSetting();
  820. $this->view->msg = $info;
  821. }
  822. public function statusAction()
  823. {
  824. $request = $this->getRequest();
  825. $getDiskInfo = $request->getParam('diskinfo') == "true";
  826. $status = array(
  827. "platform"=>Application_Model_Systemstatus::GetPlatformInfo(),
  828. "airtime_version"=>Application_Model_Preference::GetAirtimeVersion(),
  829. "services"=>array(
  830. "pypo"=>Application_Model_Systemstatus::GetPypoStatus(),
  831. "liquidsoap"=>Application_Model_Systemstatus::GetLiquidsoapStatus(),
  832. "media_monitor"=>Application_Model_Systemstatus::GetMediaMonitorStatus()
  833. )
  834. );
  835. if ($getDiskInfo) {
  836. $status["partitions"] = Application_Model_Systemstatus::GetDiskInfo();
  837. }
  838. $this->view->status = $status;
  839. }
  840. public function registerComponentAction()
  841. {
  842. $request = $this->getRequest();
  843. $component = $request->getParam('component');
  844. $remoteAddr = Application_Model_ServiceRegister::GetRemoteIpAddr();
  845. Logging::info("Registered Component: ".$component."@".$remoteAddr);
  846. Application_Model_ServiceRegister::Register($component, $remoteAddr);
  847. }
  848. public function updateLiquidsoapStatusAction()
  849. {
  850. $request = $this->getRequest();
  851. $msg = $request->getParam('msg_post');
  852. $stream_id = $request->getParam('stream_id');
  853. $boot_time = $request->getParam('boot_time');
  854. Application_Model_StreamSetting::setLiquidsoapError($stream_id, $msg, $boot_time);
  855. }
  856. public function updateSourceStatusAction()
  857. {
  858. $request = $this->getRequest();
  859. $sourcename = $request->getParam('sourcename');
  860. $status = $request->getParam('status');
  861. // on source disconnection sent msg to pypo to turn off the switch
  862. // Added AutoTransition option
  863. if ($status == "false" && Application_Model_Preference::GetAutoTransition()) {
  864. $data = array("sourcename"=>$sourcename, "status"=>"off");
  865. Application_Model_RabbitMq::SendMessageToPypo("switch_source", $data);
  866. Application_Model_Preference::SetSourceSwitchStatus($sourcename, "off");
  867. Application_Model_LiveLog::SetEndTime($sourcename == 'scheduled_play'?'S':'L',
  868. new DateTime("now", new DateTimeZone('UTC')));
  869. } elseif ($status == "true" && Application_Model_Preference::GetAutoSwitch()) {
  870. $data = array("sourcename"=>$sourcename, "status"=>"on");
  871. Application_Model_RabbitMq::SendMessageToPypo("switch_source", $data);
  872. Application_Model_Preference::SetSourceSwitchStatus($sourcename, "on");
  873. Application_Model_LiveLog::SetNewLogTime($sourcename == 'scheduled_play'?'S':'L',
  874. new DateTime("now", new DateTimeZone('UTC')));
  875. }
  876. Application_Model_Preference::SetSourceStatus($sourcename, $status);
  877. }
  878. // handles addition/deletion of mount point which watched dirs reside
  879. public function updateFileSystemMountAction()
  880. {
  881. $request = $this->getRequest();
  882. $params = $request->getParams();
  883. $added_list = empty($params['added_dir'])?array():explode(',', $params['added_dir']);
  884. $removed_list = empty($params['removed_dir'])?array():explode(',', $params['removed_dir']);
  885. // get all watched dirs
  886. $watched_dirs = Application_Model_MusicDir::getWatchedDirs(null, null);
  887. foreach ($added_list as $ad) {
  888. $ad .= '/';
  889. foreach ($watched_dirs as $dir) {
  890. $dirPath = $dir->getDirectory();
  891. // if mount path itself was watched
  892. if ($dirPath == $ad) {
  893. Application_Model_MusicDir::addWatchedDir($dirPath, false);
  894. } elseif (substr($dirPath, 0, strlen($ad)) === $ad && $dir->getExistsFlag() == false) {
  895. // if dir contains any dir in removed_list( if watched dir resides on new mounted path )
  896. Application_Model_MusicDir::addWatchedDir($dirPath, false);
  897. } elseif (substr($ad, 0, strlen($dirPath)) === $dirPath) {
  898. // is new mount point within the watched dir?
  899. // pyinotify doesn't notify anyhing in this case, so we add this mount point as
  900. // watched dir
  901. // bypass nested loop check
  902. Application_Model_MusicDir::addWatchedDir($ad, false, true);
  903. }
  904. }
  905. }
  906. foreach ($removed_list as $rd) {
  907. $rd .= '/';
  908. foreach ($watched_dirs as $dir) {
  909. $dirPath = $dir->getDirectory();
  910. // if dir contains any dir in removed_list( if watched dir resides on new mounted path )
  911. if (substr($dirPath, 0, strlen($rd)) === $rd && $dir->getExistsFlag() == true) {
  912. Application_Model_MusicDir::removeWatchedDir($dirPath, false);
  913. } elseif (substr($rd, 0, strlen($dirPath)) === $dirPath) {
  914. // is new mount point within the watched dir?
  915. // pyinotify doesn't notify anyhing in this case, so we walk through all files within
  916. // this watched dir in DB and mark them deleted.
  917. // In case of h) of use cases, due to pyinotify behaviour of noticing mounted dir, we need to
  918. // compare agaisnt all files in cc_files table
  919. $watchDir = Application_Model_MusicDir::getDirByPath($rd);
  920. // get all the files that is under $dirPath
  921. $files = Application_Model_StoredFile::listAllFiles(
  922. $dir->getId(),$all=false);
  923. foreach ($files as $f) {
  924. // if the file is from this mount
  925. if (substr($f->getFilePath(), 0, strlen($rd)) === $rd) {
  926. $f->delete();
  927. }
  928. }
  929. if ($watchDir) {
  930. Application_Model_MusicDir::removeWatchedDir($rd, false);
  931. }
  932. }
  933. }
  934. }
  935. }
  936. // handles case where watched dir is missing
  937. public function handleWatchedDirMissingAction()
  938. {
  939. $request = $this->getRequest();
  940. $dir = base64_decode($request->getParam('dir'));
  941. Application_Model_MusicDir::removeWatchedDir($dir, false);
  942. }
  943. /* This action is for use by our dev scripts, that make
  944. * a change to the database and we want rabbitmq to send
  945. * out a message to pypo that a potential change has been made. */
  946. public function rabbitmqDoPushAction()
  947. {
  948. Logging::info("Notifying RabbitMQ to send message to pypo");
  949. Application_Model_RabbitMq::SendMessageToPypo("reset_liquidsoap_bootstrap", array());
  950. Application_Model_RabbitMq::PushSchedule();
  951. }
  952. public function getBootstrapInfoAction()
  953. {
  954. $live_dj = Application_Model_Preference::GetSourceSwitchStatus('live_dj');
  955. $master_dj = Application_Model_Preference::GetSourceSwitchStatus('master_dj');
  956. $scheduled_play = Application_Model_Preference::GetSourceSwitchStatus('scheduled_play');
  957. $res = array("live_dj"=>$live_dj, "master_dj"=>$master_dj, "scheduled_play"=>$scheduled_play);
  958. $this->view->switch_status = $res;
  959. $this->view->station_name = Application_Model_Preference::GetStationName();
  960. $this->view->stream_label = Application_Model_Preference::GetStreamLabelFormat();
  961. $this->view->transition_fade = Application_Model_Preference::GetDefaultTransitionFade();
  962. }
  963. /* This is used but Liquidsoap to check authentication of live streams*/
  964. public function checkLiveStreamAuthAction()
  965. {
  966. $request = $this->getRequest();
  967. $username = $request->getParam('username');
  968. $password = $request->getParam('password');
  969. $djtype = $request->getParam('djtype');
  970. if ($djtype == 'master') {
  971. //check against master
  972. if ($username == Application_Model_Preference::GetLiveStreamMasterUsername()
  973. && $password == Application_Model_Preference::GetLiveStreamMasterPassword()) {
  974. $this->view->msg = true;
  975. } else {
  976. $this->view->msg = false;
  977. }
  978. } elseif ($djtype == "dj") {
  979. //check against show dj auth
  980. $showInfo = Application_Model_Show::getCurrentShow();
  981. // there is current playing show
  982. if (isset($showInfo[0]['id'])) {
  983. $current_show_id = $showInfo[0]['id'];
  984. $CcShow = CcShowQuery::create()->findPK($current_show_id);
  985. // get custom pass info from the show
  986. $custom_user = $CcShow->getDbLiveStreamUser();
  987. $custom_pass = $CcShow->getDbLiveStreamPass();
  988. // get hosts ids
  989. $show = new Application_Model_Show($current_show_id);
  990. $hosts_ids = $show->getHostsIds();
  991. // check against hosts auth
  992. if ($CcShow->getDbLiveStreamUsingAirtimeAuth()) {
  993. foreach ($hosts_ids as $host) {
  994. $h = new Application_Model_User($host['subjs_id']);
  995. if ($username == $h->getLogin() && md5($password) == $h->getPassword()) {
  996. $this->view->msg = true;
  997. return;
  998. }
  999. }
  1000. }
  1001. // check against custom auth
  1002. if ($CcShow->getDbLiveStreamUsingCustomAuth()) {
  1003. if ($username == $custom_user && $password == $custom_pass) {
  1004. $this->view->msg = true;
  1005. } else {
  1006. $this->view->msg = false;
  1007. }
  1008. } else {
  1009. $this->view->msg = false;
  1010. }
  1011. } else {
  1012. // no show is currently playing
  1013. $this->view->msg = false;
  1014. }
  1015. }
  1016. }
  1017. /* This action is for use by our dev scripts, that make
  1018. * a change to the database and we want rabbitmq to send
  1019. * out a message to pypo that a potential change has been made. */
  1020. public function getFilesWithoutReplayGainAction()
  1021. {
  1022. $dir_id = $this->_getParam('dir_id');
  1023. //connect to db and get get sql
  1024. $rows = Application_Model_StoredFile::listAllFiles2($dir_id, 100);
  1025. $this->_helper->json->sendJson($rows);
  1026. }
  1027. public function getFilesWithoutSilanValueAction()
  1028. {
  1029. //connect to db and get get sql
  1030. $rows = Application_Model_StoredFile::getAllFilesWithoutSilan();
  1031. $this->_helper->json->sendJson($rows);
  1032. }
  1033. public function updateReplayGainValueAction()
  1034. {
  1035. $request = $this->getRequest();
  1036. $data = json_decode($request->getParam('data'));
  1037. foreach ($data as $pair) {
  1038. list($id, $gain) = $pair;
  1039. // TODO : move this code into model -- RG
  1040. $file = Application_Model_StoredFile::RecallById($p_id = $id)->getPropelOrm();
  1041. $file->setDbReplayGain($gain);
  1042. $file->save();
  1043. }
  1044. $this->_helper->json->sendJson(array());
  1045. }
  1046. public function updateCueValuesBySilanAction()
  1047. {
  1048. $request = $this->getRequest();
  1049. $data = json_decode($request->getParam('data'), $assoc = true);
  1050. foreach ($data as $pair) {
  1051. list($id, $info) = $pair;
  1052. // TODO : move this code into model -- RG
  1053. $file = Application_Model_StoredFile::RecallById($p_id = $id)->getPropelOrm();
  1054. //What we are doing here is setting a more accurate length that was
  1055. //calculated with silan by actually scanning the entire file. This
  1056. //process takes a really long time, and so we only do it in the background
  1057. //after the file has already been imported -MK
  1058. try {
  1059. $length = $file->getDbLength();
  1060. if (isset($info['length'])) {
  1061. $length = $info['length'];
  1062. //length decimal number in seconds. Need to convert it to format
  1063. //HH:mm:ss to get around silly PHP limitations.
  1064. $length = Application_Common_DateHelper::secondsToPlaylistTime($length);
  1065. $file->setDbLength($length);
  1066. }
  1067. $cuein = isset($info['cuein']) ? $info['cuein'] : 0;
  1068. $cueout = isset($info['cueout']) ? $info['cueout'] : $length;
  1069. $file->setDbCuein($cuein);
  1070. $file->setDbCueout($cueout);
  1071. $file->setDbSilanCheck(true);
  1072. $file->save();
  1073. } catch (Exception $e) {
  1074. Logging::info("Failed to update silan values for ".$file->getDbTrackTitle());
  1075. Logging::info("File length analyzed by Silan is: ".$length);
  1076. //set silan_check to true so we don't attempt to re-anaylze again
  1077. $file->setDbSilanCheck(true);
  1078. $file->save();
  1079. }
  1080. }
  1081. $this->_helper->json->sendJson(array());
  1082. }
  1083. public function notifyWebstreamDataAction()
  1084. {
  1085. $request = $this->getRequest();
  1086. $data = $request->getParam("data");
  1087. $media_id = intval($request->getParam("media_id"));
  1088. $data_arr = json_decode($data);
  1089. //$media_id is -1 sometimes when a stream has stopped playing
  1090. if (!is_null($media_id) && $media_id > 0) {
  1091. if (isset($data_arr->title)) {
  1092. $data_title = substr($data_arr->title, 0, 1024);
  1093. $previous_metadata = CcWebstreamMetadataQuery::create()
  1094. ->orderByDbStartTime('desc')
  1095. ->filterByDbInstanceId($media_id)
  1096. ->findOne();
  1097. $do_insert = true;
  1098. if ($previous_metadata) {
  1099. if ($previous_metadata->getDbLiquidsoapData() == $data_title) {
  1100. Logging::debug("Duplicate found: ". $data_title);
  1101. $do_insert = false;
  1102. }
  1103. }
  1104. if ($do_insert) {
  1105. $startDT = new DateTime("now", new DateTimeZone("UTC"));
  1106. $webstream_metadata = new CcWebstreamMetadata();
  1107. $webstream_metadata->setDbInstanceId($media_id);
  1108. $webstream_metadata->setDbStartTime($startDT);
  1109. $webstream_metadata->setDbLiquidsoapData($data_title);
  1110. $webstream_metadata->save();
  1111. $historyService = new Application_Service_HistoryService();
  1112. $historyService->insertWebstreamMetadata($media_id, $startDT, $data_arr);
  1113. }
  1114. }
  1115. }
  1116. $this->view->response = $data;
  1117. $this->view->media_id = $media_id;
  1118. }
  1119. public function getStreamParametersAction() {
  1120. $streams = array("s1", "s2", "s3");
  1121. $stream_params = array();
  1122. foreach ($streams as $s) {
  1123. $stream_params[$s] =
  1124. Application_Model_StreamSetting::getStreamDataNormalized($s);
  1125. }
  1126. $this->view->stream_params = $stream_params;
  1127. }
  1128. public function pushStreamStatsAction() {
  1129. $request = $this->getRequest();
  1130. $data = json_decode($request->getParam("data"), true);
  1131. Application_Model_ListenerStat::insertDataPoints($data);
  1132. $this->view->data = $data;
  1133. }
  1134. public function updateStreamSettingTableAction() {
  1135. $request = $this->getRequest();
  1136. $data = json_decode($request->getParam("data"), true);
  1137. foreach ($data as $k=>$v) {
  1138. Application_Model_StreamSetting::SetListenerStatError($k, $v);
  1139. }
  1140. }
  1141. /**
  1142. * display played items for a given time range and show instance_id
  1143. *
  1144. * @return json array
  1145. */
  1146. public function itemHistoryFeedAction()
  1147. {
  1148. try {
  1149. $request = $this->getRequest();
  1150. $params = $request->getParams();
  1151. $instance = $request->getParam("instance_id", null);
  1152. list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request);
  1153. $historyService = new Application_Service_HistoryService();
  1154. $results = $historyService->getPlayedItemData($startsDT, $endsDT, $params, $instance);
  1155. $this->_helper->json->sendJson($results['history']);
  1156. }
  1157. catch (Exception $e) {
  1158. Logging::info($e);
  1159. Logging::info($e->getMessage());
  1160. }
  1161. }
  1162. /**
  1163. * display show schedules for a given time range and show instance_id
  1164. *
  1165. * @return json array
  1166. */
  1167. public function showHistoryFeedAction()
  1168. {
  1169. try {
  1170. $request = $this->getRequest();
  1171. $params = $request->getParams();
  1172. $userId = $request->getParam("user_id", null);
  1173. list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request);
  1174. $historyService = new Application_Service_HistoryService();
  1175. $shows = $historyService->getShowList($startsDT, $endsDT, $userId);
  1176. $this->_helper->json->sendJson($shows);
  1177. }
  1178. catch (Exception $e) {
  1179. Logging::info($e);
  1180. Logging::info($e->getMessage());
  1181. }
  1182. }
  1183. /**
  1184. * display show info (without schedule) for given show_id
  1185. *
  1186. * @return json array
  1187. */
  1188. public function showsAction()
  1189. {
  1190. try {
  1191. $request = $this->getRequest();
  1192. $params = $request->getParams();
  1193. $showId = $request->getParam("show_id", null);
  1194. $results = array();
  1195. if (empty($showId)) {
  1196. $shows = CcShowQuery::create()->find();
  1197. foreach($shows as $show) {
  1198. $results[] = $show->getShowInfo();
  1199. }
  1200. } else {
  1201. $show = CcShowQuery::create()->findPK($showId);
  1202. $results[] = $show->getShowInfo();
  1203. }
  1204. $this->_helper->json->sendJson($results);
  1205. }
  1206. catch (Exception $e) {
  1207. Logging::info($e);
  1208. Logging::info($e->getMessage());
  1209. }
  1210. }
  1211. /**
  1212. * display show schedule for given show_id
  1213. *
  1214. * @return json array
  1215. */
  1216. public function showSchedulesAction()
  1217. {
  1218. try {
  1219. $request = $this->getRequest();
  1220. $params = $request->getParams();
  1221. $showId = $request->getParam("show_id", null);
  1222. list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request);
  1223. if ((!isset($showId)) || (!is_numeric($showId))) {
  1224. //if (!isset($showId)) {
  1225. $this->_helper->json->sendJson(
  1226. array("jsonrpc" => "2.0", "error" => array("code" => 400, "message" => "missing invalid type for required show_id parameter. use type int.".$showId))
  1227. );
  1228. }
  1229. $shows = Application_Model_Show::getShows($startsDT, $endsDT, FALSE, $showId);
  1230. // is this a valid show?
  1231. if (empty($shows)) {
  1232. $this->_helper->json->sendJson(
  1233. array("jsonrpc" => "2.0", "error" => array("code" => 204, "message" => "no content for requested show_id"))
  1234. );
  1235. }
  1236. $this->_helper->json->sendJson($shows);
  1237. }
  1238. catch (Exception $e) {
  1239. Logging::info($e);
  1240. Logging::info($e->getMessage());
  1241. }
  1242. }
  1243. /**
  1244. * displays track listing for given instance_id
  1245. *
  1246. * @return json array
  1247. */
  1248. public function showTracksAction()
  1249. {
  1250. $baseUrl = Application_Common_OsPath::getBaseDir();
  1251. $prefTimezone = Application_Model_Preference::GetTimezone();
  1252. $instanceId = $this->_getParam('instance_id');
  1253. if ((!isset($instanceId)) || (!is_numeric($instanceId))) {
  1254. $this->_helper->json->sendJson(
  1255. array("jsonrpc" => "2.0", "error" => array("code" => 400, "message" => "missing invalid type for required instance_id parameter. use type int"))
  1256. );
  1257. }
  1258. $showInstance = new Application_Model_ShowInstance($instanceId);
  1259. $showInstanceContent = $showInstance->getShowListContent($prefTimezone);
  1260. // is this a valid show instance with content?
  1261. if (empty($showInstanceContent)) {
  1262. $this->_helper->json->sendJson(
  1263. array("jsonrpc" => "2.0", "error" => array("code" => 204, "message" => "no content for requested instance_id"))
  1264. );
  1265. }
  1266. $result = array();
  1267. $position = 0;
  1268. foreach ($showInstanceContent as $track) {
  1269. $elementMap = array(
  1270. 'title' => isset($track['track_title']) ? $track['track_title'] : "",
  1271. 'artist' => isset($track['creator']) ? $track['creator'] : "",
  1272. 'position' => $position,
  1273. 'id' => ++$position,
  1274. 'mime' => isset($track['mime'])?$track['mime']:"",
  1275. 'starts' => isset($track['starts']) ? $track['starts'] : "",
  1276. 'length' => isset($track['length']) ? $track['length'] : "",
  1277. 'file_id' => ($track['type'] == 0) ? $track['item_id'] : $track['filepath']
  1278. );
  1279. $result[] = $elementMap;
  1280. }
  1281. $this->_helper->json($result);
  1282. }
  1283. }