Merge branch 'saas-dev' into saas-installer-albert

Conflicts:
	python_apps/api_clients/api_clients/api_client.py
This commit is contained in:
Albert Santoni 2015-06-02 15:40:57 -04:00
commit 01ea6f27ae
11 changed files with 128 additions and 81 deletions

View File

@ -17,12 +17,20 @@ class Application_Common_TuneIn
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_exec($ch); $xmlResponse = curl_exec($ch);
if (curl_error($ch)) { if (curl_error($ch)) {
Logging::error("Failed to reach TuneIn: ". curl_errno($ch)." - ". curl_error($ch) . " - " . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); Logging::error("Failed to reach TuneIn: ". curl_errno($ch)." - ". curl_error($ch) . " - " . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
} }
curl_close($ch); 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() { private static function getCredentialsQueryString() {
@ -33,20 +41,4 @@ class Application_Common_TuneIn
return "?partnerId=".$tuneInPartnerID."&partnerKey=".$tuneInPartnerKey."&id=".$tuneInStationID; 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);
}
} }

View File

@ -73,6 +73,7 @@ class ApiController extends Zend_Controller_Action
print _('You are not allowed to access this resource.'); print _('You are not allowed to access this resource.');
exit; exit;
} }
return true;
} }
public function versionAction() public function versionAction()
@ -157,7 +158,7 @@ class ApiController extends Zend_Controller_Action
*/ */
public function liveInfoAction() public function liveInfoAction()
{ {
if (Application_Model_Preference::GetAllow3rdPartyApi()) { if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
@ -252,7 +253,7 @@ class ApiController extends Zend_Controller_Action
*/ */
public function liveInfoV2Action() public function liveInfoV2Action()
{ {
if (Application_Model_Preference::GetAllow3rdPartyApi()) { if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
@ -360,7 +361,7 @@ class ApiController extends Zend_Controller_Action
public function weekInfoAction() public function weekInfoAction()
{ {
if (Application_Model_Preference::GetAllow3rdPartyApi()) { if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
@ -479,7 +480,7 @@ class ApiController extends Zend_Controller_Action
*/ */
public function showLogoAction() public function showLogoAction()
{ {
if (Application_Model_Preference::GetAllow3rdPartyApi()) { if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
$request = $this->getRequest(); $request = $this->getRequest();
$showId = $request->getParam('id'); $showId = $request->getParam('id');
@ -510,7 +511,7 @@ class ApiController extends Zend_Controller_Action
*/ */
public function stationMetadataAction() public function stationMetadataAction()
{ {
if (Application_Model_Preference::GetAllow3rdPartyApi()) { if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
@ -549,7 +550,7 @@ class ApiController extends Zend_Controller_Action
*/ */
public function stationLogoAction() public function stationLogoAction()
{ {
if (Application_Model_Preference::GetAllow3rdPartyApi()) { if (Application_Model_Preference::GetAllow3rdPartyApi() || $this->checkAuth()) {
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
@ -1515,5 +1516,30 @@ class ApiController extends Zend_Controller_Action
$this->_helper->json($result); $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));
}
} }

View File

@ -72,6 +72,7 @@ class PreferenceController extends Zend_Controller_Action
Application_Model_Preference::SetSoundCloudLicense($values["SoundCloudLicense"]);*/ Application_Model_Preference::SetSoundCloudLicense($values["SoundCloudLicense"]);*/
$this->view->statusMsg = "<div class='success'>". _("Preferences updated.")."</div>"; $this->view->statusMsg = "<div class='success'>". _("Preferences updated.")."</div>";
$form = new Application_Form_Preferences();
$this->view->form = $form; $this->view->form = $form;
//$this->_helper->json->sendJson(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml'))); //$this->_helper->json->sendJson(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml')));
} else { } else {
@ -491,31 +492,42 @@ class PreferenceController extends Zend_Controller_Action
return; return;
} }
$user = Application_Model_User::getCurrentUser(); $this->deleteFutureScheduleItems();
$playlists = $blocks = $streams = []; $this->deleteCloudFiles();
$this->deleteStoredFiles();
$allPlaylists = CcPlaylistQuery::create()->find(); $this->getResponse()
foreach ($allPlaylists as $p) { ->setHttpResponseCode(200)
$playlists[] = $p->getDbId(); ->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(); private function deleteCloudFiles() {
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 { try {
// Delete all the cloud files
$CC_CONFIG = Config::getConfig(); $CC_CONFIG = Config::getConfig();
foreach ($CC_CONFIG["supportedStorageBackends"] as $storageBackend) { foreach ($CC_CONFIG["supportedStorageBackends"] as $storageBackend) {
@ -525,7 +537,9 @@ class PreferenceController extends Zend_Controller_Action
} catch(Exception $e) { } catch(Exception $e) {
Logging::info($e->getMessage()); Logging::info($e->getMessage());
} }
}
private function deleteStoredFiles() {
// Delete all files from the database // Delete all files from the database
$files = CcFilesQuery::create()->find(); $files = CcFilesQuery::create()->find();
foreach ($files as $file) { foreach ($files as $file) {
@ -534,10 +548,6 @@ class PreferenceController extends Zend_Controller_Action
// every S3 file we delete. // every S3 file we delete.
$storedFile->delete(true); $storedFile->delete(true);
} }
$this->getResponse()
->setHttpResponseCode(200)
->appendBody("OK");
} }
} }

View File

@ -315,13 +315,6 @@ class ScheduleController extends Zend_Controller_Action
{ {
$range = Application_Model_Schedule::GetPlayOrderRangeOld(); $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(); $show = Application_Model_Show::getCurrentShow();
/* Convert all UTC times to localtime before sending back to user. */ /* Convert all UTC times to localtime before sending back to user. */

View File

@ -239,7 +239,7 @@ class WHMCS_Auth_Adapter implements Zend_Auth_Adapter_Interface {
} }
else else
{ {
if ($product["status"] === "Active") { if (($product["status"] === "Active") || ($product["status"] === "Suspended")) {
$airtimeProduct = $product; $airtimeProduct = $product;
$subdomain = ''; $subdomain = '';

View File

@ -18,7 +18,6 @@ class Application_Form_TuneInPreferences extends Zend_Form_SubForm
$enableTunein->addDecorator('Label', array('class' => 'enable-tunein')); $enableTunein->addDecorator('Label', array('class' => 'enable-tunein'));
$enableTunein->setLabel(_("Push metadata to your station on TuneIn?")); $enableTunein->setLabel(_("Push metadata to your station on TuneIn?"));
$enableTunein->setValue(Application_Model_Preference::getTuneinEnabled()); $enableTunein->setValue(Application_Model_Preference::getTuneinEnabled());
$enableTunein->setAttrib("class", "block-display");
$this->addElement($enableTunein); $this->addElement($enableTunein);
$tuneinStationId = new Zend_Form_Element_Text("tunein_station_id"); $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)); 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") { 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."))); $this->getElement("enable_tunein")->setErrors(array(_("Invalid TuneIn Settings. Please ensure your TuneIn settings are correct and try again.")));
$valid = false; $valid = false;
} }
} }
@ -85,8 +83,10 @@ class Application_Form_TuneInPreferences extends Zend_Form_SubForm
if ($valid) { if ($valid) {
$xmlObj = new SimpleXMLElement($xmlData); $xmlObj = new SimpleXMLElement($xmlData);
if (!$xmlObj || $xmlObj->head->status != "200") { 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; $valid = false;
} else if ($xmlObj->head->status == "200") { } else if ($xmlObj->head->status == "200") {
Application_Model_Preference::setLastTuneinMetadataUpdate(time());
$valid = true; $valid = true;
} }
} }

View File

@ -1513,4 +1513,14 @@ class Application_Model_Preference
{ {
return self::getValue("tunein_station_id"); 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);
}
} }

View File

@ -199,7 +199,6 @@ SQL;
$currentMedia["ends"] = $currentMedia["show_ends"]; $currentMedia["ends"] = $currentMedia["show_ends"];
} }
$currentMediaScheduleId = $currentMedia["id"];
$currentMediaFileId = $currentMedia["file_id"]; $currentMediaFileId = $currentMedia["file_id"];
$currentMediaStreamId = $currentMedia["stream_id"]; $currentMediaStreamId = $currentMedia["stream_id"];
if (isset($currentMediaFileId)) { if (isset($currentMediaFileId)) {
@ -234,9 +233,10 @@ SQL;
); );
$previousMedia = CcScheduleQuery::create() $previousMedia = CcScheduleQuery::create()
->filterByDbId($currentMediaScheduleId-1) ->filterByDbStarts($currentMedia["starts"], Criteria::LESS_THAN)
->filterByDbId($currentMedia["id"], Criteria::NOT_EQUAL)
->filterByDbPlayoutStatus(0, Criteria::GREATER_THAN) ->filterByDbPlayoutStatus(0, Criteria::GREATER_THAN)
->orderByDbStarts() ->orderByDbStarts(Criteria::DESC)
->findOne(); ->findOne();
if (isset($previousMedia)) { if (isset($previousMedia)) {
$previousMediaFileId = $previousMedia->getDbFileId(); $previousMediaFileId = $previousMedia->getDbFileId();
@ -253,10 +253,6 @@ SQL;
$previousWebstream = CcWebstreamQuery::create() $previousWebstream = CcWebstreamQuery::create()
->filterByDbId($previousMediaStreamId) ->filterByDbId($previousMediaStreamId)
->findOne(); ->findOne();
/*$previousWebstreamMetadata = CcWebstreamMetadataQuery::create()
->filterByDbInstanceId($previousMedia->getDbInstanceId())
->orderByDbStartTime(Criteria::DESC)
->findOne();*/
$previousMediaName = $previousWebstream->getDbName(); $previousMediaName = $previousWebstream->getDbName();
} else { } else {
$previousMediaType = null; $previousMediaType = null;
@ -270,8 +266,9 @@ SQL;
} }
$nextMedia = CcScheduleQuery::create() $nextMedia = CcScheduleQuery::create()
->filterByDbId($currentMediaScheduleId+1) ->filterByDbStarts($currentMedia["starts"], Criteria::GREATER_THAN)
->orderByDbStarts() ->filterByDbId($currentMedia["id"], Criteria::NOT_EQUAL)
->orderByDbStarts(Criteria::ASC)
->findOne(); ->findOne();
if (isset($nextMedia)) { if (isset($nextMedia)) {
$nextMediaFileId = $nextMedia->getDbFileId(); $nextMediaFileId = $nextMedia->getDbFileId();
@ -287,10 +284,6 @@ SQL;
$nextWebstream = CcWebstreamQuery::create() $nextWebstream = CcWebstreamQuery::create()
->filterByDbId($nextMediaStreamId) ->filterByDbId($nextMediaStreamId)
->findOne(); ->findOne();
/*$nextWebstreamMetadata = CcWebstreamMetadataQuery::create()
->filterByDbInstanceId($nextMedia->getDbInstanceId())
->orderByDbStartTime(Criteria::DESC)
->findOne();*/
$nextMediaName = $nextWebstream->getDbName(); $nextMediaName = $nextWebstream->getDbName();
} else { } else {
$nextMediaType = null; $nextMediaType = null;

View File

@ -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['update_cue_values_by_silan'] = 'update-cue-values-by-silan/api_key/%%api_key%%'
api_config['api_base'] = 'api' api_config['api_base'] = 'api'
api_config['bin_dir'] = '/usr/lib/airtime/api_clients/' 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 #TODO
self.logger.error(str(e)) self.logger.error(str(e))
def update_metadata_on_tunein(self):
self.services.update_metadata_on_tunein()
class InvalidContentType(Exception): class InvalidContentType(Exception):
pass pass

View File

@ -14,7 +14,7 @@ import traceback
import pure import pure
from Queue import Empty from Queue import Empty
from threading import Thread from threading import Thread, Timer
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from api_clients import api_client from api_clients import api_client
@ -441,6 +441,12 @@ class PypoFetch(Thread):
return success 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): def main(self):
#Make sure all Liquidsoap queues are empty. This is important in the #Make sure all Liquidsoap queues are empty. This is important in the
@ -452,8 +458,10 @@ class PypoFetch(Thread):
self.set_bootstrap_variables() self.set_bootstrap_variables()
self.update_metadata_on_tunein()
# Bootstrap: since we are just starting up, we need to grab the # 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. # minutes or wait for schedule updates to get pushed.
success = self.persistent_manual_schedule_fetch(max_attempts=5) success = self.persistent_manual_schedule_fetch(max_attempts=5)
@ -463,6 +471,7 @@ class PypoFetch(Thread):
loops = 1 loops = 1
while True: while True:
self.logger.info("Loop #%s", loops) self.logger.info("Loop #%s", loops)
manual_fetch_needed = False
try: try:
""" """
our simple_queue.get() requires a timeout, in which case we 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 Currently we are checking every POLL_INTERVAL seconds
""" """
message = self.fetch_queue.get(block=True, timeout=self.listener_timeout) message = self.fetch_queue.get(block=True, timeout=self.listener_timeout)
manual_fetch_needed = False
self.handle_message(message) self.handle_message(message)
except Empty, e: except Empty as e:
self.logger.info("Queue timeout. Fetching schedule manually") self.logger.info("Queue timeout. Fetching schedule manually")
self.persistent_manual_schedule_fetch(max_attempts=5) manual_fetch_needed = True
except Exception, e: except Exception as e:
top = traceback.format_exc() top = traceback.format_exc()
self.logger.error('Exception: %s', e) self.logger.error('Exception: %s', e)
self.logger.error("traceback: %s", top) 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 loops += 1
def run(self): def run(self):
@ -496,3 +514,4 @@ class PypoFetch(Thread):
Entry point of the thread Entry point of the thread
""" """
self.main() self.main()
self.logger.info('PypoFetch thread exiting')

View File

@ -30,6 +30,7 @@ class PypoFile(Thread):
self.media_queue = schedule_queue self.media_queue = schedule_queue
self.media = None self.media = None
self.cache_dir = os.path.join(config["cache_dir"], "scheduler") self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
self._config = self.read_config_file(CONFIG_PATH)
def copy_file(self, media_item): def copy_file(self, media_item):
""" """
@ -60,11 +61,9 @@ class PypoFile(Thread):
if do_copy: if do_copy:
self.logger.debug("copying from %s to local cache %s" % (src, dst)) self.logger.debug("copying from %s to local cache %s" % (src, dst))
try: try:
config = self.read_config_file(CONFIG_PATH)
CONFIG_SECTION = "general" CONFIG_SECTION = "general"
username = config.get(CONFIG_SECTION, 'api_key') username = self._config.get(CONFIG_SECTION, 'api_key')
host = self._config.get(CONFIG_SECTION, 'base_url')
host = config.get(CONFIG_SECTION, 'base_url')
url = "http://%s/rest/media/%s/download" % (host, media_item["id"]) url = "http://%s/rest/media/%s/download" % (host, media_item["id"])
with open(dst, "wb") as handle: with open(dst, "wb") as handle:
response = requests.get(url, auth=requests.auth.HTTPBasicAuth(username, ''), stream=True, verify=False) 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 Entry point of the thread
""" """
self.main() self.main()