Skip to content

Commit 90157f5

Browse files
authored
Improve reliability of source calendar retrieval by enabling retries for intermittent HTTP failures (#403)
1 parent 3c36a55 commit 90157f5

File tree

2 files changed

+56
-37
lines changed

2 files changed

+56
-37
lines changed

Code.gs

+9
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ var addedEvents = [];
145145
var modifiedEvents = [];
146146
var removedEvents = [];
147147

148+
// Syncing logic can set this to true to cause the Google Apps Script "Executions" dashboard to report failure
149+
var reportOverallFailure = false;
150+
148151
function startSync(){
149152
if (PropertiesService.getUserProperties().getProperty('LastRun') > 0 && (new Date().getTime() - PropertiesService.getUserProperties().getProperty('LastRun')) < 360000) {
150153
Logger.log("Another iteration is currently running! Exiting...");
@@ -249,4 +252,10 @@ function startSync(){
249252
}
250253
Logger.log("Sync finished!");
251254
PropertiesService.getUserProperties().setProperty('LastRun', 0);
255+
256+
if (reportOverallFailure) {
257+
// Cause the Google Apps Script "Executions" dashboard to show a failure
258+
// (the message text does not seem to be logged anywhere)
259+
throw new Error('The sync operation produced errors. See log for details.');
260+
}
252261
}

Helpers.gs

+47-37
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function formatDate(date) {
4444

4545
const time = date.slice(11,16)
4646
const timeZone = date.slice(19)
47-
47+
4848
return formattedDate + " at " + time + " (UTC" + (timeZone == "Z" ? "": timeZone) + ")"
4949
}
5050

@@ -132,39 +132,45 @@ function fetchSourceCalendars(sourceCalendarURLs){
132132
for (var source of sourceCalendarURLs){
133133
var url = source[0].replace("webcal://", "https://");
134134
var colorId = source[1];
135-
136-
callWithBackoff(function() {
137-
var urlResponse = UrlFetchApp.fetch(url, { 'validateHttpsCertificates' : false, 'muteHttpExceptions' : true });
138-
if (urlResponse.getResponseCode() == 200){
139-
var icsContent = urlResponse.getContentText()
140-
const icsRegex = RegExp("(BEGIN:VCALENDAR.*?END:VCALENDAR)", "s")
141-
var urlContent = icsRegex.exec(icsContent);
142-
if (urlContent == null){
143-
// Microsoft Outlook has a bug that sometimes results in incorrectly formatted ics files. This tries to fix that problem.
144-
// Add END:VEVENT for every BEGIN:VEVENT that's missing it
145-
const veventRegex = /BEGIN:VEVENT(?:(?!END:VEVENT).)*?(?=.BEGIN|.END:VCALENDAR|$)/sg;
146-
icsContent = icsContent.replace(veventRegex, (match) => match + "\nEND:VEVENT");
147-
148-
// Add END:VCALENDAR if missing
149-
if (!icsContent.endsWith("END:VCALENDAR")){
150-
icsContent += "\nEND:VCALENDAR";
151-
}
152-
urlContent = icsRegex.exec(icsContent)
135+
136+
try {
137+
callWithBackoff(function() {
138+
var urlResponse = UrlFetchApp.fetch(url, { 'validateHttpsCertificates' : false, 'muteHttpExceptions' : true });
139+
if (urlResponse.getResponseCode() == 200){
140+
var icsContent = urlResponse.getContentText()
141+
const icsRegex = RegExp("(BEGIN:VCALENDAR.*?END:VCALENDAR)", "s")
142+
var urlContent = icsRegex.exec(icsContent);
153143
if (urlContent == null){
154-
Logger.log("[ERROR] Incorrect ics/ical URL: " + url)
155-
return
144+
// Microsoft Outlook has a bug that sometimes results in incorrectly formatted ics files. This tries to fix that problem.
145+
// Add END:VEVENT for every BEGIN:VEVENT that's missing it
146+
const veventRegex = /BEGIN:VEVENT(?:(?!END:VEVENT).)*?(?=.BEGIN|.END:VCALENDAR|$)/sg;
147+
icsContent = icsContent.replace(veventRegex, (match) => match + "\nEND:VEVENT");
148+
149+
// Add END:VCALENDAR if missing
150+
if (!icsContent.endsWith("END:VCALENDAR")){
151+
icsContent += "\nEND:VCALENDAR";
152+
}
153+
urlContent = icsRegex.exec(icsContent)
154+
if (urlContent == null){
155+
Logger.log("[ERROR] Incorrect ics/ical URL: " + url)
156+
reportOverallFailure = true;
157+
return
158+
}
159+
Logger.log("[WARNING] Microsoft is incorrectly formatting ics/ical at: " + url)
156160
}
157-
Logger.log("[WARNING] Microsoft is incorrectly formatting ics/ical at: " + url)
161+
result.push([urlContent[0], colorId]);
162+
return;
158163
}
159-
result.push([urlContent[0], colorId]);
160-
return;
161-
}
162-
else{ //Throw here to make callWithBackoff run again
163-
throw "Error: Encountered HTTP error " + urlResponse.getResponseCode() + " when accessing " + url;
164-
}
165-
}, defaultMaxRetries);
164+
else{ //Throw here to make callWithBackoff run again
165+
throw "Error: Encountered HTTP error " + urlResponse.getResponseCode() + " when accessing " + url;
166+
}
167+
}, defaultMaxRetries);
168+
}
169+
catch (e) {
170+
reportOverallFailure = true;
171+
}
166172
}
167-
173+
168174
return result;
169175
}
170176

@@ -242,7 +248,7 @@ function parseResponses(responses){
242248
});
243249
}
244250

245-
//No need to process calcelled events as they will be added to gcal's trash anyway
251+
//No need to process cancelled events as they will be added to gcal's trash anyway
246252
result = result.filter(function(event){
247253
try{
248254
return (event.getFirstPropertyValue('status').toString().toLowerCase() != "cancelled");
@@ -436,7 +442,7 @@ function createEvent(event, calendarTz){
436442
if (organizerMail)
437443
newEvent.organizer.email = organizerMail.toString();
438444

439-
if (addOrganizerToTitle && organizerName){
445+
if (addOrganizerToTitle && organizerName){
440446
newEvent.summary = organizerName + ": " + newEvent.summary;
441447
}
442448
}
@@ -1090,7 +1096,14 @@ function sendSummary() {
10901096
var backoffRecoverableErrors = [
10911097
"service invoked too many times in a short time",
10921098
"rate limit exceeded",
1093-
"internal error"];
1099+
"internal error",
1100+
"http error 403", // forbidden
1101+
"http error 408", // request timeout
1102+
"http error 423", // locked
1103+
"http error 500", // internal server error
1104+
"http error 503", // service unavailable
1105+
"http error 504" // gateway timeout
1106+
];
10941107
function callWithBackoff(func, maxRetries) {
10951108
var tries = 0;
10961109
var result;
@@ -1102,10 +1115,7 @@ function callWithBackoff(func, maxRetries) {
11021115
}
11031116
catch(err){
11041117
err = err.message || err;
1105-
if ( err.includes("HTTP error") ) {
1106-
Logger.log(err);
1107-
return null;
1108-
} else if ( err.includes("is not a function") || !backoffRecoverableErrors.some(function(e){
1118+
if ( err.includes("is not a function") || !backoffRecoverableErrors.some(function(e){
11091119
return err.toLowerCase().includes(e);
11101120
}) ) {
11111121
throw err;

0 commit comments

Comments
 (0)