Bug fix to correctly handle the logic for 'maxEntries' Issue #2050

This commit is contained in:
chamakura 2020-06-20 11:01:37 -07:00
parent 6d3308621f
commit be3616abe2
3 changed files with 103 additions and 88 deletions

View File

@ -37,6 +37,7 @@ _This release is scheduled to be released on 2020-07-01._
- Support multiple instances of calendar module with different config [#1109](https://github.com/MichMich/MagicMirror/issues/1109) - Support multiple instances of calendar module with different config [#1109](https://github.com/MichMich/MagicMirror/issues/1109)
- Fix the use of "maxNumberOfDays" in the module "weatherforecast" [#2018](https://github.com/MichMich/MagicMirror/issues/2018) - Fix the use of "maxNumberOfDays" in the module "weatherforecast" [#2018](https://github.com/MichMich/MagicMirror/issues/2018)
- Throw error when check_config fails [#1928](https://github.com/MichMich/MagicMirror/issues/1928) - Throw error when check_config fails [#1928](https://github.com/MichMich/MagicMirror/issues/1928)
- Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050)
## [2.11.0] - 2020-04-01 ## [2.11.0] - 2020-04-01

View File

@ -1,49 +1,51 @@
/* Magic Mirror /* Magic Mirror
* Node Helper: Calendar - CalendarFetcher * Node Helper: Calendar - CalendarFetcher
* *
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const Log = require("../../../js/logger.js");
const ical = require("./vendor/ical.js");
const moment = require("moment");
var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) { var ical = require("./vendor/ical.js");
var moment = require("moment");
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumNumberOfDays, auth, includePastEvents) {
var self = this; var self = this;
var reloadTimer = null; var reloadTimer = null;
var events = []; var events = [];
var fetchFailedCallback = function () {}; var fetchFailedCallback = function() {};
var eventsReceivedCallback = function () {}; var eventsReceivedCallback = function() {};
/* fetchCalendar() /* fetchCalendar()
* Initiates calendar fetch. * Initiates calendar fetch.
*/ */
var fetchCalendar = function () { var fetchCalendar = function() {
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = null; reloadTimer = null;
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
var opts = { var opts = {
headers: { headers: {
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)" "User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
}, },
gzip: true gzip: true
}; };
if (auth) { if (auth) {
if (auth.method === "bearer") { if(auth.method === "bearer"){
opts.auth = { opts.auth = {
bearer: auth.pass bearer: auth.pass
}; };
} else { } else {
opts.auth = { opts.auth = {
user: auth.user, user: auth.user,
pass: auth.pass pass: auth.pass
}; };
if (auth.method === "digest") { if(auth.method === "digest"){
opts.auth.sendImmediately = false; opts.auth.sendImmediately = false;
} else { } else {
opts.auth.sendImmediately = true; opts.auth.sendImmediately = true;
@ -51,36 +53,36 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
} }
} }
ical.fromURL(url, opts, function (err, data) { ical.fromURL(url, opts, function(err, data) {
if (err) { if (err) {
fetchFailedCallback(self, err); fetchFailedCallback(self, err);
scheduleTimer(); scheduleTimer();
return; return;
} }
var newEvents = []; // console.log(data);
newEvents = [];
// limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves // limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves
var limitFunction = function (date, i) { var limitFunction = function(date, i) {return true;};
return true;
};
var eventDate = function (event, time) { var eventDate = function(event, time) {
return event[time].length === 8 ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time])); return (event[time].length === 8) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
}; };
for (var e in data) { for (var e in data) {
var event = data[e]; var event = data[e];
var now = new Date(); var now = new Date();
var today = moment().startOf("day").toDate(); var today = moment().startOf("day").toDate();
var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat. var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
var past = today; var past = today;
if (includePastEvents) { if (includePastEvents) {
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate(); past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
} }
// FIXME: Ugly fix to solve the facebook birthday issue. // FIXME:
// Ugly fix to solve the facebook birthday issue.
// Otherwise, the recurring events only show the birthday for next year. // Otherwise, the recurring events only show the birthday for next year.
var isFacebookBirthday = false; var isFacebookBirthday = false;
if (typeof event.uid !== "undefined") { if (typeof event.uid !== "undefined") {
@ -90,12 +92,13 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
} }
if (event.type === "VEVENT") { if (event.type === "VEVENT") {
var startDate = eventDate(event, "start"); var startDate = eventDate(event, "start");
var endDate; var endDate;
if (typeof event.end !== "undefined") { if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end"); endDate = eventDate(event, "end");
} else if (typeof event.duration !== "undefined") { } else if(typeof event.duration !== "undefined") {
var dur = moment.duration(event.duration); dur=moment.duration(event.duration);
endDate = startDate.clone().add(dur); endDate = startDate.clone().add(dur);
} else { } else {
if (!isFacebookBirthday) { if (!isFacebookBirthday) {
@ -173,7 +176,8 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
var addedEvents = 0; var addedEvents = 0;
// can cause problems with e.g. birthdays before 1900 // can cause problems with e.g. birthdays before 1900
if ((rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900) || (rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900)) { if(rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 ||
rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900){
rule.origOptions.dtstart.setYear(1900); rule.origOptions.dtstart.setYear(1900);
rule.options.dtstart.setYear(1900); rule.options.dtstart.setYear(1900);
} }
@ -184,7 +188,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
var pastLocal = moment(past).subtract(past.getTimezoneOffset(), "minutes").toDate(); var pastLocal = moment(past).subtract(past.getTimezoneOffset(), "minutes").toDate();
var futureLocal = moment(future).subtract(future.getTimezoneOffset(), "minutes").toDate(); var futureLocal = moment(future).subtract(future.getTimezoneOffset(), "minutes").toDate();
var datesLocal = rule.between(pastLocal, futureLocal, true, limitFunction); var datesLocal = rule.between(pastLocal, futureLocal, true, limitFunction);
var dates = datesLocal.map(function (dateLocal) { var dates = datesLocal.map(function(dateLocal) {
var date = moment(dateLocal).add(dateLocal.getTimezoneOffset(), "minutes").toDate(); var date = moment(dateLocal).add(dateLocal.getTimezoneOffset(), "minutes").toDate();
return date; return date;
}); });
@ -196,14 +200,17 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
// because the logic below will filter out any recurrences that don"t actually belong within // because the logic below will filter out any recurrences that don"t actually belong within
// our display range. // our display range.
// Would be great if there was a better way to handle this. // Would be great if there was a better way to handle this.
if (event.recurrences !== undefined) { if (event.recurrences != undefined)
{
var pastMoment = moment(past); var pastMoment = moment(past);
var futureMoment = moment(future); var futureMoment = moment(future);
for (var r in event.recurrences) { for (var r in event.recurrences)
{
// Only add dates that weren't already in the range we added from the rrule so that // Only add dates that weren't already in the range we added from the rrule so that
// we don"t double-add those events. // we don"t double-add those events.
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) !== true) { if (moment(new Date(r)).isBetween(pastMoment, futureMoment) != true)
{
dates.push(new Date(r)); dates.push(new Date(r));
} }
} }
@ -215,33 +222,29 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
// ical.js started returning recurrences and exdates as ISOStrings without time information. // ical.js started returning recurrences and exdates as ISOStrings without time information.
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same // .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
// (see https://github.com/peterbraden/ical.js/pull/84 ) // (see https://github.com/peterbraden/ical.js/pull/84 )
var dateKey = date.toISOString().substring(0, 10); var dateKey = date.toISOString().substring(0,10);
var curEvent = event; var curEvent = event;
var showRecurrence = true; var showRecurrence = true;
// Stop parsing this event's recurrences if we've already found maximumEntries worth of recurrences.
// (The logic below would still filter the extras, but the check is simple since we're already tracking the count)
if (addedEvents >= maximumEntries) {
break;
}
startDate = moment(date); startDate = moment(date);
// For each date that we"re checking, it"s possible that there is a recurrence override for that one day. // For each date that we"re checking, it"s possible that there is a recurrence override for that one day.
if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) { if ((curEvent.recurrences != undefined) && (curEvent.recurrences[dateKey] != undefined))
{
// We found an override, so for this recurrence, use a potentially different title, start date, and duration. // We found an override, so for this recurrence, use a potentially different title, start date, and duration.
curEvent = curEvent.recurrences[dateKey]; curEvent = curEvent.recurrences[dateKey];
startDate = moment(curEvent.start); startDate = moment(curEvent.start);
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x")); duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
} }
// If there"s no recurrence override, check for an exception date. Exception dates represent exceptions to the rule. // If there"s no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
else if (curEvent.exdate !== undefined && curEvent.exdate[dateKey] !== undefined) { else if ((curEvent.exdate != undefined) && (curEvent.exdate[dateKey] != undefined))
{
// This date is an exception date, which means we should skip it in the recurrence pattern. // This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false; showRecurrence = false;
} }
endDate = moment(parseInt(startDate.format("x")) + duration, "x"); endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (startDate.format("x") === endDate.format("x")) { if (startDate.format("x") == endDate.format("x")) {
endDate = endDate.endOf("day"); endDate = endDate.endOf("day");
} }
@ -257,7 +260,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
showRecurrence = false; showRecurrence = false;
} }
if (showRecurrence === true && addedEvents < maximumEntries) { if (showRecurrence === true) {
addedEvents++; addedEvents++;
newEvents.push({ newEvents.push({
title: recurrenceTitle, title: recurrenceTitle,
@ -274,28 +277,29 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
} }
// end recurring event parsing // end recurring event parsing
} else { } else {
// console.log("Single event ...");
// Single event. // Single event.
var fullDayEvent = isFacebookBirthday ? true : isFullDayEvent(event); var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
if (includePastEvents) { if (includePastEvents) {
// Past event is too far in the past, so skip.
if (endDate < past) { if (endDate < past) {
//console.log("Past event is too far in the past. So skip: " + title);
continue; continue;
} }
} else { } else {
// It's not a fullday event, and it is in the past, so skip.
if (!fullDayEvent && endDate < new Date()) { if (!fullDayEvent && endDate < new Date()) {
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
continue; continue;
} }
// It's a fullday event, and it is before today, So skip.
if (fullDayEvent && endDate <= today) { if (fullDayEvent && endDate <= today) {
//console.log("It's a fullday event, and it is before today. So skip: " + title);
continue; continue;
} }
} }
// It exceeds the maximumNumberOfDays limit, so skip.
if (startDate > future) { if (startDate > future) {
//console.log("It exceeds the maximumNumberOfDays limit. So skip: " + title);
continue; continue;
} }
@ -303,12 +307,13 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
continue; continue;
} }
// Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already // adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if (fullDayEvent && startDate <= today) { if (fullDayEvent && startDate <= today) {
startDate = moment(today); startDate = moment(today);
} }
// Every thing is good. Add it to the list. // Every thing is good. Add it to the list.
newEvents.push({ newEvents.push({
title: title, title: title,
startDate: startDate.format("x"), startDate: startDate.format("x"),
@ -319,15 +324,17 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
geo: geo, geo: geo,
description: description description: description
}); });
} }
} }
} }
newEvents.sort(function (a, b) { newEvents.sort(function(a, b) {
return a.startDate - b.startDate; return a.startDate - b.startDate;
}); });
events = newEvents.slice(0, maximumEntries); //console.log(newEvents);
events = newEvents;
self.broadcastEvents(); self.broadcastEvents();
scheduleTimer(); scheduleTimer();
@ -337,9 +344,10 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
/* scheduleTimer() /* scheduleTimer()
* Schedule the timer for the next update. * Schedule the timer for the next update.
*/ */
var scheduleTimer = function () { var scheduleTimer = function() {
//console.log('Schedule update timer.');
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = setTimeout(function () { reloadTimer = setTimeout(function() {
fetchCalendar(); fetchCalendar();
}, reloadInterval); }, reloadInterval);
}; };
@ -351,7 +359,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
* *
* return bool - The event is a fullday event. * return bool - The event is a fullday event.
*/ */
var isFullDayEvent = function (event) { var isFullDayEvent = function(event) {
if (event.start.length === 8 || event.start.dateOnly) { if (event.start.length === 8 || event.start.dateOnly) {
return true; return true;
} }
@ -359,7 +367,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
var start = event.start || 0; var start = event.start || 0;
var startDate = new Date(start); var startDate = new Date(start);
var end = event.end || 0; var end = event.end || 0;
if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) { if (((end - start) % (24 * 60 * 60 * 1000)) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
// Is 24 hours, and starts on the middle of the night. // Is 24 hours, and starts on the middle of the night.
return true; return true;
} }
@ -376,7 +384,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
* *
* return bool - The event should be filtered out * return bool - The event should be filtered out
*/ */
var timeFilterApplies = function (now, endDate, filter) { var timeFilterApplies = function(now, endDate, filter) {
if (filter) { if (filter) {
var until = filter.split(" "), var until = filter.split(" "),
value = parseInt(until[0]), value = parseInt(until[0]),
@ -390,16 +398,16 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
}; };
/* getTitleFromEvent(event) /* getTitleFromEvent(event)
* Gets the title from the event. * Gets the title from the event.
* *
* argument event object - The event object to check. * argument event object - The event object to check.
* *
* return string - The title of the event, or "Event" if no title is found. * return string - The title of the event, or "Event" if no title is found.
*/ */
var getTitleFromEvent = function (event) { var getTitleFromEvent = function (event) {
var title = "Event"; var title = "Event";
if (event.summary) { if (event.summary) {
title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary; title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
} else if (event.description) { } else if (event.description) {
title = event.description; title = event.description;
} }
@ -428,15 +436,15 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
/* startFetch() /* startFetch()
* Initiate fetchCalendar(); * Initiate fetchCalendar();
*/ */
this.startFetch = function () { this.startFetch = function() {
fetchCalendar(); fetchCalendar();
}; };
/* broadcastItems() /* broadcastItems()
* Broadcast the existing events. * Broadcast the existing events.
*/ */
this.broadcastEvents = function () { this.broadcastEvents = function() {
Log.info("Calendar-Fetcher: Broadcasting " + events.length + " events."); //console.log('Broadcasting ' + events.length + ' events.');
eventsReceivedCallback(self); eventsReceivedCallback(self);
}; };
@ -445,7 +453,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
* *
* argument callback function - The on success callback. * argument callback function - The on success callback.
*/ */
this.onReceive = function (callback) { this.onReceive = function(callback) {
eventsReceivedCallback = callback; eventsReceivedCallback = callback;
}; };
@ -454,7 +462,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
* *
* argument callback function - The on error callback. * argument callback function - The on error callback.
*/ */
this.onError = function (callback) { this.onError = function(callback) {
fetchFailedCallback = callback; fetchFailedCallback = callback;
}; };
@ -463,7 +471,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
* *
* return string - The url of this fetcher. * return string - The url of this fetcher.
*/ */
this.url = function () { this.url = function() {
return url; return url;
}; };
@ -472,7 +480,7 @@ var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntr
* *
* return array - The current available events for this fetcher. * return array - The current available events for this fetcher.
*/ */
this.events = function () { this.events = function() {
return events; return events;
}; };
}; };

View File

@ -1,26 +1,30 @@
/* Magic Mirror /* Magic Mirror
* Node Helper: Calendar * Node Helper: Calendar
* *
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const NodeHelper = require("node_helper"); var NodeHelper = require("node_helper");
const validUrl = require("valid-url"); var validUrl = require("valid-url");
const CalendarFetcher = require("./calendarfetcher.js"); var CalendarFetcher = require("./calendarfetcher.js");
const Log = require("../../../js/logger");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
// Override start method. // Override start method.
start: function () { start: function() {
Log.log("Starting node helper for: " + this.name); var events = [];
this.fetchers = []; this.fetchers = [];
console.log("Starting node helper for: " + this.name);
}, },
// Override socketNotificationReceived method. // Override socketNotificationReceived method.
socketNotificationReceived: function (notification, payload) { socketNotificationReceived: function(notification, payload) {
if (notification === "ADD_CALENDAR") { if (notification === "ADD_CALENDAR") {
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id); //console.log('ADD_CALENDAR: ');
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents);
} }
}, },
@ -31,40 +35,42 @@ module.exports = NodeHelper.create({
* attribute url string - URL of the news feed. * attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds. * attribute reloadInterval number - Reload interval in milliseconds.
*/ */
createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) {
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) {
var self = this; var self = this;
if (!validUrl.isUri(url)) { if (!validUrl.isUri(url)) {
self.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url }); self.sendSocketNotification("INCORRECT_URL", {url: url});
return; return;
} }
var fetcher; var fetcher;
if (typeof self.fetchers[identifier + url] === "undefined") { if (typeof self.fetchers[url] === "undefined") {
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents); fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumNumberOfDays, auth, broadcastPastEvents);
fetcher.onReceive(function(fetcher) {
//console.log('Broadcast events.');
//console.log(fetcher.events());
fetcher.onReceive(function (fetcher) {
self.sendSocketNotification("CALENDAR_EVENTS", { self.sendSocketNotification("CALENDAR_EVENTS", {
id: identifier,
url: fetcher.url(), url: fetcher.url(),
events: fetcher.events() events: fetcher.events()
}); });
}); });
fetcher.onError(function (fetcher, error) { fetcher.onError(function(fetcher, error) {
Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error); console.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
self.sendSocketNotification("FETCH_ERROR", { self.sendSocketNotification("FETCH_ERROR", {
id: identifier,
url: fetcher.url(), url: fetcher.url(),
error: error error: error
}); });
}); });
self.fetchers[identifier + url] = fetcher; self.fetchers[url] = fetcher;
} else { } else {
Log.log("Use existing calendar fetcher for url: " + url); //console.log('Use existing news fetcher for url: ' + url);
fetcher = self.fetchers[identifier + url]; fetcher = self.fetchers[url];
fetcher.broadcastEvents(); fetcher.broadcastEvents();
} }