Reduce overhead for google calendar state updates (#108133)

This commit is contained in:
Allen Porter 2024-01-23 01:50:00 -08:00 committed by GitHub
parent d9f1450ee6
commit fa63719161
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 4 deletions

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Iterable from collections.abc import Iterable
from datetime import datetime, timedelta from datetime import datetime, timedelta
import itertools
import logging import logging
from typing import Any, cast 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.store import ScopedCalendarStore
from gcal_sync.sync import CalendarEventSyncManager from gcal_sync.sync import CalendarEventSyncManager
from gcal_sync.timeline import Timeline from gcal_sync.timeline import Timeline
from ical.iter import SortableItemValue
from homeassistant.components.calendar import ( from homeassistant.components.calendar import (
CREATE_EVENT_SCHEMA, CREATE_EVENT_SCHEMA,
@ -76,6 +78,9 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) 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 # Avoid syncing super old data on initial syncs. Note that old but active
# recurring events are still included. # 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]): class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
"""Coordinator for calendar RPC calls that use an efficient sync.""" """Coordinator for calendar RPC calls that use an efficient sync."""
@ -263,6 +284,7 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
update_interval=MIN_TIME_BETWEEN_UPDATES, update_interval=MIN_TIME_BETWEEN_UPDATES,
) )
self.sync = sync self.sync = sync
self._upcoming_timeline: Timeline | None = None
async def _async_update_data(self) -> Timeline: async def _async_update_data(self) -> Timeline:
"""Fetch data from API endpoint.""" """Fetch data from API endpoint."""
@ -271,9 +293,11 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
except ApiException as err: except ApiException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from 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 dt_util.DEFAULT_TIME_ZONE
) )
self._upcoming_timeline = _truncate_timeline(timeline, MAX_UPCOMING_EVENTS)
return timeline
async def async_get_events( async def async_get_events(
self, start_date: datetime, end_date: datetime self, start_date: datetime, end_date: datetime
@ -291,8 +315,8 @@ class CalendarSyncUpdateCoordinator(DataUpdateCoordinator[Timeline]):
@property @property
def upcoming(self) -> Iterable[Event] | None: def upcoming(self) -> Iterable[Event] | None:
"""Return upcoming events if any.""" """Return upcoming events if any."""
if self.data: if self._upcoming_timeline:
return self.data.active_after(dt_util.now()) return self._upcoming_timeline.active_after(dt_util.now())
return None return None

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/calendar.google", "documentation": "https://www.home-assistant.io/integrations/calendar.google",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["googleapiclient"], "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"]
} }

View File

@ -1088,6 +1088,7 @@ ibeacon-ble==1.0.1
# homeassistant.components.watson_iot # homeassistant.components.watson_iot
ibmiotf==0.3.4 ibmiotf==0.3.4
# homeassistant.components.google
# homeassistant.components.local_calendar # homeassistant.components.local_calendar
# homeassistant.components.local_todo # homeassistant.components.local_todo
ical==6.1.1 ical==6.1.1

View File

@ -872,6 +872,7 @@ iaqualink==0.5.0
# homeassistant.components.ibeacon # homeassistant.components.ibeacon
ibeacon-ble==1.0.1 ibeacon-ble==1.0.1
# homeassistant.components.google
# homeassistant.components.local_calendar # homeassistant.components.local_calendar
# homeassistant.components.local_todo # homeassistant.components.local_todo
ical==6.1.1 ical==6.1.1