From 46e62a94edc0dcf76765f11470691ba7b5fd026a Mon Sep 17 00:00:00 2001 From: Marc Koch Date: Tue, 19 Aug 2025 17:04:24 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20introduce=20max=20search=20horizon?= =?UTF-8?q?=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `MAX_SEARCH_HORIZON` environment variable to control the maximum range of days for search operations. Splits longer search periods into multiple requests, preventing potential performance issues or timeouts with large datasets. --- docker-compose.yaml | 1 + src/clear_bookings.py | 57 ++++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 3958406..4665646 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,6 +16,7 @@ services: - SMTP_SERVER=your.smtp.server # adjust this - SMTP_PORT=587 # adjust this if necessary - SMTP_SENDER_NAME=Room Booking System # adjust this if you want + - MAX_SEARCH_HORIZON=14 # adjust maximal range of days (longer search periods will get split into multiple requests) volumes: app-data: \ No newline at end of file diff --git a/src/clear_bookings.py b/src/clear_bookings.py index 4773e0d..0f51352 100644 --- a/src/clear_bookings.py +++ b/src/clear_bookings.py @@ -12,10 +12,12 @@ from pprint import pprint import caldav import pytz -from caldav import CalendarObjectResource, Principal, Calendar +from caldav import CalendarObjectResource, Principal, Calendar, DAVObject from jinja2 import Template +from django.core.exceptions import ObjectDoesNotExist tz = pytz.timezone(os.getenv("TIME_ZONE", "Europe/Berlin")) +MAX_SEARCH_HORIZON = int(os.getenv("MAX_SEARCH_HORIZON", 14)) class DavEvent: """ @@ -29,7 +31,7 @@ class DavEvent: created: datetime status: str organizer: str - calendar: Calendar + calendar: DAVObject obj: CalendarObjectResource missing_required_fields: list @@ -119,11 +121,10 @@ class DavEvent: if match: token = match.group() print(f"Priority token found in event description: {token}") - token_obj = PriorityEventToken.objects.get(token=token) - - # Check if the token object exists in the database - if not token_obj: - print(f"Priority token '{token}' not found in database.") + try: + token_obj = PriorityEventToken.objects.get(token=token) + except ObjectDoesNotExist: + print(f"Priority token could not be found in database: {token}") return False # If token is already used, verify signature @@ -138,7 +139,7 @@ class DavEvent: # TODO: Notify about invalid token usage return False - # If the token hasn't been used yet, redeem it + # If the token hasn't been used yet, redeem it else: print(f"Redeeming priority token '{token}' for event '{uid}'.") token_obj.redeem(uid) @@ -263,17 +264,34 @@ def clear(target_calendars: list, is_test: bool=False) -> dict: print(f"--- Clearing cancelled bookings and overlaps in calendar: {calendar.id}") horizon = tcal_by_name[calendar.id].auto_clear_overlap_horizon_days - try: - events_fetched = calendar.search( - start=datetime.now(), - end=date.today() + timedelta(days=horizon), - event=True, - expand=True, - split_expanded=True, - ) - except Exception as e: - print(f"--- Failed to fetch events for calendar: {calendar.id}: {e}") - continue + # Split horizon search into multiple requests if horizon is bigger + # than MAX_SEARCH_HORIZON + horizons = [] + while horizon > 0: + if horizon >= MAX_SEARCH_HORIZON: + horizons.append(MAX_SEARCH_HORIZON) + else: + horizons.append(horizon) + horizon -= MAX_SEARCH_HORIZON + + events_fetched = [] + start_delta = 0 + end_delta = 0 + today = datetime.now(tz=tz).date() + for h in horizons: + end_delta += h + try: + events_fetched.extend(calendar.search( + start=today + timedelta(days=start_delta), + end=today + timedelta(days=end_delta), + event=True, + expand=True, + split_expanded=True, + )) + except Exception as e: + print(f"--- Failed to fetch events for calendar: {calendar.id}: {e}") + continue + start_delta += h # Create DavEvent objects from fetched events events = [] @@ -348,7 +366,6 @@ def clear(target_calendars: list, is_test: bool=False) -> dict: elif not event.is_cancelled: if not is_test: event.cancel() - is_cancelled = True result["cancellation_type"] = "cancelled" results["cancelled_overlapping_recurring_events"].append(result) print("Cancelled overlapping recurring event:")