Skip to content

Commit

Permalink
fix(parser): abort expanding recurring events when end is reached
Browse files Browse the repository at this point in the history
  • Loading branch information
tripodsan committed Dec 3, 2019
1 parent 643ff02 commit 4d54c2c
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 29 deletions.
2 changes: 1 addition & 1 deletion lib/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function createCalendar(request) {
const xmlRequest = byTimeTemplate({ start, end });

return request(this.config, "REPORT", 1, xmlRequest)
.then(xmlParser.parseEvents)
.then(xml => xmlParser.parseEvents(xml, start, end))
.then(events => {
return events.filter(event => {
const isNotRecurring = !event.data.type.recurring;
Expand Down
63 changes: 35 additions & 28 deletions lib/xml/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,15 @@ function getModifiedOccurenceEventData(vevent, eventData) {
};
}

function parseEvents(xml) {
function parseEvents(xml, start, end) {
let parsed;
const formatted = [];

return parseXMLString(xml, PARSE_XML_CONFIG).then((result) => {
if (!result.multistatus || !result.multistatus.response) {
return formatted;
}
const iEnd = end ? ICAL.Time.fromDateTimeString(moment(end).toISOString()) : null;

parsed = result.multistatus.response;

Expand Down Expand Up @@ -183,42 +184,48 @@ function parseEvents(xml) {

// Since there are infinite rules, its a good idea to limit the scope
// of the iteration then resume later on
// Expand upto a maximum of 10 upcoming occurances
for (let i = 0; i < 10; i++) {
// Expand upto a maximum of 10 upcoming occurances or when end is reached
for (let i = 0; iEnd || i < 10; i++) {
const nextOccuranceTime = expand.next();

if (!expand.complete) {
// Handle this next expanded occurence
const nextEvent = icalEvent.getOccurrenceDetails(nextOccuranceTime);
if (expand.complete) {
break;
}
// Handle this next expanded occurence
const nextEvent = icalEvent.getOccurrenceDetails(nextOccuranceTime);

// if event is past specified range, terminate expansion
if (iEnd && nextEvent.startDate.compare(iEnd) > 0) {
break;
}

eventData = {};
eventData = {};

if (modifiedOccurences.length === 0) {
// No events have been modified
formatted.push({
ics: event.href[0],
etag,
data: getNormalOccurenceEventData(nextEvent, eventData, vevent)
});
} else {
// There are modified occurences
if (isModified(nextOccuranceTime, modifiedOccurences)) {
// This is the event that has been modied
const key = getModifiedOccuranceKey(nextOccuranceTime, modifiedOccurences) || 0;

if (modifiedOccurences.length === 0) {
// No events have been modified
formatted.push({
ics: event.href[0],
etag,
data: getNormalOccurenceEventData(nextEvent, eventData, vevent)
data: getModifiedOccurenceEventData(vevents[key], eventData)
});
} else {
// There are modified occurences
if (isModified(nextOccuranceTime, modifiedOccurences)) {
// This is the event that has been modied
const key = getModifiedOccuranceKey(nextOccuranceTime, modifiedOccurences) || 0;

formatted.push({
ics: event.href[0],
etag,
data: getModifiedOccurenceEventData(vevents[key], eventData)
});
} else {
// Expand this event normally
formatted.push({
ics: event.href[0],
etag,
data: getNormalOccurenceEventData(nextEvent, eventData, vevent)
});
}
// Expand this event normally
formatted.push({
ics: event.href[0],
etag,
data: getNormalOccurenceEventData(nextEvent, eventData, vevent)
});
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions test/Calendar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,40 @@ describe("Calendar", () => {
});
});

it("should return an array of objects with all events that occur between start and end dates including recurring", () => {
const response = fixtures.getRecurringEventsResponse;
const request = Promise.resolve(response);
const Calendar = createCalendar(() => request);
const calendar = new Calendar(config);

return calendar
.getEventsByTime("20170201T000000Z", "20180201T000000Z")
.then((response) => {
expect(response).to.be.an("array");
expect(response).to.have.lengthOf(51);
expect(response[0]).to.have.property("ics");
expect(response[0]).to.have.property("etag");
expect(response[0]).to.have.property("data");
});
});

it("should return an array of objects with all events that occur between start and end dates including recurring open ended", () => {
const response = fixtures.getRecurringEventsResponse;
const request = Promise.resolve(response);
const Calendar = createCalendar(() => request);
const calendar = new Calendar(config);

return calendar
.getEventsByTime("20170201T000000Z")
.then((response) => {
expect(response).to.be.an("array");
expect(response).to.have.lengthOf(10);
expect(response[0]).to.have.property("ics");
expect(response[0]).to.have.property("etag");
expect(response[0]).to.have.property("data");
});
});

it("should return an array of objects with all upcoming events from today if start and end are left out", () => {
const response = fixtures.getFutureEventsResponse;
const request = Promise.resolve(response);
Expand Down
49 changes: 49 additions & 0 deletions test/fixtures/eventsAllRecurring.response.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<!--
~ Copyright 2019 Adobe. All rights reserved.
~ This file is licensed to you under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License. You may obtain a copy
~ of the License at http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software distributed under
~ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
~ OF ANY KIND, either express or implied. See the License for the specific language
~ governing permissions and limitations under the License.
-->

<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:
params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:card="urn:ietf
:params:xml:ns:carddav">
<d:response>
<d:href>/cal.php/calendars/antosan/default/event5.ics</d:href>
<d:propstat>
<d:prop>
<cal:calendar-data>BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20170118T143300Z
LAST-MODIFIED:20170125T142648Z
DTSTAMP:20170125T142648Z
UID:[email protected]
SUMMARY:Repeating Event on Wednesdays
STATUS:CONFIRMED
RRULE:FREQ=WEEKLY;COUNT=100;BYDAY=WE
EXDATE;VALUE=DATE:20170222
EXDATE;VALUE=DATE:20170208
DTSTART;VALUE=DATE:20170201
DTEND;VALUE=DATE:20170202
DESCRIPTION:#Repeating
LOCATION:Am Silbermannpark\, 86161 Augsburg\, Germany
SEQUENCE:0
TRANSP:TRANSPARENT
X-MOZ-GENERATION:2
END:VEVENT
END:VCALENDAR
</cal:calendar-data>
<d:getetag>&quot;049d0b4228f3c4aaf8ff097a4bebc166&quot;</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
1 change: 1 addition & 0 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const fixtures = {
getEtagsNoNamespaceResponse: fs.readFileSync(path.join(__dirname, "/etagsNoNamespace.response.xml"), "utf8"),
getEventsResponse: fs.readFileSync(path.join(__dirname, "/events.response.xml"), "utf8"),
getAllEventsResponse: fs.readFileSync(path.join(__dirname, "/eventsAll.response.xml"), "utf8"),
getRecurringEventsResponse: fs.readFileSync(path.join(__dirname, "/eventsAllRecurring.response.xml"), "utf8"),
getAllEventsNoNamespaceResponse: fs.readFileSync(path.join(__dirname, "/eventsAllNoNamespace.response.xml"), "utf8"),
getEventsByTimeResponse: fs.readFileSync(path.join(__dirname, "/eventsByTime.response.xml"), "utf8"),
getFutureEventsResponse: fs.readFileSync(path.join(__dirname, "/eventsFuture.response.xml"), "utf8")
Expand Down

0 comments on commit 4d54c2c

Please sign in to comment.