import ICAL from 'ical.js' import shows from './shows' function max(a, b) { if (a} * */ 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 */