Reduce overhead for google calendar state updates (#108133)

pull/108715/head
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 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

View File

@ -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"]
}

View File

@ -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

View File

@ -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