diff --git a/airtime_mvc/application/common/ProvisioningHelper.php b/airtime_mvc/application/common/ProvisioningHelper.php index 580c20e52..605617105 100644 --- a/airtime_mvc/application/common/ProvisioningHelper.php +++ b/airtime_mvc/application/common/ProvisioningHelper.php @@ -10,7 +10,7 @@ class ProvisioningHelper // Parameter values private $dbuser, $dbpass, $dbname, $dbhost, $dbowner, $apikey; private $instanceId; - private $station_name, $description; + private $stationName, $description; public function __construct($apikey) { @@ -40,18 +40,14 @@ class ProvisioningHelper if ($this->dbhost && !empty($this->dbhost)) { $this->setNewDatabaseConnection(); - //if ($this->checkDatabaseExists()) { - // throw new Exception("ERROR: Airtime database already exists"); - //} - if (!$this->checkDatabaseExists()) { - throw new Exception("ERROR: $this->dbname database does not exist."); + throw new DatabaseDoesNotExistException("ERROR: $this->dbname database does not exist."); } //We really want to do this check because all the Propel-generated SQL starts with "DROP TABLE IF EXISTS". //If we don't check, then a second call to this API endpoint would wipe all the tables! if ($this->checkTablesExist()) { - throw new Exception("ERROR: airtime tables already exists"); + throw new DatabaseAlreadyExistsException(); } $this->createDatabaseTables(); @@ -63,11 +59,19 @@ class ProvisioningHelper //All we need to do is create the database tables. $this->initializePrefs(); - } catch (Exception $e) { + } catch (DatabaseDoesNotExistException $e) { http_response_code(400); Logging::error($e->getMessage()); echo $e->getMessage() . PHP_EOL; return; + } catch (DatabaseAlreadyExistsException $e) { + // When we recreate a terminated instance, the process will fail + // if we return a 40x response here. In order to circumvent this, + // just return a 200; we still avoid dropping the existing tables + http_response_code(200); + Logging::info($e->getMessage()); + echo $e->getMessage() . PHP_EOL; + return; } http_response_code(201); @@ -108,7 +112,7 @@ class ProvisioningHelper $this->dbowner = $_POST['dbowner']; $this->instanceId = $_POST['instanceid']; - $this->station_name = $_POST['station_name']; + $this->stationName = $_POST['station_name']; $this->description = $_POST['description']; } @@ -194,8 +198,8 @@ class ProvisioningHelper * Initialize preference values passed from the dashboard (if any exist) */ private function initializePrefs() { - if ($this->station_name) { - Application_Model_Preference::SetStationName($this->station_name); + if ($this->stationName) { + Application_Model_Preference::SetStationName($this->stationName); } if ($this->description) { Application_Model_Preference::SetStationDescription($this->description); @@ -203,3 +207,14 @@ class ProvisioningHelper } } + +class DatabaseAlreadyExistsException extends Exception { + private static $_defaultMessage = "ERROR: airtime tables already exists"; + public function __construct($message = null, $code = 0, Exception $previous = null) { + $message = _((is_null($message) ? self::$_defaultMessage : $message)); + parent::__construct($message, $code, $previous); + } +} + +class DatabaseDoesNotExistException extends Exception {} + diff --git a/airtime_mvc/application/common/TuneIn.php b/airtime_mvc/application/common/TuneIn.php new file mode 100644 index 000000000..6040fb184 --- /dev/null +++ b/airtime_mvc/application/common/TuneIn.php @@ -0,0 +1,48 @@ +setLastPlayedTime($now); + + // Push metadata to TuneIn + if (Application_Model_Preference::getTuneinEnabled()) { + $filePropelOrm = $file->getPropelOrm(); + $title = urlencode($filePropelOrm->getDbTrackTitle()); + $artist = urlencode($filePropelOrm->getDbArtistName()); + Application_Common_TuneIn::sendMetadataToTunein($title, $artist); + } } } else { // webstream diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index 2de1529c8..741125422 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -57,14 +57,19 @@ class PreferenceController extends Zend_Controller_Action Application_Model_Preference::SetStationLogo($imagePath); } - Application_Model_Preference::SetUploadToSoundcloudOption($values["UploadToSoundcloudOption"]); + Application_Model_Preference::setTuneinEnabled($values["enable_tunein"]); + Application_Model_Preference::setTuneinStationId($values["tunein_station_id"]); + Application_Model_Preference::setTuneinPartnerKey($values["tunein_partner_key"]); + Application_Model_Preference::setTuneinPartnerId($values["tunein_partner_id"]); + + /*Application_Model_Preference::SetUploadToSoundcloudOption($values["UploadToSoundcloudOption"]); Application_Model_Preference::SetSoundCloudDownloadbleOption($values["SoundCloudDownloadbleOption"]); Application_Model_Preference::SetSoundCloudUser($values["SoundCloudUser"]); Application_Model_Preference::SetSoundCloudPassword($values["SoundCloudPassword"]); Application_Model_Preference::SetSoundCloudTags($values["SoundCloudTags"]); Application_Model_Preference::SetSoundCloudGenre($values["SoundCloudGenre"]); Application_Model_Preference::SetSoundCloudTrackType($values["SoundCloudTrackType"]); - Application_Model_Preference::SetSoundCloudLicense($values["SoundCloudLicense"]); + Application_Model_Preference::SetSoundCloudLicense($values["SoundCloudLicense"]);*/ $this->view->statusMsg = "
". _("Preferences updated.")."
"; $this->view->form = $form; @@ -183,9 +188,14 @@ class PreferenceController extends Zend_Controller_Action $num_of_stream = intval(Application_Model_Preference::GetNumOfStreams()); $form = new Application_Form_StreamSetting(); - $form->addElement('hash', 'csrf', array( - 'salt' => 'unique' - )); + // $form->addElement('hash', 'csrf', array( + // 'salt' => 'unique' + // )); + + $csrf_namespace = new Zend_Session_Namespace('csrf_namespace'); + $csrf_element = new Zend_Form_Element_Hidden('csrf'); + $csrf_element->setValue($csrf_namespace->authtoken)->setRequired('true')->removeDecorator('HtmlTag')->removeDecorator('Label'); + $form->addElement($csrf_element); $form->setSetting($setting); $form->startFrom(); @@ -465,4 +475,69 @@ class PreferenceController extends Zend_Controller_Action } $this->_helper->json->sendJson($out); } + + public function deleteAllFilesAction() + { + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + // Only admin users should get here through ACL permissioning + // Only allow POST requests + $method = $_SERVER['REQUEST_METHOD']; + if (!($method == 'POST')) { + $this->getResponse() + ->setHttpResponseCode(405) + ->appendBody(_("Request method not accepted") . ": $method"); + return; + } + + $user = Application_Model_User::getCurrentUser(); + $playlists = $blocks = $streams = []; + + $allPlaylists = CcPlaylistQuery::create()->find(); + foreach ($allPlaylists as $p) { + $playlists[] = $p->getDbId(); + } + + $allBlocks = CcBlockQuery::create()->find(); + foreach ($allBlocks as $b) { + $blocks[] = $b->getDbId(); + } + + $allStreams = CcWebstreamQuery::create()->find(); + foreach ($allStreams as $s) { + $streams[] = $s->getDbId(); + } + + // Delete all playlists, blocks, and streams + Application_Model_Playlist::deletePlaylists($playlists, $user->getId()); + Application_Model_Block::deleteBlocks($blocks, $user->getId()); + Application_Model_Webstream::deleteStreams($streams, $user->getId()); + + try { + // Delete all the cloud files + $CC_CONFIG = Config::getConfig(); + + foreach ($CC_CONFIG["supportedStorageBackends"] as $storageBackend) { + $proxyStorageBackend = new ProxyStorageBackend($storageBackend); + $proxyStorageBackend->deleteAllCloudFileObjects(); + } + } catch(Exception $e) { + Logging::info($e->getMessage()); + } + + // Delete all files from the database + $files = CcFilesQuery::create()->find(); + foreach ($files as $file) { + $storedFile = new Application_Model_StoredFile($file, null); + // Delete the files quietly to avoid getting Sentry errors for + // every S3 file we delete. + $storedFile->delete(true); + } + + $this->getResponse() + ->setHttpResponseCode(200) + ->appendBody("OK"); + } + } diff --git a/airtime_mvc/application/controllers/ProvisioningController.php b/airtime_mvc/application/controllers/ProvisioningController.php index cda59e0db..06ee7a2ab 100644 --- a/airtime_mvc/application/controllers/ProvisioningController.php +++ b/airtime_mvc/application/controllers/ProvisioningController.php @@ -57,13 +57,16 @@ class ProvisioningController extends Zend_Controller_Action /** * Delete the Airtime Pro station's files from Amazon S3 + * + * FIXME: When we deploy this next time, we should ensure that + * this function can only be accessed with POST requests! */ public function terminateAction() { $this->view->layout()->disableLayout(); $this->_helper->viewRenderer->setNoRender(true); - if (!RestAuth::verifyAuth(true, true, $this)) { + if (!RestAuth::verifyAuth(true, false, $this)) { return; } diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index d8d0f1f42..8d7aa0587 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -1,4 +1,5 @@ setDecorators(array( + array('ViewScript', array('viewScript' => 'form/preferences_danger.phtml')) + )); + + $clearLibrary = new Zend_Form_Element_Button('clear_library'); + $clearLibrary->setLabel(_('Delete All Tracks in Library')); + //$submit->removeDecorator('Label'); + $clearLibrary->setAttribs(array('class'=>'btn centered')); + $clearLibrary->setAttrib('onclick', 'deleteAllFiles();'); + $clearLibrary->removeDecorator('DtDdWrapper'); + + $this->addElement($clearLibrary); + } + +} diff --git a/airtime_mvc/application/forms/Preferences.php b/airtime_mvc/application/forms/Preferences.php index 3e5158413..d410ed3c6 100644 --- a/airtime_mvc/application/forms/Preferences.php +++ b/airtime_mvc/application/forms/Preferences.php @@ -12,18 +12,30 @@ class Application_Form_Preferences extends Zend_Form $general_pref = new Application_Form_GeneralPreferences(); - $this->addElement('hash', 'csrf', array( - 'salt' => 'unique', - 'decorators' => array( - 'ViewHelper' - ) - )); + // $this->addElement('hash', 'csrf', array( + // 'salt' => 'unique', + // 'decorators' => array( + // 'ViewHelper' + // ) + // )); + + $csrf_namespace = new Zend_Session_Namespace('csrf_namespace'); + $csrf_element = new Zend_Form_Element_Hidden('csrf'); + $csrf_element->setValue($csrf_namespace->authtoken)->setRequired('true')->removeDecorator('HtmlTag')->removeDecorator('Label'); + $this->addElement($csrf_element); $this->addSubForm($general_pref, 'preferences_general'); + //tunein form + $tuneinPreferences = new Application_Form_TuneInPreferences(); + $this->addSubForm($tuneinPreferences, 'preferences_tunein'); + $soundcloud_pref = new Application_Form_SoundcloudPreferences(); $this->addSubForm($soundcloud_pref, 'preferences_soundcloud'); + $danger_pref = new Application_Form_DangerousPreferences(); + $this->addSubForm($danger_pref, 'preferences_danger'); + $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel(_('Save')); //$submit->removeDecorator('Label'); diff --git a/airtime_mvc/application/forms/TuneInPreferences.php b/airtime_mvc/application/forms/TuneInPreferences.php new file mode 100644 index 000000000..7eee0d56f --- /dev/null +++ b/airtime_mvc/application/forms/TuneInPreferences.php @@ -0,0 +1,144 @@ +setDecorators(array( + array('ViewScript', array('viewScript' => 'form/preferences_tunein.phtml')) + )); + + $enableTunein = new Zend_Form_Element_Checkbox("enable_tunein"); + $enableTunein->setDecorators(array( + 'ViewHelper', + 'Errors', + 'Label' + )); + $enableTunein->addDecorator('Label', array('class' => 'enable-tunein')); + $enableTunein->setLabel(_("Push metadata to your station on TuneIn?")); + $enableTunein->setValue(Application_Model_Preference::getTuneinEnabled()); + $enableTunein->setAttrib("class", "block-display"); + $this->addElement($enableTunein); + + $tuneinStationId = new Zend_Form_Element_Text("tunein_station_id"); + $tuneinStationId->setLabel(_("Station ID:")); + $tuneinStationId->setValue(Application_Model_Preference::getTuneinStationId()); + $tuneinStationId->setAttrib("class", "input_text"); + $this->addElement($tuneinStationId); + + $tuneinPartnerKey = new Zend_Form_Element_Text("tunein_partner_key"); + $tuneinPartnerKey->setLabel(_("Partner Key:")); + $tuneinPartnerKey->setValue(Application_Model_Preference::getTuneinPartnerKey()); + $tuneinPartnerKey->setAttrib("class", "input_text"); + $this->addElement($tuneinPartnerKey); + + $tuneinPartnerId = new Zend_Form_Element_Text("tunein_partner_id"); + $tuneinPartnerId->setLabel(_("Partner Id:")); + $tuneinPartnerId->setValue(Application_Model_Preference::getTuneinPartnerId()); + $tuneinPartnerId->setAttrib("class", "input_text"); + $this->addElement($tuneinPartnerId); + } + + public function isValid($data) + { + $valid = true; + // Make request to TuneIn API to test the settings are valid. + // TuneIn does not have an API to make test requests to check if + // the credentials are correct. Therefore we will make a request + // with the commercial flag set to true, which removes the metadata + // from the station on TuneIn. After that, and if the test request + // succeeds, we will make another request with the real metadata. + if ($data["enable_tunein"]) { + $credentialsQryStr = "?partnerId=".$data["tunein_partner_id"]."&partnerKey=".$data["tunein_partner_key"]."&id=".$data["tunein_station_id"]; + $commercialFlagQryStr = "&commercial=true"; + + $metadata = Application_Model_Schedule::getCurrentPlayingTrack(); + + if (is_null($metadata)) { + $qryStr = $credentialsQryStr . $commercialFlagQryStr; + } else { + $metadata["artist"] = empty($metadata["artist"]) ? "n/a" : $metadata["artist"]; + $metadata["title"] = empty($metadata["title"]) ? "n/a" : $metadata["title"]; + $metadataQryStr = "&artist=" . $metadata["artist"] . "&title=" . $metadata["title"]; + + $qryStr = $credentialsQryStr . $metadataQryStr; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, TUNEIN_API_URL . $qryStr); + curl_setopt($ch, CURLOPT_FAILONERROR, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + + $xmlData = curl_exec($ch); + if (curl_error($ch)) { + Logging::error("Failed to reach TuneIn: ". curl_errno($ch)." - ". curl_error($ch) . " - " . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); + if (curl_error($ch) == "The requested URL returned error: 403 Forbidden") { + $this->getElement("enable_tunein")->setErrors(array(_("Invalid TuneIn Settings. Please ensure your TuneIn settings are correct and try again."))); + + $valid = false; + } + } + curl_close($ch); + + if ($valid) { + $xmlObj = new SimpleXMLElement($xmlData); + if (!$xmlObj || $xmlObj->head->status != "200") { + $valid = false; + } else if ($xmlObj->head->status == "200") { + $valid = true; + + // Make another request to TuneIn to update the metadata right away + // and to turn off the commercial flag. + + /*$metadata = Application_Model_Schedule::getCurrentPlayingTrack(); + + if (!is_null($metadata)) { + + Logging::info($metadata); + // Replace empty strings with "n/a" since the TuneIn API will complain + // and return an error that title and/or artist is not set. + $metadata["artist"] = empty($metadata["artist"]) ? "n/a" : $metadata["artist"]; + $metadata["title"] = empty($metadata["title"]) ? "n/a" : $metadata["title"]; + Logging::info($metadata); + + $metadataQryStr = "&artist=" . $metadata["artist"] . "&title=" . $metadata["title"]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, TUNEIN_API_URL . $qry_str . "&commercial=false" . $metadataQryStr); + curl_setopt($ch, CURLOPT_FAILONERROR, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + + $xmlData = curl_exec($ch); + Logging::info($xmlData); + if (curl_error($ch)) { + Logging::error("Failed to reach TuneIn: " . curl_errno($ch) . " - " . curl_error($ch) . " - " . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); + } + + curl_close($ch); + $xmlObj = new SimpleXMLElement($xmlData); + if (!$xmlObj || $xmlObj->head->status != "200") { + Logging::error("Failed updating metadata on TuneIn"); + } + }*/ + + } + } + } else { + $valid = true; + } + + if (!$valid) { + // Set values to what the user entered since the form is invalid so they + // don't have to enter in the values again and can see what they entered. + $this->getElement("enable_tunein")->setValue($data["enable_tunein"]); + $this->getElement("tunein_partner_key")->setValue($data["tunein_partner_key"]); + $this->getElement("tunein_partner_id")->setValue($data["tunein_partner_id"]); + $this->getElement("tunein_station_id")->setValue($data["tunein_station_id"]); + } + + return $valid; + } +} diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index 64263b94c..1387d326b 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -1453,4 +1453,44 @@ class Application_Model_Preference { return self::getValue("provisioning_status"); } + + public static function setTuneinEnabled($value) + { + self::setValue("tunein_enabled", $value); + } + + public static function getTuneinEnabled() + { + return self::getValue("tunein_enabled"); + } + + public static function setTuneinPartnerKey($value) + { + self::setValue("tunein_partner_key", $value); + } + + public static function getTuneinPartnerKey() + { + return self::getValue("tunein_partner_key"); + } + + public static function setTuneinPartnerId($value) + { + self::setValue("tunein_partner_id", $value); + } + + public static function getTuneinPartnerId() + { + return self::getValue("tunein_partner_id"); + } + + public static function setTuneinStationId($value) + { + self::setValue("tunein_station_id", $value); + } + + public static function getTuneinStationId() + { + return self::getValue("tunein_station_id"); + } } diff --git a/airtime_mvc/application/models/RabbitMq.php b/airtime_mvc/application/models/RabbitMq.php index 5daf227a2..fa05f3986 100644 --- a/airtime_mvc/application/models/RabbitMq.php +++ b/airtime_mvc/application/models/RabbitMq.php @@ -112,12 +112,7 @@ class Application_Model_RabbitMq $data['original_filename'] = $originalFilename; $data['callback_url'] = $callbackUrl; $data['api_key'] = $apiKey; - // Pass station name to the analyzer so we can set it with the file's - // metadata before uploading it to the cloud. This isn't a requirement - // for cloud storage, but put there as a safeguard, since all Airtime - // Pro stations will share the same bucket. - $data['station_domain'] = $stationDomain = Application_Model_Preference::GetStationName(); - + // We add a prefix to the resource name so files are not all placed // under the root folder. We do this in case we need to restore a // customer's file/s; File restoration is done via the S3 Browser diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 2993dc03d..66d00661b 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -56,6 +56,29 @@ SQL; return $real_streams; } + + /** + * Returns an array with 2 elements: artist and title name of the track that is currently playing. + * Elements will be set to null if metadata is not set for those fields. + * + * Returns null if no track is currently playing. + * + * Data is based on GetPlayOrderRange() in this class. + */ + public static function getCurrentPlayingTrack() + { + $currentScheduleInfo = self::GetPlayOrderRange(); + if (empty($currentScheduleInfo["tracks"]["current"])) { + return null; + } + + $currentTrackArray = explode(" - ", $currentScheduleInfo["tracks"]["current"]["name"]); + $currentTrackMetadata = array( + "artist" => empty($currentTrackArray[0]) ? null : urlencode($currentTrackArray[0]), + "title" => empty($currentTrackArray[1]) ? null : urlencode($currentTrackArray[1]) + ); + return $currentTrackMetadata; + } /** * Returns data related to the scheduled items. diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 8e3dd7a59..32d6ec371 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -375,7 +375,7 @@ SQL; * Deletes the physical file from the local file system or from the cloud * */ - public function delete() + public function delete($quiet=false) { // Check if the file is scheduled to be played in the future if (Application_Model_Schedule::IsFileScheduledInTheFuture($this->_file->getCcFileId())) { @@ -405,8 +405,12 @@ SQL; } catch (Exception $e) { - //Just log the exception and continue. - Logging::error($e); + if ($quiet) { + Logging::info($e); + } else { + //Just log the exception and continue. + Logging::error($e); + } } } diff --git a/airtime_mvc/application/views/scripts/form/preferences.phtml b/airtime_mvc/application/views/scripts/form/preferences.phtml index 0ffe1ea9d..d200bf627 100644 --- a/airtime_mvc/application/views/scripts/form/preferences.phtml +++ b/airtime_mvc/application/views/scripts/form/preferences.phtml @@ -2,8 +2,22 @@ element->getElement('csrf') ?> element->getSubform('preferences_general') ?> + +

