Update caldav to use an DataUpdateCoordinator for fetching data (#102089)

This commit is contained in:
Allen Porter 2023-10-28 15:35:31 -07:00 committed by GitHub
parent a4c31f63bf
commit 4599b788b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -24,12 +24,16 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle, dt as dt_util from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -103,16 +107,14 @@ def setup_platform(
name = cust_calendar[CONF_NAME] name = cust_calendar[CONF_NAME]
device_id = f"{cust_calendar[CONF_CALENDAR]} {cust_calendar[CONF_NAME]}" device_id = f"{cust_calendar[CONF_CALENDAR]} {cust_calendar[CONF_NAME]}"
entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
calendar_devices.append( coordinator = CalDavUpdateCoordinator(
WebDavCalendarEntity( hass,
name=name,
calendar=calendar, calendar=calendar,
entity_id=entity_id,
days=days, days=days,
all_day=True, include_all_day=True,
search=cust_calendar[CONF_SEARCH], search=cust_calendar[CONF_SEARCH],
) )
) calendar_devices.append(WebDavCalendarEntity(name, entity_id, coordinator))
# Create a default calendar if there was no custom one for all calendars # Create a default calendar if there was no custom one for all calendars
# that support events. # that support events.
@ -130,24 +132,26 @@ def setup_platform(
name = calendar.name name = calendar.name
device_id = calendar.name device_id = calendar.name
entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
calendar_devices.append( coordinator = CalDavUpdateCoordinator(
WebDavCalendarEntity(name, calendar, entity_id, days) hass,
calendar=calendar,
days=days,
include_all_day=False,
search=None,
) )
calendar_devices.append(WebDavCalendarEntity(name, entity_id, coordinator))
add_entities(calendar_devices, True) add_entities(calendar_devices, True)
class WebDavCalendarEntity(CalendarEntity): class WebDavCalendarEntity(
CoordinatorEntity["CalDavUpdateCoordinator"], CalendarEntity
):
"""A device for getting the next Task from a WebDav Calendar.""" """A device for getting the next Task from a WebDav Calendar."""
def __init__(self, name, calendar, entity_id, days, all_day=False, search=None): def __init__(self, name, entity_id, coordinator):
"""Create the WebDav Calendar Event Device.""" """Create the WebDav Calendar Event Device."""
self.data = WebDavCalendarData( super().__init__(coordinator)
calendar=calendar,
days=days,
include_all_day=all_day,
search=search,
)
self.entity_id = entity_id self.entity_id = entity_id
self._event: CalendarEvent | None = None self._event: CalendarEvent | None = None
self._attr_name = name self._attr_name = name
@ -161,31 +165,42 @@ class WebDavCalendarEntity(CalendarEntity):
self, hass: HomeAssistant, start_date: datetime, end_date: datetime self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]: ) -> list[CalendarEvent]:
"""Get all events in a specific time frame.""" """Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date) return await self.coordinator.async_get_events(hass, start_date, end_date)
def update(self) -> None: @callback
def _handle_coordinator_update(self) -> None:
"""Update event data.""" """Update event data."""
self.data.update() self._event = self.coordinator.data
self._event = self.data.event
self._attr_extra_state_attributes = { self._attr_extra_state_attributes = {
"offset_reached": is_offset_reached( "offset_reached": is_offset_reached(
self._event.start_datetime_local, self.data.offset self._event.start_datetime_local, self.coordinator.offset
) )
if self._event if self._event
else False else False
} }
super()._handle_coordinator_update()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass update state from existing coordinator data."""
await super().async_added_to_hass()
self._handle_coordinator_update()
class WebDavCalendarData: class CalDavUpdateCoordinator(DataUpdateCoordinator[CalendarEvent | None]):
"""Class to utilize the calendar dav client object to get next event.""" """Class to utilize the calendar dav client object to get next event."""
def __init__(self, calendar, days, include_all_day, search): def __init__(self, hass, calendar, days, include_all_day, search):
"""Set up how we are going to search the WebDav calendar.""" """Set up how we are going to search the WebDav calendar."""
super().__init__(
hass,
_LOGGER,
name=f"CalDAV {calendar.name}",
update_interval=MIN_TIME_BETWEEN_UPDATES,
)
self.calendar = calendar self.calendar = calendar
self.days = days self.days = days
self.include_all_day = include_all_day self.include_all_day = include_all_day
self.search = search self.search = search
self.event = None
self.offset = None self.offset = None
async def async_get_events( async def async_get_events(
@ -222,19 +237,21 @@ class WebDavCalendarData:
return event_list return event_list
@Throttle(MIN_TIME_BETWEEN_UPDATES) async def _async_update_data(self) -> CalendarEvent | None:
def update(self):
"""Get the latest data.""" """Get the latest data."""
start_of_today = dt_util.start_of_local_day() start_of_today = dt_util.start_of_local_day()
start_of_tomorrow = dt_util.start_of_local_day() + timedelta(days=self.days) start_of_tomorrow = dt_util.start_of_local_day() + timedelta(days=self.days)
# We have to retrieve the results for the whole day as the server # We have to retrieve the results for the whole day as the server
# won't return events that have already started # won't return events that have already started
results = self.calendar.search( results = await self.hass.async_add_executor_job(
partial(
self.calendar.search,
start=start_of_today, start=start_of_today,
end=start_of_tomorrow, end=start_of_tomorrow,
event=True, event=True,
expand=True, expand=True,
),
) )
# Create new events for each recurrence of an event that happens today. # Create new events for each recurrence of an event that happens today.
@ -247,12 +264,15 @@ class WebDavCalendarData:
continue continue
vevent = event.instance.vevent vevent = event.instance.vevent
for start_dt in vevent.getrruleset() or []: for start_dt in vevent.getrruleset() or []:
_start_of_today = start_of_today _start_of_today: date | datetime
_start_of_tomorrow = start_of_tomorrow _start_of_tomorrow: datetime | date
if self.is_all_day(vevent): if self.is_all_day(vevent):
start_dt = start_dt.date() start_dt = start_dt.date()
_start_of_today = _start_of_today.date() _start_of_today = start_of_today.date()
_start_of_tomorrow = _start_of_tomorrow.date() _start_of_tomorrow = start_of_tomorrow.date()
else:
_start_of_today = start_of_today
_start_of_tomorrow = start_of_tomorrow
if _start_of_today <= start_dt < _start_of_tomorrow: if _start_of_today <= start_dt < _start_of_tomorrow:
new_event = event.copy() new_event = event.copy()
new_vevent = new_event.instance.vevent new_vevent = new_event.instance.vevent
@ -293,21 +313,21 @@ class WebDavCalendarData:
len(vevents), len(vevents),
self.calendar.name, self.calendar.name,
) )
self.event = None self.offset = None
return return None
# Populate the entity attributes with the event values # Populate the entity attributes with the event values
(summary, offset) = extract_offset( (summary, offset) = extract_offset(
self.get_attr_value(vevent, "summary") or "", OFFSET self.get_attr_value(vevent, "summary") or "", OFFSET
) )
self.event = CalendarEvent( self.offset = offset
return CalendarEvent(
summary=summary, summary=summary,
start=self.to_local(vevent.dtstart.value), start=self.to_local(vevent.dtstart.value),
end=self.to_local(self.get_end_date(vevent)), end=self.to_local(self.get_end_date(vevent)),
location=self.get_attr_value(vevent, "location"), location=self.get_attr_value(vevent, "location"),
description=self.get_attr_value(vevent, "description"), description=self.get_attr_value(vevent, "description"),
) )
self.offset = offset
@staticmethod @staticmethod
def is_matching(vevent, search): def is_matching(vevent, search):
@ -333,15 +353,15 @@ class WebDavCalendarData:
@staticmethod @staticmethod
def is_over(vevent): def is_over(vevent):
"""Return if the event is over.""" """Return if the event is over."""
return dt_util.now() >= WebDavCalendarData.to_datetime( return dt_util.now() >= CalDavUpdateCoordinator.to_datetime(
WebDavCalendarData.get_end_date(vevent) CalDavUpdateCoordinator.get_end_date(vevent)
) )
@staticmethod @staticmethod
def to_datetime(obj): def to_datetime(obj):
"""Return a datetime.""" """Return a datetime."""
if isinstance(obj, datetime): if isinstance(obj, datetime):
return WebDavCalendarData.to_local(obj) return CalDavUpdateCoordinator.to_local(obj)
return datetime.combine(obj, time.min).replace(tzinfo=dt_util.DEFAULT_TIME_ZONE) return datetime.combine(obj, time.min).replace(tzinfo=dt_util.DEFAULT_TIME_ZONE)
@staticmethod @staticmethod