sintonia/legacy/application/services/SchedulerService.php

504 lines
20 KiB
PHP

<?php
class Application_Service_SchedulerService
{
private $con;
private $fileInfo = [
'id' => '',
'cliplength' => '',
'cuein' => '00:00:00',
'cueout' => '00:00:00',
'fadein' => '00:00:00',
'fadeout' => '00:00:00',
'sched_id' => null,
'type' => 0, // default type of '0' to represent files. type '1' represents a webstream
];
private $epochNow;
private $nowDT;
private $currentUser;
private $checkUserPermissions = true;
public function __construct()
{
$this->con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);
// subtracting one because sometimes when we cancel a track, we set its end time
// to epochNow and then send the new schedule to pypo. Sometimes the currently cancelled
// track can still be included in the new schedule because it may have a few ms left to play.
// subtracting 1 second from epochNow resolves this issue.
$this->epochNow = microtime(true) - 1;
$this->nowDT = DateTime::createFromFormat('U.u', $this->epochNow, new DateTimeZone('UTC'));
if ($this->nowDT === false) {
// DateTime::createFromFormat does not support millisecond string formatting in PHP 5.3.2 (Ubuntu 10.04).
// In PHP 5.3.3 (Ubuntu 10.10), this has been fixed.
$this->nowDT = DateTime::createFromFormat('U', time(), new DateTimeZone('UTC'));
}
$user_service = new Application_Service_UserService();
$this->currentUser = $user_service->getCurrentUser();
}
/**
* Applies the show start difference to any scheduled items.
*
* @param $diff (integer, difference between unix epoch in seconds)
* @param mixed $instanceIds
*/
public static function updateScheduleStartTime($instanceIds, $diff)
{
$con = Propel::getConnection();
if (count($instanceIds) > 0) {
$showIdList = implode(',', $instanceIds);
$ccSchedules = CcScheduleQuery::create()
->filterByDbInstanceId($instanceIds, Criteria::IN)
->find($con);
$interval = new DateInterval('PT' . abs($diff) . 'S');
if ($diff < 0) {
$interval->invert = 1;
}
foreach ($ccSchedules as $ccSchedule) {
$start = $ccSchedule->getDbStarts(null);
$newStart = $start->add($interval);
$end = $ccSchedule->getDbEnds(null);
$newEnd = $end->add($interval);
$ccSchedule
->setDbStarts($newStart)
->setDbEnds($newEnd)
->save($con);
}
}
}
/**
* Removes any time gaps in shows.
*
* @param array $schedIds schedule ids to exclude
* @param mixed $showId
*/
public function removeGaps($showId, $schedIds = null)
{
$ccShowInstances = CcShowInstancesQuery::create()->filterByDbShowId($showId)->find();
foreach ($ccShowInstances as $instance) {
Logging::info('Removing gaps from show instance #' . $instance->getDbId());
// DateTime object
$itemStart = $instance->getDbStarts(null);
$ccScheduleItems = CcScheduleQuery::create()
->filterByDbInstanceId($instance->getDbId())
->filterByDbId($schedIds, Criteria::NOT_IN)
->orderByDbStarts()
->find();
foreach ($ccScheduleItems as $ccSchedule) {
// DateTime object
$itemEnd = $this->findEndTime($itemStart, $ccSchedule->getDbClipLength());
$ccSchedule->setDbStarts($itemStart)
->setDbEnds($itemEnd);
$itemStart = $itemEnd;
}
$ccScheduleItems->save();
}
}
/**
* Enter description here ...
*
* @param DateTime $instanceStart
* @param string $clipLength
*/
private static function findEndTime($instanceStart, $clipLength)
{
$startEpoch = $instanceStart->format('U.u');
$durationSeconds = Application_Common_DateHelper::playlistTimeToSeconds($clipLength);
// add two float numbers to 6 subsecond precision
// DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
$endEpoch = bcadd($startEpoch, (string) $durationSeconds, 6);
$dt = DateTime::createFromFormat('U.u', $endEpoch, new DateTimeZone('UTC'));
if ($dt === false) {
// PHP 5.3.2 problem
$dt = DateTime::createFromFormat('U', intval($endEpoch), new DateTimeZone('UTC'));
}
return $dt;
}
private static function findTimeDifference($p_startDT, $p_seconds)
{
$startEpoch = $p_startDT->format('U.u');
// add two float numbers to 6 subsecond precision
// DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
$newEpoch = bcsub($startEpoch, (string) $p_seconds, 6);
$dt = DateTime::createFromFormat('U.u', $newEpoch, new DateTimeZone('UTC'));
if ($dt === false) {
// PHP 5.3.2 problem
$dt = DateTime::createFromFormat('U', intval($newEpoch), new DateTimeZone('UTC'));
}
return $dt;
}
/**
* Gets a copy of the linked show's schedule from cc_schedule table.
*
* If $instanceId is not null, we use that variable to grab the linked
* show's schedule from cc_schedule table. (This is likely to be the case
* if a user has edited a show and changed it from un-linked to linked, in
* which case we copy the show content from the show instance that was clicked
* on to edit the show in the calendar.) Otherwise the schedule is taken
* from the most recent show instance that existed before new show
* instances were created. (This is likely to be the case when a user edits a
* show and a new repeat show day is added (i.e. mondays), or moves forward in the
* calendar triggering show creation)
*
* @param int $showId
* @param array $instancsIdsToFill
* @param int $instanceId
*/
private static function getLinkedShowSchedule($showId, $instancsIdsToFill, $instanceId)
{
$con = Propel::getConnection();
if (is_null($instanceId)) {
$showsPopulatedUntil = Application_Model_Preference::GetShowsPopulatedUntil();
$showInstanceWithMostRecentSchedule = CcShowInstancesQuery::create()
->filterByDbShowId($showId)
->filterByDbStarts($showsPopulatedUntil->format(DEFAULT_TIMESTAMP_FORMAT), Criteria::LESS_THAN)
->filterByDbId($instancsIdsToFill, Criteria::NOT_IN)
->orderByDbStarts(Criteria::DESC)
->limit(1)
->findOne();
if (is_null($showInstanceWithMostRecentSchedule)) {
return null;
}
$instanceId = $showInstanceWithMostRecentSchedule->getDbId();
}
$linkedShowSchedule_sql = $con->prepare(
'select * from cc_schedule where instance_id = :instance_id ' .
'order by starts'
);
$linkedShowSchedule_sql->bindParam(':instance_id', $instanceId);
$linkedShowSchedule_sql->execute();
return $linkedShowSchedule_sql->fetchAll();
}
/**
* This function gets called after new linked show_instances are created, or after
* a show has been edited and went from being un-linked to linked.
* It fills the new show instances' schedules.
*
* @param CcShow_type $ccShow
* @param array $instanceIdsToFill ids of the new linked cc_show_instances that
* need their schedules filled
* @param null|mixed $instanceId
*/
public static function fillLinkedInstances($ccShow, $instanceIdsToFill, $instanceId = null)
{
// Get the "template" schedule for the linked show (contents of the linked show) that will be
// copied into to all the new show instances.
$linkedShowSchedule = self::getLinkedShowSchedule($ccShow->getDbId(), $instanceIdsToFill, $instanceId);
// get time_filled so we can update cc_show_instances
if (!empty($linkedShowSchedule)) {
$timeFilled_sql = 'SELECT time_filled FROM cc_show_instances ' .
"WHERE id = {$linkedShowSchedule[0]['instance_id']}";
$timeFilled = Application_Common_Database::prepareAndExecute(
$timeFilled_sql,
[],
Application_Common_Database::COLUMN
);
} else {
// We probably shouldn't return here because the code below will
// set this on each empty show instance...
$timeFilled = '00:00:00';
}
$values = [];
$con = Propel::getConnection();
// Here we begin to fill the new show instances (as specified by $instanceIdsToFill)
// with content from $linkedShowSchedule.
try {
$con->beginTransaction();
if (!empty($linkedShowSchedule)) {
foreach ($instanceIdsToFill as $id) {
// Start by clearing the show instance that needs to be filling. This ensure
// we're not going to get in trouble in case there's an programming error somewhere else.
self::clearShowInstanceContents($id);
// Now fill the show instance with the same content that $linkedShowSchedule has.
$instanceStart_sql = 'SELECT starts FROM cc_show_instances ' .
"WHERE id = {$id} " . 'ORDER BY starts';
// What's tricky here is that when we copy the content, we have to adjust
// the start and end times of each track so they're inside the new show instance's time slot.
$nextStartDT = new DateTime(
Application_Common_Database::prepareAndExecute(
$instanceStart_sql,
[],
Application_Common_Database::COLUMN
),
new DateTimeZone('UTC')
);
$defaultCrossfadeDuration = Application_Model_Preference::GetDefaultCrossfadeDuration();
unset($values);
$values = [];
foreach ($linkedShowSchedule as $item) {
$endTimeDT = self::findEndTime(
$nextStartDT,
$item['clip_length']
);
if (is_null($item['file_id'])) {
$item['file_id'] = 'null';
}
if (is_null($item['stream_id'])) {
$item['stream_id'] = 'null';
}
$values[] = '(' . "'{$nextStartDT->format(DEFAULT_TIMESTAMP_FORMAT)}', " .
"'{$endTimeDT->format(DEFAULT_TIMESTAMP_FORMAT)}', " .
"'{$item['clip_length']}', " .
"'{$item['fade_in']}', '{$item['fade_out']}', " .
"'{$item['cue_in']}', '{$item['cue_out']}', " .
"{$item['file_id']}, {$item['stream_id']}, " .
"{$id}, {$item['position']})";
$nextStartDT = self::findTimeDifference(
$endTimeDT,
$defaultCrossfadeDuration
);
} // foreach show item
if (!empty($values)) {
$insert_sql = 'INSERT INTO cc_schedule (starts, ends, ' .
'clip_length, fade_in, fade_out, cue_in, cue_out, ' .
'file_id, stream_id, instance_id, position) VALUES ' .
implode(',', $values);
Application_Common_Database::prepareAndExecute(
$insert_sql,
[],
Application_Common_Database::EXECUTE
);
}
// update cc_schedule status column
$instance = CcShowInstancesQuery::create()->findPk($id);
$instance->updateScheduleStatus($con);
} // foreach linked instance
}
// update time_filled and last_scheduled in cc_show_instances
$now = gmdate(DEFAULT_TIMESTAMP_FORMAT);
$whereClause = new Criteria();
$whereClause->add(CcShowInstancesPeer::ID, $instanceIdsToFill, Criteria::IN);
$updateClause = new Criteria();
$updateClause->add(CcShowInstancesPeer::TIME_FILLED, $timeFilled);
$updateClause->add(CcShowInstancesPeer::LAST_SCHEDULED, $now);
BasePeer::doUpdate($whereClause, $updateClause, $con);
$con->commit();
Logging::info('finished fill');
} catch (Exception $e) {
$con->rollback();
Logging::info('Error filling linked shows: ' . $e->getMessage());
exit;
}
}
public static function fillPreservedLinkedShowContent($ccShow, $showStamp)
{
$item = $showStamp->getFirst();
$timeFilled = $item->getCcShowInstances()->getDbTimeFilled();
foreach ($ccShow->getCcShowInstancess() as $ccShowInstance) {
$ccSchedules = CcScheduleQuery::create()
->filterByDbInstanceId($ccShowInstance->getDbId())
->find();
if ($ccSchedules->isEmpty()) {
$nextStartDT = $ccShowInstance->getDbStarts(null);
foreach ($showStamp as $item) {
$endTimeDT = self::findEndTime($nextStartDT, $item->getDbClipLength());
$ccSchedule = new CcSchedule();
$ccSchedule
->setDbStarts($nextStartDT)
->setDbEnds($endTimeDT)
->setDbFileId($item->getDbFileId())
->setDbStreamId($item->getDbStreamId())
->setDbClipLength($item->getDbClipLength())
->setDbFadeIn($item->getDbFadeIn())
->setDbFadeOut($item->getDbFadeOut())
->setDbCuein($item->getDbCueIn())
->setDbCueOut($item->getDbCueOut())
->setDbInstanceId($ccShowInstance->getDbId())
->setDbPosition($item->getDbPosition())
->save();
$nextStartDT = self::findTimeDifference(
$endTimeDT,
Application_Model_Preference::GetDefaultCrossfadeDuration()
);
} // foreach show item
$ccShowInstance
->setDbTimeFilled($timeFilled)
->setDbLastScheduled(gmdate(DEFAULT_TIMESTAMP_FORMAT))
->save();
}
}
}
/** Clears a show instance's schedule (which is actually clearing cc_schedule during that show instance's time slot.) */
private static function clearShowInstanceContents($instanceId)
{
// Application_Common_Database::prepareAndExecute($delete_sql, array(), Application_Common_Database::EXECUTE);
$con = Propel::getConnection();
$query = $con->prepare('DELETE FROM cc_schedule WHERE instance_id = :instance_id');
$query->bindParam(':instance_id', $instanceId);
$query->execute();
}
/*
private static function replaceInstanceContentCheck($currentShowStamp, $showStamp, $instance_id)
{
$counter = 0;
$erraseShow = false;
if (count($currentShowStamp) != count($showStamp)) {
$erraseShow = true;
} else {
foreach ($showStamp as $item) {
if ($item["file_id"] != $currentShowStamp[$counter]["file_id"] ||
$item["stream_id"] != $currentShowStamp[$counter]["stream_id"]) {
$erraseShow = true;
break;
//CcScheduleQuery::create()
// ->filterByDbInstanceId($ccShowInstance->getDbId())
// ->delete();
}
$counter += 1;
}
}
if ($erraseShow) {
$delete_sql = "DELETE FROM cc_schedule ".
"WHERE instance_id = :instance_id";
Application_Common_Database::prepareAndExecute(
$delete_sql, array(), Application_Common_Database::EXECUTE);
return true;
}
//If we get here, the content in the show instance is the same
// as what we want to replace it with, so we can leave as is
return false;
}*/
public function emptyShowContent($instanceId)
{
try {
$ccShowInstance = CcShowInstancesQuery::create()->findPk($instanceId);
$instances = [];
$instanceIds = [];
if ($ccShowInstance->getCcShow()->isLinked()) {
foreach ($ccShowInstance->getCcShow()->getFutureCcShowInstancess() as $instance) {
$instanceIds[] = $instance->getDbId();
$instances[] = $instance;
}
} else {
$instanceIds[] = $ccShowInstance->getDbId();
$instances[] = $ccShowInstance;
}
/* Get the file ids of the tracks we are about to delete
* from cc_schedule. We need these so we can update the
* is_scheduled flag in cc_files
*/
$ccSchedules = CcScheduleQuery::create()
->filterByDbInstanceId($instanceIds, Criteria::IN)
->setDistinct(CcSchedulePeer::FILE_ID)
->find();
$fileIds = [];
foreach ($ccSchedules as $ccSchedule) {
$fileIds[] = $ccSchedule->getDbFileId();
}
// Clear out the schedule
CcScheduleQuery::create()
->filterByDbInstanceId($instanceIds, Criteria::IN)
->delete();
/* Now that the schedule has been cleared we need to make
* sure we do not update the is_scheduled flag for tracks
* that are scheduled in other shows
*/
$futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles();
foreach ($fileIds as $k => $v) {
if (in_array($v, $futureScheduledFiles)) {
unset($fileIds[$k]);
}
}
$selectCriteria = new Criteria();
$selectCriteria->add(CcFilesPeer::ID, $fileIds, Criteria::IN);
$updateCriteria = new Criteria();
$updateCriteria->add(CcFilesPeer::IS_SCHEDULED, false);
BasePeer::doUpdate($selectCriteria, $updateCriteria, Propel::getConnection());
Application_Model_RabbitMq::PushSchedule();
$con = Propel::getConnection(CcShowInstancesPeer::DATABASE_NAME);
foreach ($instances as $instance) {
$instance->updateDbTimeFilled($con);
}
return true;
} catch (Exception $e) {
Logging::info($e->getMessage());
return false;
}
}
// TODO in the future this should probably support webstreams.
public function updateFutureIsScheduled($scheduleId, $status)
{
$sched = CcScheduleQuery::create()->findPk($scheduleId);
$redraw = false;
if (isset($sched)) {
$fileId = $sched->getDbFileId();
if (isset($fileId)) {
$redraw = Application_Model_StoredFile::setIsScheduled($fileId, $status);
}
}
return $redraw;
}
}