123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- import ICAL from "ical.js";
- import shows from "./shows";
- function max(a, b) {
- if (a < b) return b;
- return a;
- }
- /**
- * Represent a schedule (ie: a .ics file)
- */
- class RadioSchedule {
- constructor(calendar, radio) {
- this.calendar = calendar; // ICAL.Calendar
- this.radio = radio; // radiomanifest.Radio
- }
- /**
- * Get a list of all known {@link vEvent}s
- *
- * @returns {Array<external:ICAL~Component>}
- * */
- getEvents() {
- return this.calendar.getAllSubcomponents("vevent");
- }
- /**
- * @returns {RadioShow} tries to get a matching show, or create a new one
- */
- getShowByEvent(ev) {
- if (ev === null) return null;
- if (this.radio !== undefined) {
- const showid = RadioSchedule.veventGetShowID(ev);
- var show = this.radio.getShowByName(showid);
- if (show === null || show === undefined) {
- return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
- }
- return show;
- }
- return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
- }
- /**
- * @returns {external:ICAL~Component} if nothing is going on right now, `null` is returned
- */
- getNowEvent(now) {
- var ev_now = this.getEvents().filter(function (vevent) {
- const ev = new ICAL.Event(vevent);
- return isNow(ev, now);
- });
- ev_now.sort((e1, e2) => {
- return this.veventGetPriority(e1) - this.veventGetPriority(e2);
- });
- if (ev_now.length === 0) return null;
- return ev_now[0];
- }
- /**
- * @returns {RadioShow} if nothing is going on right now, `null` is returned
- */
- getNowShow(now) {
- const ev = this.getNowEvent(now);
- return this.getShowByEvent(ev);
- }
- /**
- * @returns {NextEvent} if there is none, `null` is returned
- */
- getNextEvent(now) {
- var nowEvent = this.getNowEvent(now);
- let future_events = this.getEvents()
- .filter((e) => {
- return e != nowEvent;
- })
- .map((e) => {
- const vEvent = new ICAL.Event(e);
- return { event: e, time: getNext(vEvent, now) };
- })
- .filter((x) => {
- return x.time !== null && x.time !== undefined;
- });
- // since ".sort()" is guaranteed to be stable, we can sort by priority, then by date, so that two events
- // starting at the same time will be sorted observing priority
- future_events.sort(
- (x1, x2) =>
- this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event),
- );
- future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime());
- if (future_events.length === 0) {
- return null;
- }
- return future_events[0];
- }
- /**
- * @returns {NextShow} if there is no next show, null will be returned
- */
- getNextShow(now) {
- const next = this.getNextEvent(now);
- if (next === null) return null;
- const ev = next.event;
- const show = this.getShowByEvent(ev);
- return { show: show, time: next.time };
- }
- static veventGetSummary(vevent) {
- return vevent.getFirstProperty("summary").getFirstValue();
- }
- static veventGetShowID(vevent) {
- return RadioSchedule.veventGetSummary(vevent); // XXX: X-Show-ID
- }
- /**
- * @return {integer} a normalized version of priority, easier to compare
- */
- veventGetPriority(ev) {
- const prop = ev.getFirstProperty("priority");
- let prio;
- if (prop === null) {
- prio = null;
- } else {
- prio = prop.getFirstValue();
- }
- if (prio === null || prio === 0) {
- prio = 100;
- }
- return prio;
- }
- }
- function isNow(vEvent, now) {
- if (now === undefined) {
- now = ICAL.Time.now();
- }
- if (vEvent.isRecurring()) {
- return isNowRecurring(vEvent, now);
- }
- return now < vEvent.endDate && now > vEvent.startDate;
- }
- function isNowRecurring(vEvent, now) {
- var expand = vEvent.iterator(vEvent.startDate);
- var next, next_end;
- while ((next = expand.next())) {
- next_end = next.clone();
- next_end.addDuration(vEvent.duration);
- if (next_end > now) {
- break;
- }
- }
- return now < next_end && now > next;
- }
- /*
- * @private
- * @param {external:ICAL~Component} vEvent a _recurring_ vEvent
- * @param {external:ICAL~Time} [now]
- * @return {external:ICAL~Time} first future occurrence of this event
- */
- function getNext(vEvent, now) {
- if (now === undefined) {
- now = ICAL.Time.now();
- }
- if (vEvent.isRecurring()) {
- return getNextRecurring(vEvent, now);
- }
- if (vEvent.endDate > now) {
- const val = max(now, vEvent.startDate);
- return val;
- }
- return null;
- }
- /*
- * @private
- * @param {external:ICAL~Component} vEvent a _recurring_ vEvent
- * @param {external:ICAL~Time} now
- * @return {external:ICAL~Time} first future occurrence of this event
- */
- function getNextRecurring(vEvent, now) {
- var expand = vEvent.iterator(vEvent.startDate);
- var next, next_end;
- while ((next = expand.next())) {
- const start = next.clone();
- next_end = start.clone();
- next_end.addDuration(vEvent.duration);
- if (next_end <= now) {
- continue;
- }
- return max(start, now);
- }
- return null;
- }
- async function get(manifest) {
- if (manifest.scheduleURL) {
- let resp = null;
- try {
- resp = await fetch(manifest.scheduleURL);
- } catch (e) {
- true;
- }
- if (resp !== null) {
- try {
- const text = await resp.text();
- return parse(text);
- } catch (e) {
- console.error("Error while parsing schedule", e);
- throw e;
- }
- }
- }
- }
- /**
- * Parse ICAL and get a RadioSchedule
- *
- * @param {string} text The text, in ICS format
- * @returns {RadioSchedule}
- */
- function parse(text) {
- var jcalData = ICAL.parse(text);
- var vcalendar = new ICAL.Component(jcalData);
- return new RadioSchedule(vcalendar);
- }
- export default {
- get: get,
- parse: parse,
- RadioSchedule: RadioSchedule,
- };
- /**
- * @typedef {Object} NextShow
- * @property {RadioShow} show The next show scheduled
- * @property {external:ICAL~Time} time When it will start
- */
- /**
- * @typedef {Object} NextEvent
- * @property {RadioShow} event The next show scheduled
- * @property {external:ICAL~Time} time When it will start
- */
- /**
- * @external ICAL
- * @see https://mozilla-comm.github.io/ical.js/api/index.html
- */
- /**
- * @class Component
- * @memberof external:ICAL
- * @inner
- * @see https://mozilla-comm.github.io/ical.js/api/ICAL.Component.html
- */
- /**
- * @class Time
- * @memberof external:ICAL
- * @inner
- * @see https://mozilla-comm.github.io/ical.js/api/ICAL.Time.html
- */
|