Merge branch 'saas-dev' into saas-tunein

Conflicts:
	airtime_mvc/application/views/scripts/form/preferences.phtml
This commit is contained in:
drigato 2015-05-19 15:50:16 -04:00
commit a2a9e54553
18 changed files with 238 additions and 76 deletions

View file

@ -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 {}

View file

@ -11,7 +11,7 @@ define('COMPANY_SITE_URL' , 'http://sourcefabric.org/');
define('WHOS_USING_URL' , 'http://sourcefabric.org/en/airtime/whosusing');
define('TERMS_AND_CONDITIONS_URL' , 'http://www.sourcefabric.org/en/about/policy/');
define('PRIVACY_POLICY_URL' , 'http://www.sourcefabric.org/en/about/policy/');
define('USER_MANUAL_URL' , 'http://sourcefabric.booktype.pro/airtime-25-for-broadcasters/');
define('USER_MANUAL_URL' , 'http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters');
define('LICENSE_VERSION' , 'GNU AGPL v.3');
define('LICENSE_URL' , 'http://www.gnu.org/licenses/agpl-3.0-standalone.html');

View file

@ -134,7 +134,7 @@ $pages = array(
),
array(
'label' => _('User Manual'),
'uri' => "http://sourcefabric.booktype.pro/airtime-25-for-broadcasters/",
'uri' => "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters",
'target' => "_blank"
),
array(

View file

@ -459,4 +459,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");
}
}

View file

@ -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;
}

View file

@ -0,0 +1,21 @@
<?php
class Application_Form_DangerousPreferences extends Zend_Form_SubForm {
public function init() {
$this->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);
}
}

View file

@ -28,6 +28,9 @@ class Application_Form_Preferences extends Zend_Form
$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');

View file

@ -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,10 +405,14 @@ SQL;
}
catch (Exception $e)
{
if ($quiet) {
Logging::info($e);
} else {
//Just log the exception and continue.
Logging::error($e);
}
}
}
//Update the user's disk usage
Application_Model_Preference::updateDiskUsage(-1 * $filesize);

View file

