Fix Google Calendar rate limiting issues with retry logic and delays
Co-authored-by: clayschaad <11883505+clayschaad@users.noreply.github.com>
This commit is contained in:
parent
66a05f8b8a
commit
419dd4453f
1 changed files with 67 additions and 11 deletions
|
|
@ -5,6 +5,7 @@ using Google.Apis.Services;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using ShiftScheduler.Shared;
|
using ShiftScheduler.Shared;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace ShiftScheduler.Services;
|
namespace ShiftScheduler.Services;
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ public class GoogleCalendarService : IGoogleCalendarService
|
||||||
{
|
{
|
||||||
var service = await CreateCalendarServiceAsync();
|
var service = await CreateCalendarServiceAsync();
|
||||||
var request = service.CalendarList.List();
|
var request = service.CalendarList.List();
|
||||||
var response = await request.ExecuteAsync();
|
var response = await ExecuteWithRetryAsync(async () => await request.ExecuteAsync());
|
||||||
|
|
||||||
return response.Items?.Where(c => c.AccessRole == "owner" || c.AccessRole == "writer").ToList() ?? [];
|
return response.Items?.Where(c => c.AccessRole == "owner" || c.AccessRole == "writer").ToList() ?? [];
|
||||||
}
|
}
|
||||||
|
|
@ -47,11 +48,11 @@ public class GoogleCalendarService : IGoogleCalendarService
|
||||||
// Delete existing shift events in the date range
|
// Delete existing shift events in the date range
|
||||||
await DeleteExistingShiftEventsAsync(service, calendarId, minDate, maxDate);
|
await DeleteExistingShiftEventsAsync(service, calendarId, minDate, maxDate);
|
||||||
|
|
||||||
// Create new events for each shift
|
// Add a small delay to avoid rate limiting
|
||||||
foreach (var shiftWithTransport in shifts)
|
await Task.Delay(500);
|
||||||
{
|
|
||||||
await CreateShiftEventsAsync(service, calendarId, shiftWithTransport);
|
// Create new events for each shift with rate limiting protection
|
||||||
}
|
await CreateShiftEventsWithRateLimitingAsync(service, calendarId, shifts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteExistingShiftEventsAsync(CalendarService service, string calendarId, DateTime startDate, DateTime endDate)
|
private async Task DeleteExistingShiftEventsAsync(CalendarService service, string calendarId, DateTime startDate, DateTime endDate)
|
||||||
|
|
@ -62,20 +63,41 @@ public class GoogleCalendarService : IGoogleCalendarService
|
||||||
request.SingleEvents = true;
|
request.SingleEvents = true;
|
||||||
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
|
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
|
||||||
|
|
||||||
var response = await request.ExecuteAsync();
|
var response = await ExecuteWithRetryAsync(async () => await request.ExecuteAsync());
|
||||||
|
|
||||||
if (response.Items != null)
|
if (response.Items != null)
|
||||||
{
|
{
|
||||||
var shiftsToDelete = response.Items.Where(e =>
|
var shiftsToDelete = response.Items.Where(e =>
|
||||||
e.ExtendedProperties?.Private__?.ContainsKey("shiftSchedulerEvent") == true);
|
e.ExtendedProperties?.Private__?.ContainsKey("shiftSchedulerEvent") == true).ToList();
|
||||||
|
|
||||||
|
// Delete events with rate limiting protection
|
||||||
foreach (var eventToDelete in shiftsToDelete)
|
foreach (var eventToDelete in shiftsToDelete)
|
||||||
{
|
{
|
||||||
await service.Events.Delete(calendarId, eventToDelete.Id).ExecuteAsync();
|
await ExecuteWithRetryAsync(async () =>
|
||||||
|
{
|
||||||
|
await service.Events.Delete(calendarId, eventToDelete.Id).ExecuteAsync();
|
||||||
|
return true; // Return something for the generic method
|
||||||
|
});
|
||||||
|
|
||||||
|
// Small delay between deletions to avoid rate limiting
|
||||||
|
if (shiftsToDelete.Count > 1)
|
||||||
|
await Task.Delay(200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateShiftEventsWithRateLimitingAsync(CalendarService service, string calendarId, List<ShiftWithTransport> shifts)
|
||||||
|
{
|
||||||
|
foreach (var shiftWithTransport in shifts)
|
||||||
|
{
|
||||||
|
await CreateShiftEventsAsync(service, calendarId, shiftWithTransport);
|
||||||
|
|
||||||
|
// Add delay between shift processing to avoid rate limiting
|
||||||
|
if (shifts.Count > 1)
|
||||||
|
await Task.Delay(300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CreateShiftEventsAsync(CalendarService service, string calendarId, ShiftWithTransport shiftWithTransport)
|
private async Task CreateShiftEventsAsync(CalendarService service, string calendarId, ShiftWithTransport shiftWithTransport)
|
||||||
{
|
{
|
||||||
var shift = shiftWithTransport.Shift;
|
var shift = shiftWithTransport.Shift;
|
||||||
|
|
@ -89,14 +111,14 @@ public class GoogleCalendarService : IGoogleCalendarService
|
||||||
if (!string.IsNullOrEmpty(shift.MorningTime))
|
if (!string.IsNullOrEmpty(shift.MorningTime))
|
||||||
{
|
{
|
||||||
var morningEvent = CreateEventFromShift(shift, date, shift.MorningTime, "Morning", shiftWithTransport.MorningTransport);
|
var morningEvent = CreateEventFromShift(shift, date, shift.MorningTime, "Morning", shiftWithTransport.MorningTransport);
|
||||||
await service.Events.Insert(morningEvent, calendarId).ExecuteAsync();
|
await ExecuteWithRetryAsync(async () => await service.Events.Insert(morningEvent, calendarId).ExecuteAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create afternoon event if it exists
|
// Create afternoon event if it exists
|
||||||
if (!string.IsNullOrEmpty(shift.AfternoonTime))
|
if (!string.IsNullOrEmpty(shift.AfternoonTime))
|
||||||
{
|
{
|
||||||
var afternoonEvent = CreateEventFromShift(shift, date, shift.AfternoonTime, "Afternoon", shiftWithTransport.AfternoonTransport);
|
var afternoonEvent = CreateEventFromShift(shift, date, shift.AfternoonTime, "Afternoon", shiftWithTransport.AfternoonTransport);
|
||||||
await service.Events.Insert(afternoonEvent, calendarId).ExecuteAsync();
|
await ExecuteWithRetryAsync(async () => await service.Events.Insert(afternoonEvent, calendarId).ExecuteAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,4 +188,38 @@ public class GoogleCalendarService : IGoogleCalendarService
|
||||||
ApplicationName = "ShiftScheduler"
|
ApplicationName = "ShiftScheduler"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, int maxRetries = 3)
|
||||||
|
{
|
||||||
|
var delay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
for (var attempt = 0; attempt <= maxRetries; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await operation();
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex) when (ex.Message.Contains("429") && attempt < maxRetries)
|
||||||
|
{
|
||||||
|
// Rate limit exceeded (HTTP 429), wait with exponential backoff
|
||||||
|
await Task.Delay(delay);
|
||||||
|
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2); // Exponential backoff
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex.Message.Contains("Rate Limit") && attempt < maxRetries)
|
||||||
|
{
|
||||||
|
// Another form of rate limit error
|
||||||
|
await Task.Delay(delay);
|
||||||
|
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2); // Exponential backoff
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex.Message.Contains("Forbidden") && ex.Message.Contains("quota") && attempt < maxRetries)
|
||||||
|
{
|
||||||
|
// Quota exceeded error
|
||||||
|
await Task.Delay(delay);
|
||||||
|
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2); // Exponential backoff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, all retries failed, execute one more time to get the actual exception
|
||||||
|
return await operation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue