mirror of https://github.com/home-assistant/core
222 lines
7.4 KiB
Python
222 lines
7.4 KiB
Python
"""Support for WebDav Calendar."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
import caldav
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.calendar import (
|
|
ENTITY_ID_FORMAT,
|
|
PLATFORM_SCHEMA as CALENDAR_PLATFORM_SCHEMA,
|
|
CalendarEntity,
|
|
CalendarEvent,
|
|
is_offset_reached,
|
|
)
|
|
from homeassistant.const import (
|
|
CONF_NAME,
|
|
CONF_PASSWORD,
|
|
CONF_URL,
|
|
CONF_USERNAME,
|
|
CONF_VERIFY_SSL,
|
|
)
|
|
from homeassistant.core import HomeAssistant, callback
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.helpers.entity import async_generate_entity_id
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
|
|
from . import CalDavConfigEntry
|
|
from .api import async_get_calendars
|
|
from .coordinator import CalDavUpdateCoordinator
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
CONF_CALENDARS = "calendars"
|
|
CONF_CUSTOM_CALENDARS = "custom_calendars"
|
|
CONF_CALENDAR = "calendar"
|
|
CONF_SEARCH = "search"
|
|
CONF_DAYS = "days"
|
|
|
|
# Number of days to look ahead for next event when configured by ConfigEntry
|
|
CONFIG_ENTRY_DEFAULT_DAYS = 7
|
|
|
|
# Only allow VCALENDARs that support this component type
|
|
SUPPORTED_COMPONENT = "VEVENT"
|
|
|
|
PLATFORM_SCHEMA = CALENDAR_PLATFORM_SCHEMA.extend(
|
|
{
|
|
vol.Required(CONF_URL): vol.Url(),
|
|
vol.Optional(CONF_CALENDARS, default=[]): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Inclusive(CONF_USERNAME, "authentication"): cv.string,
|
|
vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string,
|
|
vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): vol.All(
|
|
cv.ensure_list,
|
|
[
|
|
vol.Schema(
|
|
{
|
|
vol.Required(CONF_CALENDAR): cv.string,
|
|
vol.Required(CONF_NAME): cv.string,
|
|
vol.Required(CONF_SEARCH): cv.string,
|
|
}
|
|
)
|
|
],
|
|
),
|
|
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
|
vol.Optional(CONF_DAYS, default=1): cv.positive_int,
|
|
}
|
|
)
|
|
|
|
|
|
async def async_setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
async_add_entities: AddEntitiesCallback,
|
|
disc_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Set up the WebDav Calendar platform."""
|
|
url = config[CONF_URL]
|
|
username = config.get(CONF_USERNAME)
|
|
password = config.get(CONF_PASSWORD)
|
|
days = config[CONF_DAYS]
|
|
|
|
client = caldav.DAVClient(
|
|
url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL]
|
|
)
|
|
|
|
calendars = await async_get_calendars(hass, client, SUPPORTED_COMPONENT)
|
|
|
|
entities = []
|
|
device_id: str | None
|
|
for calendar in list(calendars):
|
|
# If a calendar name was given in the configuration,
|
|
# ignore all the others
|
|
if config[CONF_CALENDARS] and calendar.name not in config[CONF_CALENDARS]:
|
|
_LOGGER.debug("Ignoring calendar '%s'", calendar.name)
|
|
continue
|
|
|
|
# Create additional calendars based on custom filtering rules
|
|
for cust_calendar in config[CONF_CUSTOM_CALENDARS]:
|
|
# Check that the base calendar matches
|
|
if cust_calendar[CONF_CALENDAR] != calendar.name:
|
|
continue
|
|
|
|
name = cust_calendar[CONF_NAME]
|
|
device_id = f"{cust_calendar[CONF_CALENDAR]} {cust_calendar[CONF_NAME]}"
|
|
entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
|
|
coordinator = CalDavUpdateCoordinator(
|
|
hass,
|
|
None,
|
|
calendar=calendar,
|
|
days=days,
|
|
include_all_day=True,
|
|
search=cust_calendar[CONF_SEARCH],
|
|
)
|
|
entities.append(
|
|
WebDavCalendarEntity(name, entity_id, coordinator, supports_offset=True)
|
|
)
|
|
|
|
# Create a default calendar if there was no custom one for all calendars
|
|
# that support events.
|
|
if not config[CONF_CUSTOM_CALENDARS]:
|
|
name = calendar.name
|
|
device_id = calendar.name
|
|
entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
|
|
coordinator = CalDavUpdateCoordinator(
|
|
hass,
|
|
None,
|
|
calendar=calendar,
|
|
days=days,
|
|
include_all_day=False,
|
|
search=None,
|
|
)
|
|
entities.append(
|
|
WebDavCalendarEntity(name, entity_id, coordinator, supports_offset=True)
|
|
)
|
|
|
|
async_add_entities(entities, True)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: CalDavConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the CalDav calendar platform for a config entry."""
|
|
calendars = await async_get_calendars(hass, entry.runtime_data, SUPPORTED_COMPONENT)
|
|
async_add_entities(
|
|
(
|
|
WebDavCalendarEntity(
|
|
calendar.name,
|
|
async_generate_entity_id(ENTITY_ID_FORMAT, calendar.name, hass=hass),
|
|
CalDavUpdateCoordinator(
|
|
hass,
|
|
entry,
|
|
calendar=calendar,
|
|
days=CONFIG_ENTRY_DEFAULT_DAYS,
|
|
include_all_day=True,
|
|
search=None,
|
|
),
|
|
unique_id=f"{entry.entry_id}-{calendar.id}",
|
|
)
|
|
for calendar in calendars
|
|
if calendar.name
|
|
),
|
|
True,
|
|
)
|
|
|
|
|
|
class WebDavCalendarEntity(CoordinatorEntity[CalDavUpdateCoordinator], CalendarEntity):
|
|
"""A device for getting the next Task from a WebDav Calendar."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
entity_id: str,
|
|
coordinator: CalDavUpdateCoordinator,
|
|
unique_id: str | None = None,
|
|
supports_offset: bool = False,
|
|
) -> None:
|
|
"""Create the WebDav Calendar Event Device."""
|
|
super().__init__(coordinator)
|
|
self.entity_id = entity_id
|
|
self._event: CalendarEvent | None = None
|
|
self._attr_name = name
|
|
if unique_id is not None:
|
|
self._attr_unique_id = unique_id
|
|
self._supports_offset = supports_offset
|
|
|
|
@property
|
|
def event(self) -> CalendarEvent | None:
|
|
"""Return the next upcoming event."""
|
|
return self._event
|
|
|
|
async def async_get_events(
|
|
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
|
) -> list[CalendarEvent]:
|
|
"""Get all events in a specific time frame."""
|
|
return await self.coordinator.async_get_events(hass, start_date, end_date)
|
|
|
|
@callback
|
|
def _handle_coordinator_update(self) -> None:
|
|
"""Update event data."""
|
|
self._event = self.coordinator.data
|
|
if self._supports_offset:
|
|
self._attr_extra_state_attributes = {
|
|
"offset_reached": is_offset_reached(
|
|
self._event.start_datetime_local,
|
|
self.coordinator.offset, # type: ignore[arg-type]
|
|
)
|
|
if self._event
|
|
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()
|