diff --git a/api/app/Http/Controllers/ProgramController.php b/api/app/Http/Controllers/ProgramController.php index 3fd535b..c5d6802 100644 --- a/api/app/Http/Controllers/ProgramController.php +++ b/api/app/Http/Controllers/ProgramController.php @@ -18,7 +18,7 @@ class ProgramController extends Controller ((`schedule`.`enddate` IS NULL) OR (`schedule`.`enddate` >= :startdate)) AND `schedule`.`active` = 1 AND `schedule`.`final` = 1 - ORDER BY `schedule`.`priority` DESC, MOD(`startday` + 6, 7) + 1, `schedule`.`starttime` ASC + ORDER BY `schedule`.`priority` DESC, `schedule`.`startday` ASC, `schedule`.`starttime` ASC QUERY; /** @@ -30,6 +30,153 @@ QUERY; { // } + + public function addToSchedule(&$schedule, $start, $end, $program) { + if($start == $end) { + return; + } + + $last = count($schedule) ? $schedule[count($schedule) - 1] : null; + if($last && ($last['program'] == $program) && ($last['end'] == $start)) + { + // Merge with the previous entry + $last['end'] = $end; + } + else + { + $schedule[] = ['start' => clone $start, 'end' => clone $end, 'program' => new \Model\Program($program)]; + } + } + + + public function createSchedule(\DateTimeImmutable $start, \DateTimeImmutable $end, $DEBUG = false) { + $startDay = $start->format('N'); + $endDay = $end->format('N'); + $schedule = []; + + $START = 1; $END = -1; + $WEEK = new \DateInterval('P7D'); + $WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" /* == 0 and 7 */]; + $scheduleItems = app('db')->select(self::$SCHEDULE_SQL, + ['enddate' => $end->format('Y-m-d'), 'startdate' => $start->format('Y-m-d')]); + $scheduleChanges = new \SplPriorityQueue(); + $active = []; + foreach($scheduleItems as $item) { + // Find the start time and end time of the last occurrence on or before the start time + $itemStart = new \DateTime("last " . $WEEKDAYS[$item->startday] . " " . $start->format('Y-m-d') . " " . $item->starttime); + $itemEnd = new \DateTime("last " . $WEEKDAYS[$item->endday] . " " . $start->format('Y-m-d') . " " . $item->endtime); + if($itemEnd <= $itemStart) { + $itemEnd->add($WEEK); + } + + // If the first occurrence is completely before the period of interest, look at the next week + while($itemEnd < $start) { + $itemStart->add($WEEK); + $itemEnd->add($WEEK); + } + + // Create a list in which all start and end times are separate entries + $scheduleChanges->insert( [$START, $itemStart, $item], [-$itemStart->getTimestamp(), -$START] ); + $scheduleChanges->insert( [$END, $itemEnd, $item], [-$itemEnd->getTimestamp(), -$END] ); + + // Also keep track of all priorities seen + $active[$item->priority] = null; + } + + if($DEBUG) { + print "

Schedule changes - initial state


"; + } + + // Go through all programs and keep going until we have one that starts after the end date. + $activeProgram = null; + $activeProgramStart = null; + while(true) { + $item = $scheduleChanges->extract(); + + if($DEBUG) print "{$item[1]->format('d-m-Y H:i')}: {$item[2]->name} " . ($item[0] == $START ? "begint" : "eindigt") . "
"; + + // If we passed the end time, we are (almost) done + if($item[1] > $end) { + if($DEBUG) { print "Reached end at {$item[1]->format('d-m-Y H:i')} ({$item[2]->name}).
"; } + break; + } + + $priority = $item[2]->priority; + if($item[0] == $START) { + if($active[$priority] != null) { + print( "Error:" . $item[2]->name . ' starts at ' . $item[1]->format('d-m-Y H:i') . ' but ' . $active[$priority]->name . ' is still active then.' ); + print "
"; var_dump($active); print "
"; + } + + // When a program starts, it becomes the active program in its layer + $active[$priority] = $item[2]; + + // In addition, if it has higher priority than the currently active program, it replaces that. + if($activeProgram == null || $priority < $activeProgram->priority) { + if(($activeProgram != null) && ($item[1] > $start)) { + if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$item[1]->format('d-m-Y H:i')}: {$activeProgram->name} (reason: {$item[2]->name} starts).
"; + $this->addToSchedule($schedule, $activeProgramStart, $item[1], $activeProgram); + } + + $activeProgramStart = ($item[1] < $start) ? $start : clone $item[1]; + $activeProgram = $item[2]; + } + + } else if($item[0] == $END) { + if($active[$priority] == null) { + print( "Error:" . $item[2]->name . ' ends at ' . $item[1]->format('d-m-Y H:i') . ' but no program is active at that priority and timestamp.'); + print "
"; var_dump($active); print "
"; + } + + // When the program ends, its layer becomes inactive. + $active[$priority] = null; + + // In addition, when the ending program was the active program, we need to schedule it and find the new currently active program + if($activeProgram == $item[2]) { + if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$item[1]->format('d-m-Y H:i')}: {$activeProgram->name} (reason: {$item[2]->name} ends).
"; + $this->addToSchedule($schedule, $activeProgramStart, $item[1], $activeProgram); + + $activeProgram = null; + foreach($active as $newPriority => $newActive) { + if(($newActive != null) && ($activeProgram == null || $newPriority < $activeProgram->priority)) { + $activeProgram = $newActive; + } + } + + if($activeProgram != null) { + $activeProgramStart = clone $item[1]; + } + + if($DEBUG) print " New active program is " . ($activeProgram ? $activeProgram->name : "---") . "
"; + } + } else { + return response()->abort(500, "Invalid item type: expected START ($START) or END ($END), but got {$item[0]}."); + } + + // The item will recur in a week + $item[1]->add($WEEK); + $scheduleChanges->insert($item, [-$item[1]->getTimestamp(), -$item[0]]); + } + + if(($activeProgram != null) && ($activeProgramStart != $end)) { + if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$end->format('d-m-Y H:i')}: {$activeProgram->name} (reason: schedule finished).
"; + $this->addToSchedule($schedule, $activeProgramStart, $end, $activeProgram); + } + + return ['startdate' => $start, 'enddate' => $end, 'schedule' => $schedule]; + } + + public function testSchedule($from, $to) + { + $start = new \DateTimeImmutable(urldecode($from)); + $end = new \DateTimeImmutable(urldecode($to)); + $this->createSchedule($start, $end, true); + } + /** * Details over een specifiek programma, inclusief schema en podcasts @@ -51,88 +198,14 @@ QUERY; AND `user_emailaddresses`.`preference` = 0 AND `programs_users`.`active` = 1 ORDER BY `name`", ['program' => $program->id]); + foreach($hosts as $host) { $program->hosts[] = new \Model\ProgramHost($host); } - $schedule = app('db')->select(<< $program->id] ); - $recentSchedule = $this->recent($schedule); - $program->schedule = $recentSchedule; - return response()->json($program); } - private function recent($schedule) { - $now = new \DateTimeImmutable(); - $ONE_DAY = \DateInterval::createFromDateString('1 day'); - $ONE_WEEK = \DateInterval::createFromDateString('1 week'); - $recent = []; - $next = []; - - // Vind voor elke regel in de uitzendmomenten de eerstvolgende en twee meest recente. - $isCurrent = false; - foreach($schedule as $broadcast) { - $broadcast->startdate = ($broadcast->startdate == null) ? null : new \DateTimeImmutable($broadcast->startdate); - $broadcast->enddate = ($broadcast->enddate == null) ? null : new \DateTimeImmutable($broadcast->enddate); - $startTijd = new \DateTimeImmutable($broadcast->starttime); - $endDate = ($broadcast->enddate == null) ? $now : $broadcast->enddate; - $endDate = $endDate->setTime($startTijd->format('H'), $startTijd->format('i'), $startTijd->format('s')); - $endDateWeekday = $endDate->format('N'); - - // Zoek de laatste uitzending voor de einddatum (of voor het huidige tijdstip), op de juiste weekdag - while($endDateWeekday != $broadcast->startday) { - $endDate = $endDate->sub($ONE_DAY); - if(--$endDateWeekday < 0) { $endDateWeekday += 7; } - } - - $eindTijd = new \DateTimeImmutable($broadcast->endtime); - $endOfBroadcast = $endDate->setTime($eindTijd->format('H'), $eindTijd->format('i'), $eindTijd->format('s')); - if($endOfBroadcast < $endDate) { $endOfBroadcast = $endOfBroadcast->add($ONE_DAY); } - - if($isInProgress = (($endDate <= $now) && ($endOfBroadcast > $now))) { - $isCurrent = true; - } - - // Als de uitzending niet al afgelopen is, telt hij als volgende. - // Dit gebeurt als de uitzending later vandaag is of nu loopt. - if($endOfBroadcast >= $now) { - $endDate = $endDate->sub($ONE_WEEK); - $endOfBroadcast = $endOfBroadcast->sub($ONE_WEEK); - } - - if((($broadcast->startdate == null) || ($broadcast->startdate <= $endDate)) - && (($broadcast->enddate == null) || ($broadcast->enddate >= $endDate->setTime(0,0,0)))) { - $recent[] = ['time' => $endDate, 'suffix' => $broadcast->suffix, 'isSpecial' => ($broadcast->priority < 2)]; - } - - - $previous = $endDate->sub($ONE_WEEK); - if((($broadcast->startdate == null) || ($broadcast->startdate <= $previous)) - && (($broadcast->enddate == null) || ($broadcast->enddate >= $previous->setTime(0,0,0)))) { - $recent[] = ['time' => $previous, 'suffix' => $broadcast->suffix, 'isSpecial' => ($broadcast->priority < 2)]; - } - - $following = $endDate->add($ONE_WEEK); - if(($broadcast->enddate == null) || ($broadcast->enddate->setTime(23,59,59) > $following)) { - if($following < $now) { - $isCurrent = true; - } - - $next[] = ['time' => $following, 'suffix' => $broadcast->suffix, 'isSpecial' => ($broadcast->priority < 2)]; - } - } - - sort($next); - rsort($recent); - return ['recent' => array_reverse(array_splice($recent, 0, 2)), 'next' => count($next) ? $next[0] : null, 'iscurrent' => $isCurrent]; - } - /** * Programmas nu en straks (24 uur vooruit) */ @@ -153,109 +226,4 @@ QUERY return response()->json($this->createSchedule($start, $einde)); } - - private function createSchedule($start, $einde) { - if(!defined('PROGRAMMA_START')) { - define('PROGRAMMA_START', 1); - define('PROGRAMMA_EINDE', -1); - - define('PW_TIJDSTIP', 0); - define('PW_TYPE', 1); - define('PW_PROGRAMMA', 2); - } - - $oneWeek = \DateInterval::createFromDateString('1 week'); - - $programmas = app('db')->select(self::$SCHEDULE_SQL, - ['enddate' => $einde->format('Y-m-d'), 'startdate' => $start->format('Y-m-d')]); - $programmaInfo = []; - - // Maak een lijstje van alle start en eindtijden en welk programma dan begint/eindigt - $startEinde = []; - $startMaandag = $start->modify('Monday this week'); - foreach($programmas as $programma) { - if($programma->suffix) $programma->name .= ' ' . $programma->suffix; - $programmaInfo[$programma->scheduleid] = new \Model\Program($programma); - - $programmaStart = $startMaandag->add(\DateInterval::createFromDateString(($programma->startday - 1) % 7 . ' days')); - $programmaEinde = $startMaandag->add(\DateInterval::createFromDateString(($programma->endday - 1) % 7 . ' days')); - - $startTijd = new \DateTime($programma->starttime); - $eindTijd = new \DateTime($programma->endtime); - - $programmaStart = $programmaStart->setTime($startTijd->format('H'), $startTijd->format('i'), $startTijd->format('s')); - $programmaEinde = $programmaEinde->setTime($eindTijd->format('H'), $eindTijd->format('i'), $eindTijd->format('s')); - - if($programmaEinde <= $programmaStart) { - $programmaEinde = $programmaEinde->add($oneWeek); - } - - if($programmaEinde < $start) { - $programmaStart = $programmaStart->add($oneWeek); - $programmaEinde = $programmaEinde->add($oneWeek); - } - - while($programmaStart < $einde) { - if((($programma->startdate == null) || ($programmaStart >= new \DateTime($programma->startdate))) - && (($programma->enddate == null) || ($programmaStart <= (new \DateTimeImmutable($programma->enddate))->setTime(23,59,59))) - && (($programmaEinde >= $start) && ($programmaStart <= $einde))) - { - $startEinde[] = [$programmaStart, PROGRAMMA_START, $programma->scheduleid]; - $startEinde[] = [$programmaEinde, PROGRAMMA_EINDE, $programma->scheduleid]; - } - - $programmaStart = $programmaStart->add($oneWeek); - $programmaEinde = $programmaEinde->add($oneWeek); - } - } - - array_multisort($startEinde); - - $actieveProgrammas = [null, null, null, null]; - $schema = []; - $fouten = []; - $index = -1; - foreach($startEinde as $programmaWissel) { - $scheduleId = $programmaWissel[PW_PROGRAMMA]; - $programma = $programmaInfo[$scheduleId]; - $tijdstip = $programmaWissel[PW_TIJDSTIP]; - if($programmaWissel[PW_TIJDSTIP] < $start) { $tijdstip = $start; } - if($programmaWissel[PW_TIJDSTIP] > $einde) { $tijdstip = $einde; } - if($tijdstip == $einde) { break; } - - if($programmaWissel[PW_TYPE] == PROGRAMMA_START) { - if($actieveProgrammas[$programma->priority] != null) { - $fouten[] = [$tijdstip, "Begin van {$programma->name} maar {$actieveProgrammas[$programma->priority]->name} is nog actief."]; - } - - $actieveProgrammas[$programma->priority] = $scheduleId; - for($prio = $programma->priority; $prio >= 0; --$prio) { - if($actieveProgrammas[$prio] != null) { - if(($index == -1) || ($schema[$index]['starttime'] != $tijdstip)) { $index++; } - $schema[$index] = ['starttime' => $tijdstip, 'endtime' => null, 'program' => $programmaInfo[$actieveProgrammas[$prio]]]; - break; - } - } - } else /*if($programmaWissel[PW_TYPE] == PROGRAMMA_EINDE)*/ { - $actieveProgrammas[$programma->priority] = null; - if(($index == -1) || ($schema[$index]['program'] != null)) { $index++; } - $schema[$index] = ['starttime' => $tijdstip, 'program' => null]; - for($prio = $programma->priority; $prio < count($actieveProgrammas); ++$prio) { - if($actieveProgrammas[$prio] != null) { - $schema[$index] = ['starttime' => $tijdstip, 'endtime' => null, 'program' => $programmaInfo[$actieveProgrammas[$prio]]]; - break; - } - } - } - } - - for($i = 1; $i <= count($schema); $i++) { - $eindTijd = ($i == count($schema)) ? $einde : $schema[$i]['starttime']; - $schema[$i - 1]['endtime'] = $eindTijd; - //$programma = $schema[$i-1]['program']; - //print $schema[$i-1]['starttime']->format('D d-m-Y, H:i:s') . " - " . $eindTijd->format('D d-m-Y, H:i:s') . ": " . ($programma != null ? $programma->name : "NULL") . "\n"; - } - - return ['startdate' => $start, 'enddate' => $einde, 'schedule' => $schema, 'errors' => $fouten]; - } } diff --git a/api/routes/web.php b/api/routes/web.php index 9222f13..4919dde 100644 --- a/api/routes/web.php +++ b/api/routes/web.php @@ -32,7 +32,9 @@ $app->get('podcast/stream/{id:\d+}/{title}', 'PodcastController@stream' ); $app->get('programma/schema/nustraks', 'ProgramController@comingup' ); $app->get('programma/schema/week[/{shiftWeeks:-?\d+}]', 'ProgramController@schedule' ); -$app->get('/programma/details/{id:\d+}', 'ProgramController@details' ); +$app->get('programma/details/{id:\d+}', 'ProgramController@details' ); + +$app->get('programma/schema/test/{from}/{to}', 'ProgramController@testSchedule' ); // live/onair // live/stream diff --git a/common/classes/Program.php b/common/classes/Program.php index 358bba4..3878af3 100644 --- a/common/classes/Program.php +++ b/common/classes/Program.php @@ -9,12 +9,16 @@ class Program extends Model { public $email; public $priority; - // TODO: Implementeren public $hosts; public $schedule; public function __construct($data) { parent::__construct($data); + + if(isset($data->suffix) && $data->suffix) { + $this->name .= ' ' . $data->suffix; + } + if($this->schedule && $this->schedule->next && $this->schedule->next->time) { parent::ConvertToDateTime($this->schedule->next->time); } @@ -31,4 +35,8 @@ class Program extends Model { $this->url = "/{$this->id}/" . parent::url_slug($this->name); } + + public function isSpecial() { + return ($this->priority < 2); + } }