another gcal try without chunking
This commit is contained in:
@@ -79,13 +79,6 @@ def create_gcal_service_object():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _invalidate_service_on_error(exc):
|
|
||||||
"""Reset the cached service object so the next call retries credential loading."""
|
|
||||||
global _service_object
|
|
||||||
logger.warning(f"Invalidating cached GCal service due to error: {exc}")
|
|
||||||
_service_object = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_service_object():
|
def get_service_object():
|
||||||
"""Get or create the Google Calendar service object."""
|
"""Get or create the Google Calendar service object."""
|
||||||
global _service_object
|
global _service_object
|
||||||
@@ -204,24 +197,29 @@ def build_gcal_event(event, timezone="Europe/Berlin"):
|
|||||||
# ------------------------------ Callback Functions ------------------------------------------------
|
# ------------------------------ Callback Functions ------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def on_gcal_event_created(request_id, response, exception=None):
|
def _save_gcal_mapping_from_response(response):
|
||||||
"""Callback function for created events to enter new gcal id in the mapping table."""
|
"""Save a GCal mapping from an insert response."""
|
||||||
if exception is not None:
|
|
||||||
logger.error(f"Error creating GCal event: {exception}")
|
|
||||||
return # Don't raise — let the batch continue processing other events
|
|
||||||
|
|
||||||
google_id = response["id"]
|
google_id = response["id"]
|
||||||
django_id = response["extendedProperties"]["private"]["blechreizID"]
|
django_id = response["extendedProperties"]["private"]["blechreizID"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event = Event.objects.get(pk=django_id)
|
event = Event.objects.get(pk=django_id)
|
||||||
mapping = GCalMapping(gcal_id=google_id, event=event)
|
GCalMapping.objects.update_or_create(
|
||||||
mapping.save()
|
event=event, defaults={"gcal_id": google_id}
|
||||||
|
)
|
||||||
logger.info(f"Created mapping: GCal {google_id} <-> Event {django_id}")
|
logger.info(f"Created mapping: GCal {google_id} <-> Event {django_id}")
|
||||||
except Event.DoesNotExist:
|
except Event.DoesNotExist:
|
||||||
logger.error(f"Event {django_id} not found when creating GCal mapping")
|
logger.error(f"Event {django_id} not found when creating GCal mapping")
|
||||||
|
|
||||||
|
|
||||||
|
def on_gcal_event_created(request_id, response, exception=None):
|
||||||
|
"""Callback function for batch delete_all — kept for backwards compat."""
|
||||||
|
if exception is not None:
|
||||||
|
logger.error(f"Error creating GCal event: {exception}")
|
||||||
|
return
|
||||||
|
_save_gcal_mapping_from_response(response)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------ GCal Api Calls -------------------------------------------------
|
# ------------------------------ GCal Api Calls -------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -328,51 +326,12 @@ def delete_all_gcal_events(service=None):
|
|||||||
batch.execute()
|
batch.execute()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error deleting GCal events: {e}")
|
logger.error(f"Error deleting GCal events: {e}")
|
||||||
status = getattr(e, 'status_code', None) or getattr(e, 'resp', {}).get('status')
|
|
||||||
if str(status) in ('401', '403'):
|
|
||||||
_invalidate_service_on_error(e)
|
|
||||||
|
|
||||||
GCalMapping.objects.all().delete()
|
GCalMapping.objects.all().delete()
|
||||||
|
|
||||||
return count
|
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):
|
def sync_from_local_to_google(service=None):
|
||||||
"""
|
"""
|
||||||
Creates a google event for each local event (if it does not exist yet) and
|
Creates a google event for each local event (if it does not exist yet) and
|
||||||
@@ -425,13 +384,13 @@ def sync_from_local_to_google(service=None):
|
|||||||
if django_id not in local_events_django_id
|
if django_id not in local_events_django_id
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Deletes (usually few, single batch is fine) ---
|
# --- Deletes: one by one ---
|
||||||
if events_to_delete_google_id:
|
for gcal_id in events_to_delete_google_id:
|
||||||
delete_pairs = [
|
try:
|
||||||
(service.events().delete(calendarId="primary", eventId=gcal_id), None)
|
service.events().delete(calendarId="primary", eventId=gcal_id).execute()
|
||||||
for gcal_id in events_to_delete_google_id
|
GCalMapping.objects.filter(gcal_id=gcal_id).delete()
|
||||||
]
|
except Exception as e:
|
||||||
_execute_in_chunks(service, delete_pairs)
|
logger.error(f"Failed to delete GCal event {gcal_id}: {e}")
|
||||||
|
|
||||||
# --- Creates: future events first (soonest upcoming), then past events ---
|
# --- Creates: future events first (soonest upcoming), then past events ---
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
@@ -445,23 +404,20 @@ def sync_from_local_to_google(service=None):
|
|||||||
.order_by("-date")
|
.order_by("-date")
|
||||||
.values_list("pk", flat=True)
|
.values_list("pk", flat=True)
|
||||||
)
|
)
|
||||||
ordered_create_ids = future_ids + past_ids
|
ordered_create_ids = future_ids # + past_ids
|
||||||
|
|
||||||
create_pairs = []
|
|
||||||
for event_django_id in ordered_create_ids:
|
for event_django_id in ordered_create_ids:
|
||||||
try:
|
try:
|
||||||
event = Event.objects.get(pk=event_django_id)
|
event = Event.objects.get(pk=event_django_id)
|
||||||
create_pairs.append(
|
request = create_gcal_event_request(service, event)
|
||||||
(create_gcal_event_request(service, event), on_gcal_event_created)
|
response = request.execute()
|
||||||
)
|
_save_gcal_mapping_from_response(response)
|
||||||
except Event.DoesNotExist:
|
except Event.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
if create_pairs:
|
logger.error(f"Failed to create GCal event for Event {event_django_id}: {e}")
|
||||||
_execute_in_chunks(service, create_pairs)
|
|
||||||
|
|
||||||
# --- Updates: attendee status changes ---
|
# --- Updates: attendee status changes ---
|
||||||
update_pairs = []
|
|
||||||
for gcal_ev in all_events:
|
for gcal_ev in all_events:
|
||||||
try:
|
try:
|
||||||
event_django_id = int(
|
event_django_id = int(
|
||||||
@@ -473,14 +429,13 @@ def sync_from_local_to_google(service=None):
|
|||||||
local_attendees = build_gcal_attendees_obj(django_ev)
|
local_attendees = build_gcal_attendees_obj(django_ev)
|
||||||
|
|
||||||
if gcal_attendees != local_attendees:
|
if gcal_attendees != local_attendees:
|
||||||
update_pairs.append((update_gcal_event_request(service, django_ev), None))
|
update_gcal_event_request(service, django_ev).execute()
|
||||||
except Event.DoesNotExist:
|
except Event.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
if update_pairs:
|
logger.error(f"Failed to update GCal event: {e}")
|
||||||
_execute_in_chunks(service, update_pairs)
|
|
||||||
|
|
||||||
return len(events_to_create_django_id), len(events_to_delete_google_id)
|
return len(events_to_create_django_id), len(events_to_delete_google_id)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user