diff --git a/homeassistant/components/google/calendar.py b/homeassistant/components/google/calendar.py index 3e34a7234a4..88f59ff44f7 100644 --- a/homeassistant/components/google/calendar.py +++ b/homeassistant/components/google/calendar.py @@ -4,6 +4,7 @@ from __future__ import annotations from collections.abc import Iterable from datetime import datetime, timedelta +import itertools import logging from typing import Any, cast @@ -18,6 +19,7 @@ from gcal_sync.model import AccessRole, DateOrDatetime, Event from gcal_sync.store import ScopedCalendarStore from gcal_sync.sync import CalendarEventSyncManager from gcal_sync.timeline import Timeline +from ical.iter import SortableItemValue from homeassistant.components.calendar import ( CREATE_EVENT_SCHEMA, @@ -76,6 +78,9 @@ from .const import ( _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) +# Maximum number of upcoming events to consider for state changes between +# coordinator updates. +MAX_UPCOMING_EVENTS = 20 # Avoid syncing super old data on initial syncs. Note that old but active # recurring events are still included. @@ -244,6 +249,22 @@ async def async_setup_entry( ) +def _truncate_timeline(timeline: Timeline, max_events: int) -> Timeline: + """Truncate the timeline to a maximum number of events. + + This is used to avoid repeated expansion of recurring events during + state machine updates. + """ + upcoming = timeline.active_after(dt_util.now()) + truncated = list(itertools.islice(upcoming, max_events)) + return Timeline( + [ + SortableItemValue(event.timespan_of(dt_util.DEFAULT_TIME_ZONE), event) + for event in truncated + ] + ) + + class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]): """Coordinator for calendar RPC calls that use an efficient sync.""" @@ -263,6 +284,7 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]): update_interval=MIN_TIME_BETWEEN_UPDATES, ) self.sync = sync + self._upcoming_timeline: Timeline | None = None async def _async_update_data(self) -> Timeline: """Fetch data from API endpoint.""" @@ -271,9 +293,11 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]): except ApiException as err: raise UpdateFailed(f"Error communicating with API: {err}") from err - return await self.sync.store_service.async_get_timeline( + timeline = await self.sync.store_service.async_get_timeline( dt_util.DEFAULT_TIME_ZONE ) + self._upcoming_timeline = _truncate_timeline(timeline, MAX_UPCOMING_EVENTS) + return timeline async def async_get_events( self, start_date: datetime, end_date: datetime @@ -291,8 +315,8 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]): @property def upcoming(self) -> Iterable[Event] | None: """Return upcoming events if any.""" - if self.data: - return self.data.active_after(dt_util.now()) + if self._upcoming_timeline: + return self._upcoming_timeline.active_after(dt_util.now()) return None diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 27e462a380e..d0705f9382a 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/calendar.google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3"] + "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==6.1.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9a02be3602c..ca1075a9bf5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1088,6 +1088,7 @@ ibeacon-ble==1.0.1 # homeassistant.components.watson_iot ibmiotf==0.3.4 +# homeassistant.components.google # homeassistant.components.local_calendar # homeassistant.components.local_todo ical==6.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9037316fbae..a0eabe72941 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -872,6 +872,7 @@ iaqualink==0.5.0 # homeassistant.components.ibeacon ibeacon-ble==1.0.1 +# homeassistant.components.google # homeassistant.components.local_calendar # homeassistant.components.local_todo ical==6.1.1