@ -150,22 +150,26 @@ class Application_Service_CalendarService
$menu["edit"] = array(
"name" => _("Edit This Instance"),
"icon" => "edit",
"url" => $baseUrl."Schedule/populate-repeating-show-instance-form");
"url" => $baseUrl . "Schedule/populate-repeating-show-instance-form"
);
} else {
$menu["edit"] = array(
"name" => _("Edit"),
"icon" => "edit",
"items" => array());
"items" => array()
);
$menu["edit"]["items"]["all"] = array(
"name" => _("Edit Show"),
"icon" => "edit",
"url" => $baseUrl."Schedule/populate-show-form");
"url" => $baseUrl . "Schedule/populate-show-form"
);
$menu["edit"]["items"]["instance"] = array(
"name" => _("Edit This Instance"),
"icon" => "edit",
"url" => $baseUrl."Schedule/populate-repeating-show-instance-form");
"url" => $baseUrl . "Schedule/populate-repeating-show-instance-form"
);
}
} else {
$menu["edit"] = array(

View file

@ -153,14 +153,19 @@ class Application_Service_ShowFormService
if ($ccShowDay->isShowStartInPast()) {
//for a non-repeating show, we should never allow user to change the start time.
//for a repeating show, we should allow because the form works as repeating template form
if (!$ccShowDay->isRepeating()) {
$form->disableStartDateAndTime();
// Removing this - if there is no future instance, this will throw an error.
// If there is a future instance, then we get a WHEN block representing the next instance
// which may be confusing.
/*if (!$ccShowDay->isRepeating()) {
$form->disableStartDateAndTime();
} else {
list($showStart, $showEnd) = $this->getNextFutureRepeatShowTime();
if ($this->hasShowStarted($showStart)) {
$form->disableStartDateAndTime();
}
}
}*/
}
$form->populate(
@ -410,9 +415,8 @@ class Application_Service_ShowFormService
//if the show is repeating, set the start date to the next
//repeating instance in the future
if ($this->ccShow->isRepeating()) {
list($originalShowStartDateTime,) = $this->getNextFutureRepeatShowTime();
} else {
$originalShowStartDateTime = $this->getCurrentOrNextInstanceStartTime();
if (!$originalShowStartDateTime) {
$originalShowStartDateTime = $dt;
}
@ -421,26 +425,30 @@ class Application_Service_ShowFormService
/**
*
* Returns 2 DateTime objects, in the user's local time,
* of the next future repeat show instance start and end time
* Returns a DateTime object, in the user's local time,
* of the current or next show instance start time
*
* Returns null if there is no next future repeating show instance
*/
public function getNextFutureRepeatShowTime()
public function getCurrentOrNextInstanceStartTime()
{
$ccShowInstance = CcShowInstancesQuery::create()
->filterByDbShowId($this->ccShow->getDbId())
->filterByDbModifiedInstance(false)
->filterByDbStarts(gmdate("Y-m-d H:i:s"), Criteria::GREATER_THAN)
->filterByDbStarts(gmdate("Y-m-d"), Criteria::GREATER_EQUAL)
->orderByDbStarts()
->findOne();
if (!$ccShowInstance) {
return null;
}
$starts = new DateTime($ccShowInstance->getDbStarts(), new DateTimeZone("UTC"));
$ends = new DateTime($ccShowInstance->getDbEnds(), new DateTimeZone("UTC"));
$showTimezone = $this->ccShow->getFirstCcShowDay()->getDbTimezone();
$starts->setTimezone(new DateTimeZone($showTimezone));
$ends->setTimezone(new DateTimeZone($showTimezone));
return array($starts, $ends);
return $starts;
}

View file

@ -252,7 +252,7 @@
success: function(data) {
if (data.current === null) {
if (data.currentShow != null) {
if (data.currentShow != null && data.currentShow.length != 0) {
// Master/show source have no current track but they do have a current show.
$("p.now_playing").html(data.currentShow[0].name);
} else {

View file

@ -2,13 +2,27 @@
<?php echo $this->element->getElement('csrf') ?>
<?php echo $this->element->getSubform('preferences_general') ?>
<?php //No soundcloud stuff on Airtime Pro -- Albert ?>
<h3 class="collapsible-header" id="tunein-pref-heading"><span class="arrow-icon"></span><?php echo _("TuneIn Settings"); ?></h3>
<div class="collapsible-content" id="tunein-settings">
<?php echo $this->element->getSubform('preferences_tunein') ?>
</div>
<?php //No soundcloud stuff on Airtime Pro -- Albert ?>
<h3 class="collapsible-header" id="soundcloud-heading"><span class="arrow-icon"></span><?php echo _("SoundCloud Settings") ?></h3>
<div class="collapsible-content" id="soundcloud-settings">
<?php echo $this->element->getSubform('preferences_soundcloud') ?>
</div>
<h3 class="collapsible-header" id="dangerous-heading"><span class="arrow-icon"></span><?php echo _("Dangerous Options") ?></h3>
<div class="collapsible-content" id="dangerous-settings">
<?php echo $this->element->getSubform('preferences_danger') ?>
</div>
<br>
<?php echo $this->element->submit->render() ?>
</form>

View file

@ -0,0 +1,14 @@
<fieldset class="padded">
<dl class="zend_form">
<div class="warning" style="margin-bottom: 10px;">
<p class="warning-label">
<strong>Warning:</strong> These functions will have <strong>permanent and lasting effects</strong>
on your Airtime station. Think carefully before using them!
</p>
</div>
<?php echo $this->element->getElement('clear_library')->render() ?>
</dl>
</fieldset>

View file

@ -3,12 +3,11 @@
font-size: 200px !important;
}
</style>
<?php $upgradeLink = Application_Common_OsPath::getBaseDir() . "billing/upgrade"; ?>
<?php if ($this->quotaLimitReached) { ?>
<div class="errors quota-reached">
Disk quota exceeded. You cannot upload files until you
<a target="_parent" href=<?php $baseUrl = Application_Common_OsPath::getBaseDir(); echo $baseUrl . "billing/upgrade"?>>
upgrade your storage
</a>.
<?php printf(_pro("Disk quota exceeded. You cannot upload files until you %s upgrade your storage"),
"<a target=\"_parent\" href=$upgradeLink>");?></a>.
</div>
<?php
}

View file

@ -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,13 +2260,8 @@ 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.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%;
}
@ -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;

View file

@ -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() {
@ -144,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() {

View file

@ -3,12 +3,8 @@ import json
import logging
import collections
import Queue
import subprocess
import multiprocessing
import time
import sys
import traceback
import os
import pickle
import threading
from urlparse import urlparse
@ -158,23 +154,23 @@ class StatusReporter():
''' We use multiprocessing.Process again here because we need a thread for this stuff
anyways, and Python gives us process isolation for free (crash safety).
'''
_ipc_queue = multiprocessing.Queue()
#_request_process = multiprocessing.Process(target=process_http_requests,
_ipc_queue = Queue.Queue()
#_http_thread = multiprocessing.Process(target=process_http_requests,
# args=(_ipc_queue,))
_request_process = None
_http_thread = None
@classmethod
def start_thread(self, http_retry_queue_path):
StatusReporter._request_process = threading.Thread(target=process_http_requests,
StatusReporter._http_thread = threading.Thread(target=process_http_requests,
args=(StatusReporter._ipc_queue,http_retry_queue_path))
StatusReporter._request_process.start()
StatusReporter._http_thread.start()
@classmethod
def stop_thread(self):
logging.info("Terminating status_reporter process")
#StatusReporter._request_process.terminate() # Triggers SIGTERM on the child process
#StatusReporter._http_thread.terminate() # Triggers SIGTERM on the child process
StatusReporter._ipc_queue.put("shutdown") # Special trigger
StatusReporter._request_process.join()
StatusReporter._http_thread.join()
@classmethod
def _send_http_request(self, request):

View file

@ -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)