diff --git a/airtime_mvc/application/common/TuneIn.php b/airtime_mvc/application/common/TuneIn.php
index 21d0f8684..cbaf51871 100644
--- a/airtime_mvc/application/common/TuneIn.php
+++ b/airtime_mvc/application/common/TuneIn.php
@@ -17,12 +17,20 @@ class Application_Common_TuneIn
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
- curl_exec($ch);
+ $xmlResponse = 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));
}
curl_close($ch);
+ $xmlObj = new SimpleXMLElement($xmlResponse);
+ if (!$xmlObj || $xmlObj->head->status != "200") {
+ Logging::info("Error occurred pushing metadata to TuneIn:");
+ Logging::info($xmlResponse);
+ } else if ($xmlObj->head->status == "200") {
+ Application_Model_Preference::setLastTuneinMetadataUpdate(time());
+ }
+
}
private static function getCredentialsQueryString() {
@@ -33,20 +41,4 @@ class Application_Common_TuneIn
return "?partnerId=".$tuneInPartnerID."&partnerKey=".$tuneInPartnerKey."&id=".$tuneInStationID;
}
- public static function updateOfflineMetadata() {
- $credQryStr = self::getCredentialsQueryString();
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, TUNEIN_API_URL . $credQryStr . "&commercial=true");
- curl_setopt($ch, CURLOPT_FAILONERROR, 1);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
-
- 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));
- }
- curl_close($ch);
- }
-
}
diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php
index 1a784e366..d1940d76a 100644
--- a/airtime_mvc/application/controllers/ApiController.php
+++ b/airtime_mvc/application/controllers/ApiController.php
@@ -73,6 +73,7 @@ class ApiController extends Zend_Controller_Action
print _('You are not allowed to access this resource.');
exit;
}
+ return true;
}
public function versionAction()
@@ -157,7 +158,7 @@ class ApiController extends Zend_Controller_Action
*/
public function liveInfoAction()
{
- if (Application_Model_Preference::GetAllow3rdPartyApi()) {
+ if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
@@ -252,7 +253,7 @@ class ApiController extends Zend_Controller_Action
*/
public function liveInfoV2Action()
{
- if (Application_Model_Preference::GetAllow3rdPartyApi()) {
+ if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
@@ -360,7 +361,7 @@ class ApiController extends Zend_Controller_Action
public function weekInfoAction()
{
- if (Application_Model_Preference::GetAllow3rdPartyApi()) {
+ if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
@@ -479,7 +480,7 @@ class ApiController extends Zend_Controller_Action
*/
public function showLogoAction()
{
- if (Application_Model_Preference::GetAllow3rdPartyApi()) {
+ if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
$request = $this->getRequest();
$showId = $request->getParam('id');
@@ -510,7 +511,7 @@ class ApiController extends Zend_Controller_Action
*/
public function stationMetadataAction()
{
- if (Application_Model_Preference::GetAllow3rdPartyApi()) {
+ if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
@@ -549,7 +550,7 @@ class ApiController extends Zend_Controller_Action
*/
public function stationLogoAction()
{
- if (Application_Model_Preference::GetAllow3rdPartyApi()) {
+ if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
@@ -1515,5 +1516,30 @@ class ApiController extends Zend_Controller_Action
$this->_helper->json($result);
}
+
+ /**
+ * This function is called from PYPO (pypofetch) every 2 minutes and updates
+ * metadata on TuneIn if we haven't done so in the last 4 minutes. We have
+ * to do this because TuneIn turns off metadata if it has not received a
+ * request within 5 minutes. This is necessary for long tracks > 5 minutes.
+ */
+ public function updateMetadataOnTuneinAction()
+ {
+ if (!Application_Model_Preference::getTuneinEnabled()) {
+ $this->_helper->json->sendJson(array(0));
+ }
+
+ $lastTuneInMetadataUpdate = Application_Model_Preference::geLastTuneinMetadataUpdate();
+ if (time() - $lastTuneInMetadataUpdate >= 240) {
+ $metadata = $metadata = Application_Model_Schedule::getCurrentPlayingTrack();
+ if (!is_null($metadata)) {
+ Application_Common_TuneIn::sendMetadataToTunein(
+ $metadata["title"],
+ $metadata["artist"]
+ );
+ }
+ }
+ $this->_helper->json->sendJson(array(1));
+ }
}
diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php
index 741125422..c1d1e5f29 100644
--- a/airtime_mvc/application/controllers/PreferenceController.php
+++ b/airtime_mvc/application/controllers/PreferenceController.php
@@ -72,6 +72,7 @@ class PreferenceController extends Zend_Controller_Action
Application_Model_Preference::SetSoundCloudLicense($values["SoundCloudLicense"]);*/
$this->view->statusMsg = "
". _("Preferences updated.")."
";
+ $form = new Application_Form_Preferences();
$this->view->form = $form;
//$this->_helper->json->sendJson(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml')));
} else {
@@ -491,31 +492,42 @@ class PreferenceController extends Zend_Controller_Action
return;
}
- $user = Application_Model_User::getCurrentUser();
- $playlists = $blocks = $streams = [];
+ $this->deleteFutureScheduleItems();
+ $this->deleteCloudFiles();
+ $this->deleteStoredFiles();
- $allPlaylists = CcPlaylistQuery::create()->find();
- foreach ($allPlaylists as $p) {
- $playlists[] = $p->getDbId();
+ $this->getResponse()
+ ->setHttpResponseCode(200)
+ ->appendBody("OK");
+ }
+
+ private function deleteFutureScheduleItems() {
+ $utcTimezone = new DateTimeZone("UTC");
+ $nowDateTime = new DateTime("now", $utcTimezone);
+ $scheduleItems = CcScheduleQuery::create()
+ ->filterByDbEnds($nowDateTime->format("Y-m-d H:i:s"), Criteria::GREATER_THAN)
+ ->find();
+
+ // Delete all the schedule items
+ foreach ($scheduleItems as $i) {
+ // If this is the currently playing track, cancel the current show
+ if ($i->isCurrentItem()) {
+ $instanceId = $i->getDbInstanceId();
+ $instance = CcShowInstancesQuery::create()->findPk($instanceId);
+ $showId = $instance->getDbShowId();
+
+ // From ScheduleController
+ $scheduler = new Application_Model_Scheduler();
+ $scheduler->cancelShow($showId);
+ Application_Model_StoredFile::updatePastFilesIsScheduled();
+ }
+
+ $i->delete();
}
+ }
- $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());
-
+ private function deleteCloudFiles() {
try {
- // Delete all the cloud files
$CC_CONFIG = Config::getConfig();
foreach ($CC_CONFIG["supportedStorageBackends"] as $storageBackend) {
@@ -525,7 +537,9 @@ class PreferenceController extends Zend_Controller_Action
} catch(Exception $e) {
Logging::info($e->getMessage());
}
+ }
+ private function deleteStoredFiles() {
// Delete all files from the database
$files = CcFilesQuery::create()->find();
foreach ($files as $file) {
@@ -534,10 +548,6 @@ class PreferenceController extends Zend_Controller_Action
// every S3 file we delete.
$storedFile->delete(true);
}
-
- $this->getResponse()
- ->setHttpResponseCode(200)
- ->appendBody("OK");
}
}
diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php
index 8d7aa0587..1890ac200 100644
--- a/airtime_mvc/application/controllers/ScheduleController.php
+++ b/airtime_mvc/application/controllers/ScheduleController.php
@@ -315,13 +315,6 @@ class ScheduleController extends Zend_Controller_Action
{
$range = Application_Model_Schedule::GetPlayOrderRangeOld();
- // If there is no current track playing update TuneIn so it doesn't
- // display outdated metadata
- //TODO: find a better solution for this so we don't spam the station on TuneIn
- /*if (is_null($range["current"]) && Application_Model_Preference::getTuneinEnabled()) {
- Application_Common_TuneIn::updateOfflineMetadata();
- }*/
-
$show = Application_Model_Show::getCurrentShow();
/* Convert all UTC times to localtime before sending back to user. */
diff --git a/airtime_mvc/application/controllers/WhmcsLoginController.php b/airtime_mvc/application/controllers/WhmcsLoginController.php
index 0b8a00a83..221f4452d 100644
--- a/airtime_mvc/application/controllers/WhmcsLoginController.php
+++ b/airtime_mvc/application/controllers/WhmcsLoginController.php
@@ -239,7 +239,7 @@ class WHMCS_Auth_Adapter implements Zend_Auth_Adapter_Interface {
}
else
{
- if ($product["status"] === "Active") {
+ if (($product["status"] === "Active") || ($product["status"] === "Suspended")) {
$airtimeProduct = $product;
$subdomain = '';
diff --git a/airtime_mvc/application/forms/TuneInPreferences.php b/airtime_mvc/application/forms/TuneInPreferences.php
index d4eebc32c..c4cab8fb6 100644
--- a/airtime_mvc/application/forms/TuneInPreferences.php
+++ b/airtime_mvc/application/forms/TuneInPreferences.php
@@ -18,7 +18,6 @@ class Application_Form_TuneInPreferences extends Zend_Form_SubForm
$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");
@@ -76,7 +75,6 @@ class Application_Form_TuneInPreferences extends Zend_Form_SubForm
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;
}
}
@@ -85,8 +83,10 @@ class Application_Form_TuneInPreferences extends Zend_Form_SubForm
if ($valid) {
$xmlObj = new SimpleXMLElement($xmlData);
if (!$xmlObj || $xmlObj->head->status != "200") {
+ $this->getElement("enable_tunein")->setErrors(array(_("Invalid TuneIn Settings. Please ensure your TuneIn settings are correct and try again.")));
$valid = false;
} else if ($xmlObj->head->status == "200") {
+ Application_Model_Preference::setLastTuneinMetadataUpdate(time());
$valid = true;
}
}
diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php
index 6e9de3d39..5ba1a9874 100644
--- a/airtime_mvc/application/models/Preference.php
+++ b/airtime_mvc/application/models/Preference.php
@@ -1513,4 +1513,14 @@ class Application_Model_Preference
{
return self::getValue("tunein_station_id");
}
+
+ public static function geLastTuneinMetadataUpdate()
+ {
+ return self::getValue("last_tunein_metadata_update");
+ }
+
+ public static function setLastTuneinMetadataUpdate($value)
+ {
+ self::setValue("last_tunein_metadata_update", $value);
+ }
}
diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php
index d2e114243..75123e5d3 100644
--- a/airtime_mvc/application/models/Schedule.php
+++ b/airtime_mvc/application/models/Schedule.php
@@ -199,7 +199,6 @@ SQL;
$currentMedia["ends"] = $currentMedia["show_ends"];
}
- $currentMediaScheduleId = $currentMedia["id"];
$currentMediaFileId = $currentMedia["file_id"];
$currentMediaStreamId = $currentMedia["stream_id"];
if (isset($currentMediaFileId)) {
@@ -234,9 +233,10 @@ SQL;
);
$previousMedia = CcScheduleQuery::create()
- ->filterByDbId($currentMediaScheduleId-1)
+ ->filterByDbStarts($currentMedia["starts"], Criteria::LESS_THAN)
+ ->filterByDbId($currentMedia["id"], Criteria::NOT_EQUAL)
->filterByDbPlayoutStatus(0, Criteria::GREATER_THAN)
- ->orderByDbStarts()
+ ->orderByDbStarts(Criteria::DESC)
->findOne();
if (isset($previousMedia)) {
$previousMediaFileId = $previousMedia->getDbFileId();
@@ -253,10 +253,6 @@ SQL;
$previousWebstream = CcWebstreamQuery::create()
->filterByDbId($previousMediaStreamId)
->findOne();
- /*$previousWebstreamMetadata = CcWebstreamMetadataQuery::create()
- ->filterByDbInstanceId($previousMedia->getDbInstanceId())
- ->orderByDbStartTime(Criteria::DESC)
- ->findOne();*/
$previousMediaName = $previousWebstream->getDbName();
} else {
$previousMediaType = null;
@@ -270,8 +266,9 @@ SQL;
}
$nextMedia = CcScheduleQuery::create()
- ->filterByDbId($currentMediaScheduleId+1)
- ->orderByDbStarts()
+ ->filterByDbStarts($currentMedia["starts"], Criteria::GREATER_THAN)
+ ->filterByDbId($currentMedia["id"], Criteria::NOT_EQUAL)
+ ->orderByDbStarts(Criteria::ASC)
->findOne();
if (isset($nextMedia)) {
$nextMediaFileId = $nextMedia->getDbFileId();
@@ -287,10 +284,6 @@ SQL;
$nextWebstream = CcWebstreamQuery::create()
->filterByDbId($nextMediaStreamId)
->findOne();
- /*$nextWebstreamMetadata = CcWebstreamMetadataQuery::create()
- ->filterByDbInstanceId($nextMedia->getDbInstanceId())
- ->orderByDbStartTime(Criteria::DESC)
- ->findOne();*/
$nextMediaName = $nextWebstream->getDbName();
} else {
$nextMediaType = null;
diff --git a/python_apps/api_clients/api_clients/api_client.py b/python_apps/api_clients/api_clients/api_client.py
index 6710c959b..4eeea61d9 100644
--- a/python_apps/api_clients/api_clients/api_client.py
+++ b/python_apps/api_clients/api_clients/api_client.py
@@ -85,6 +85,7 @@ api_config['get_files_without_silan_value'] = 'get-files-without-silan-value/api
api_config['update_cue_values_by_silan'] = 'update-cue-values-by-silan/api_key/%%api_key%%'
api_config['api_base'] = 'api'
api_config['bin_dir'] = '/usr/lib/airtime/api_clients/'
+api_config['update_metadata_on_tunein'] = 'update-metadata-on-tunein/api_key/%%api_key%%'
@@ -532,6 +533,9 @@ class AirtimeApiClient(object):
#TODO
self.logger.error(str(e))
+ def update_metadata_on_tunein(self):
+ self.services.update_metadata_on_tunein()
+
class InvalidContentType(Exception):
pass
diff --git a/python_apps/pypo/pypo/pypofetch.py b/python_apps/pypo/pypo/pypofetch.py
index 530752454..3772e170c 100644
--- a/python_apps/pypo/pypo/pypofetch.py
+++ b/python_apps/pypo/pypo/pypofetch.py
@@ -14,7 +14,7 @@ import traceback
import pure
from Queue import Empty
-from threading import Thread
+from threading import Thread, Timer
from subprocess import Popen, PIPE
from api_clients import api_client
@@ -441,6 +441,12 @@ class PypoFetch(Thread):
return success
+ # This function makes a request to Airtime to see if we need to
+ # push metadata to TuneIn. We have to do this because TuneIn turns
+ # off metadata if it does not receive a request every 5 minutes.
+ def update_metadata_on_tunein(self):
+ self.api_client.update_metadata_on_tunein()
+ Timer(120, self.update_metadata_on_tunein).start()
def main(self):
#Make sure all Liquidsoap queues are empty. This is important in the
@@ -452,8 +458,10 @@ class PypoFetch(Thread):
self.set_bootstrap_variables()
+ self.update_metadata_on_tunein()
+
# Bootstrap: since we are just starting up, we need to grab the
- # most recent schedule. After that we fetch the schedule every 30
+ # most recent schedule. After that we fetch the schedule every 8
# minutes or wait for schedule updates to get pushed.
success = self.persistent_manual_schedule_fetch(max_attempts=5)
@@ -463,6 +471,7 @@ class PypoFetch(Thread):
loops = 1
while True:
self.logger.info("Loop #%s", loops)
+ manual_fetch_needed = False
try:
"""
our simple_queue.get() requires a timeout, in which case we
@@ -478,17 +487,26 @@ class PypoFetch(Thread):
Currently we are checking every POLL_INTERVAL seconds
"""
-
message = self.fetch_queue.get(block=True, timeout=self.listener_timeout)
+ manual_fetch_needed = False
self.handle_message(message)
- except Empty, e:
+ except Empty as e:
self.logger.info("Queue timeout. Fetching schedule manually")
- self.persistent_manual_schedule_fetch(max_attempts=5)
- except Exception, e:
+ manual_fetch_needed = True
+ except Exception as e:
top = traceback.format_exc()
self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top)
+ try:
+ if manual_fetch_needed:
+ self.persistent_manual_schedule_fetch(max_attempts=5)
+ except Exception as e:
+ top = traceback.format_exc()
+ self.logger.error('Failed to manually fetch the schedule.')
+ self.logger.error('Exception: %s', e)
+ self.logger.error("traceback: %s", top)
+
loops += 1
def run(self):
@@ -496,3 +514,4 @@ class PypoFetch(Thread):
Entry point of the thread
"""
self.main()
+ self.logger.info('PypoFetch thread exiting')
diff --git a/python_apps/pypo/pypo/pypofile.py b/python_apps/pypo/pypo/pypofile.py
index 891fcb882..ff2103df2 100644
--- a/python_apps/pypo/pypo/pypofile.py
+++ b/python_apps/pypo/pypo/pypofile.py
@@ -30,6 +30,7 @@ class PypoFile(Thread):
self.media_queue = schedule_queue
self.media = None
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
+ self._config = self.read_config_file(CONFIG_PATH)
def copy_file(self, media_item):
"""
@@ -60,11 +61,9 @@ class PypoFile(Thread):
if do_copy:
self.logger.debug("copying from %s to local cache %s" % (src, dst))
try:
- config = self.read_config_file(CONFIG_PATH)
CONFIG_SECTION = "general"
- username = config.get(CONFIG_SECTION, 'api_key')
-
- host = config.get(CONFIG_SECTION, 'base_url')
+ username = self._config.get(CONFIG_SECTION, 'api_key')
+ host = self._config.get(CONFIG_SECTION, 'base_url')
url = "http://%s/rest/media/%s/download" % (host, media_item["id"])
with open(dst, "wb") as handle:
response = requests.get(url, auth=requests.auth.HTTPBasicAuth(username, ''), stream=True, verify=False)
@@ -205,3 +204,4 @@ class PypoFile(Thread):
Entry point of the thread
"""
self.main()
+