more gcal fixes

This commit is contained in:
2026-04-09 16:26:17 +02:00
parent 6390211d5e
commit 51a6a8be83
2 changed files with 73 additions and 26 deletions

View File

@@ -325,11 +325,49 @@ def delete_all_gcal_events(service=None):
return count
def _execute_in_chunks(service, request_callback_pairs, chunk_size=30, delay=12):
"""
Execute API requests in small batches with a sleep between chunks.
Google Calendar API allows 500 requests per 100 seconds per user (~5 req/s).
Default: 30 requests per chunk, 12 s sleep → ~2.5 req/s average.
request_callback_pairs: list of (request, callback_or_None)
"""
total = len(request_callback_pairs)
for i in range(0, total, chunk_size):
chunk = request_callback_pairs[i : i + chunk_size]
batch = service.new_batch_http_request()
for req, cb in chunk:
if cb is not None:
batch.add(req, callback=cb)
else:
batch.add(req)
try:
batch.execute()
except Exception as e:
logger.error(f"Error executing batch chunk {i // chunk_size + 1}: {e}")
status = getattr(e, "status_code", None) or getattr(e, "resp", {}).get("status")
if str(status) in ("401", "403"):
_invalidate_service_on_error(e)
return # auth broken, no point continuing
if i + chunk_size < total:
logger.info(
f"Chunk {i // chunk_size + 1} done "
f"({min(i + chunk_size, total)}/{total}), "
f"sleeping {delay}s to stay within rate limits..."
)
time.sleep(delay)
def sync_from_local_to_google(service=None):
"""
Creates a google event for each local event (if it does not exist yet) and
deletes all google events that are not found in local database.
Updates participation info of gcal events using local data.
Creates are processed in chunks (future events first) to avoid rate limits.
"""
if service is None:
service = get_service_object()
@@ -375,26 +413,43 @@ def sync_from_local_to_google(service=None):
if django_id not in local_events_django_id
}
batch = service.new_batch_http_request()
batch_is_empty = True
# --- Deletes (usually few, single batch is fine) ---
if events_to_delete_google_id:
delete_pairs = [
(service.events().delete(calendarId="primary", eventId=gcal_id), None)
for gcal_id in events_to_delete_google_id
]
_execute_in_chunks(service, delete_pairs)
for event_django_id in events_to_create_django_id:
# --- Creates: future events first (soonest upcoming), then past events ---
today = datetime.date.today()
future_ids = list(
Event.objects.filter(pk__in=events_to_create_django_id, date__gte=today)
.order_by("date")
.values_list("pk", flat=True)
)
past_ids = list(
Event.objects.filter(pk__in=events_to_create_django_id, date__lt=today)
.order_by("-date")
.values_list("pk", flat=True)
)
ordered_create_ids = future_ids + past_ids
create_pairs = []
for event_django_id in ordered_create_ids:
try:
event = Event.objects.get(pk=event_django_id)
batch.add(
create_gcal_event_request(service, event),
callback=on_gcal_event_created,
create_pairs.append(
(create_gcal_event_request(service, event), on_gcal_event_created)
)
batch_is_empty = False
except Event.DoesNotExist:
pass
for event_google_id in events_to_delete_google_id:
batch.add(
service.events().delete(calendarId="primary", eventId=event_google_id)
)
batch_is_empty = False
if create_pairs:
_execute_in_chunks(service, create_pairs)
# --- Updates: attendee status changes ---
update_pairs = []
for gcal_ev in all_events:
try:
event_django_id = int(
@@ -405,23 +460,15 @@ def sync_from_local_to_google(service=None):
gcal_attendees = gcal_ev.get("attendees", [])
local_attendees = build_gcal_attendees_obj(django_ev)
# Simple comparison - check if attendees differ
if gcal_attendees != local_attendees:
batch.add(update_gcal_event_request(service, django_ev))
batch_is_empty = False
update_pairs.append((update_gcal_event_request(service, django_ev), None))
except Event.DoesNotExist:
pass
except (KeyError, ValueError):
pass
if not batch_is_empty:
try:
batch.execute()
except Exception as e:
logger.error(f"Error executing batch request: {e}")
status = getattr(e, 'status_code', None) or getattr(e, 'resp', {}).get('status')
if str(status) in ('401', '403'):
_invalidate_service_on_error(e)
if update_pairs:
_execute_in_chunks(service, update_pairs)
return len(events_to_create_django_id), len(events_to_delete_google_id)