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
- */
|