+
+ element->getSubform('preferences_tunein') ?> +
+ + +

+
+ element->getSubform('preferences_danger') ?> +
+ +
+ element->submit->render() ?> diff --git a/airtime_mvc/application/views/scripts/form/preferences_danger.phtml b/airtime_mvc/application/views/scripts/form/preferences_danger.phtml new file mode 100644 index 000000000..7ea8a78d1 --- /dev/null +++ b/airtime_mvc/application/views/scripts/form/preferences_danger.phtml @@ -0,0 +1,14 @@ +
+
+ +
+

+ Warning: These functions will have permanent and lasting effects + on your Airtime station. Think carefully before using them! +

+
+ + element->getElement('clear_library')->render() ?> + +
+
diff --git a/airtime_mvc/application/views/scripts/form/preferences_tunein.phtml b/airtime_mvc/application/views/scripts/form/preferences_tunein.phtml new file mode 100644 index 000000000..d5fd34dd5 --- /dev/null +++ b/airtime_mvc/application/views/scripts/form/preferences_tunein.phtml @@ -0,0 +1,18 @@ +
+
+ + element->getElement("enable_tunein")->hasErrors()) { + echo $this->element->getElement('enable_tunein')->renderErrors(); + } + ?> + element->getElement('enable_tunein')->renderViewHelper() ?> + element->getElement('enable_tunein')->renderLabel() ?> + + element->getElement('tunein_station_id')->render() ?> + + element->getElement('tunein_partner_id')->render() ?> + + element->getElement('tunein_partner_key')->render() ?> + +
+
\ No newline at end of file diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index b80cbfe8e..4331af511 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -1788,7 +1788,7 @@ ul.errors { width:278px; } -ul.errors li { +ul.errors li, .warning { color:#902d2d; font-size:11px; padding:2px 4px; @@ -1798,6 +1798,11 @@ ul.errors li { list-style: none; } +.warning-label { + font-size: medium; + text-align: center; +} + div.success{ color:#3B5323; font-size:11px; @@ -2255,14 +2260,9 @@ dd.radio-inline-list, .preferences dd.radio-inline-list, .stream-config dd.radio .radio-inline-list label { margin-right:12px; } -.preferences.simple-formblock dd.block-display { - width: 100%; -} - -.preferences.simple-formblock dd.block-display select, .stream-config.simple-formblock dd.block-display select { - width: 100%; -} -.preferences dd.block-display .input_select, .stream-config dd.block-display .input_select { +.preferences.simple-formblock dd.block-display, + .preferences.simple-formblock dd.block-display select, .stream-config.simple-formblock dd.block-display select, + .preferences dd.block-display .input_select, .stream-config dd.block-display .input_select { width: 100%; } .preferences dd.block-display .input_text_area, .preferences dd.block-display .input_text @@ -2284,6 +2284,15 @@ dd.radio-inline-list, .preferences dd.radio-inline-list, .stream-config dd.radio margin-bottom: 4px; } +.preferences #Logo-img-container { + margin-top: 30px; +} + +.centered { + margin: 0 auto; + display: block; +} + #show_time_info { font-size:12px; height:30px; @@ -3248,3 +3257,7 @@ dd .stream-status { padding-bottom: 0px; padding-top: 13px; } + +.enable-tunein { + font-weight:bold; +} diff --git a/airtime_mvc/public/js/airtime/preferences/preferences.js b/airtime_mvc/public/js/airtime/preferences/preferences.js index 1dffe24d2..252a64a99 100644 --- a/airtime_mvc/public/js/airtime/preferences/preferences.js +++ b/airtime_mvc/public/js/airtime/preferences/preferences.js @@ -1,18 +1,13 @@ function showErrorSections() { - if($("#soundcloud-settings .errors").length > 0) { - $("#soundcloud-settings").show(); - $(window).scrollTop($("#soundcloud-settings .errors").position().top); - } - - if($("#email-server-settings .errors").length > 0) { - $("#email-server-settings").show(); - $(window).scrollTop($("#email-server-settings .errors").position().top); - } - - if($("#livestream-settings .errors").length > 0) { - $("#livestream-settings").show(); - $(window).scrollTop($("#livestream-settings .errors").position().top); - } + var selector = $("[id$=-settings]"); + selector.each(function(i) { + var el = $(this); + var errors = el.find(".errors"); + if (errors.length > 0) { + el.show(); + $(window).scrollTop(errors.position().top); + } + }); } function setConfigureMailServerListener() { @@ -63,6 +58,30 @@ function setMailServerInputReadonly() { setMsAuthenticationFieldsReadonly(requiresAuthCB); } +function setTuneInSettingsListener() { + var enableTunein = $("#enable_tunein"); + enableTunein.click(function(event){ + setTuneInSettingsReadonly(); + }); +} + +function setTuneInSettingsReadonly() { + var enableTunein = $("#enable_tunein"); + var stationId = $("#tunein_station_id"); + var partnerKey = $("#tunein_partner_key"); + var partnerId = $("#tunein_partner_id"); + + if (enableTunein.is(':checked')) { + stationId.removeAttr("readonly"); + partnerKey.removeAttr("readonly"); + partnerId.removeAttr("readonly"); + } else { + stationId.attr("readonly", "readonly"); + partnerKey.attr("readonly", "readonly"); + partnerId.attr("readonly", "readonly"); + } +} + /* * Enable/disable mail server authentication fields */ @@ -120,6 +139,14 @@ function removeLogo() { location.reload(); } +function deleteAllFiles() { + var resp = confirm($.i18n._("Are you sure you want to delete all the tracks in your library?")) + if (resp) { + $.post(baseUrl+'Preference/delete-all-files', function(json){}); + location.reload(); + } +} + $(document).ready(function() { $('.collapsible-header').live('click',function() { @@ -128,6 +155,10 @@ $(document).ready(function() { return false; }).next().hide(); + if ($("#tunein-settings").find(".errors").length > 0) { + $(".collapsible-content#tunein-settings").show(); + } + /* No longer using AJAX for this form. Zend + our code makes it needlessly hard to deal with. -- Albert $('#pref_save').live('click', function() { var data = $('#pref_form').serialize(); @@ -151,4 +182,6 @@ $(document).ready(function() { setConfigureMailServerListener(); setEnableSystemEmailsListener(); setCollapsibleWidgetJsCode(); + setTuneInSettingsReadonly(); + setTuneInSettingsListener(); }); diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index ccc3a0776..fb3452181 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -249,6 +249,7 @@ s = if dj_live_stream_port != 0 and dj_live_stream_mp != "" then on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)) + dj_live = on_metadata(notify_queue, dj_live) ignore(output.dummy(dj_live, fallible=true)) switch(id="show_schedule_noise_switch", @@ -271,6 +272,7 @@ s = if master_live_stream_port != 0 and master_live_stream_mp != "" then on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)) + master_dj = on_metadata(notify_queue, master_dj) ignore(output.dummy(master_dj, fallible=true)) switch(id="master_show_schedule_noise_switch", @@ -282,6 +284,8 @@ else s end +# Send metadata notifications when using master source +s = on_metadata(notify_queue, s) # Attach a skip command to the source s: #add_skip_command(s)