more gcal fixes
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user