Updated ics_calendar to restore compatibility with HA
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""Support for ICS Calendar."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
# import homeassistant.helpers.config_validation as cv
|
||||
# import voluptuous as vol
|
||||
@@ -12,6 +13,7 @@ from homeassistant.components.calendar import (
|
||||
extract_offset,
|
||||
is_offset_reached,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_EXCLUDE,
|
||||
CONF_INCLUDE,
|
||||
@@ -24,23 +26,28 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.util.dt import now as hanow
|
||||
|
||||
from . import (
|
||||
from .calendardata import CalendarData
|
||||
from .const import (
|
||||
CONF_ACCEPT_HEADER,
|
||||
CONF_CALENDARS,
|
||||
CONF_CONNECTION_TIMEOUT,
|
||||
CONF_DAYS,
|
||||
CONF_DOWNLOAD_INTERVAL,
|
||||
CONF_INCLUDE_ALL_DAY,
|
||||
CONF_OFFSET_HOURS,
|
||||
CONF_PARSER,
|
||||
CONF_SET_TIMEOUT,
|
||||
CONF_SUMMARY_DEFAULT,
|
||||
CONF_USER_AGENT,
|
||||
DOMAIN,
|
||||
)
|
||||
from .calendardata import CalendarData
|
||||
from .filter import Filter
|
||||
from .icalendarparser import ICalendarParser
|
||||
from .getparser import GetParser
|
||||
from .parserevent import ParserEvent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,6 +58,34 @@ OFFSET = "!!"
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the calendar in background."""
|
||||
hass.async_create_task(
|
||||
_async_setup_entry_bg_task(hass, config_entry, async_add_entities)
|
||||
)
|
||||
|
||||
|
||||
async def _async_setup_entry_bg_task(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the calendar."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
device_id = f"{data[CONF_NAME]}"
|
||||
entity = ICSCalendarEntity(
|
||||
hass,
|
||||
generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass),
|
||||
hass.data[DOMAIN][config_entry.entry_id],
|
||||
config_entry.entry_id,
|
||||
)
|
||||
async_add_entities([entity])
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
@@ -70,8 +105,13 @@ def setup_platform(
|
||||
"""
|
||||
_LOGGER.debug("Setting up ics calendars")
|
||||
if discovery_info is not None:
|
||||
calendars: list = discovery_info.get(CONF_CALENDARS)
|
||||
_LOGGER.debug(
|
||||
"setup_platform: ignoring discovery_info, already imported!"
|
||||
)
|
||||
# calendars: list = discovery_info.get(CONF_CALENDARS)
|
||||
calendars = []
|
||||
else:
|
||||
_LOGGER.debug("setup_platform: discovery_info is None")
|
||||
calendars: list = config.get(CONF_CALENDARS)
|
||||
|
||||
calendar_devices = []
|
||||
@@ -91,10 +131,13 @@ def setup_platform(
|
||||
CONF_INCLUDE: calendar.get(CONF_INCLUDE),
|
||||
CONF_OFFSET_HOURS: calendar.get(CONF_OFFSET_HOURS),
|
||||
CONF_ACCEPT_HEADER: calendar.get(CONF_ACCEPT_HEADER),
|
||||
CONF_CONNECTION_TIMEOUT: calendar.get(CONF_CONNECTION_TIMEOUT),
|
||||
}
|
||||
device_id = f"{device_data[CONF_NAME]}"
|
||||
entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
|
||||
calendar_devices.append(ICSCalendarEntity(entity_id, device_data))
|
||||
calendar_devices.append(
|
||||
ICSCalendarEntity(hass, entity_id, device_data)
|
||||
)
|
||||
|
||||
add_entities(calendar_devices)
|
||||
|
||||
@@ -102,7 +145,13 @@ def setup_platform(
|
||||
class ICSCalendarEntity(CalendarEntity):
|
||||
"""A CalendarEntity for an ICS Calendar."""
|
||||
|
||||
def __init__(self, entity_id: str, device_data):
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entity_id: str,
|
||||
device_data,
|
||||
unique_id: str = None,
|
||||
):
|
||||
"""Construct ICSCalendarEntity.
|
||||
|
||||
:param entity_id: Entity id for the calendar
|
||||
@@ -111,14 +160,16 @@ class ICSCalendarEntity(CalendarEntity):
|
||||
:type device_data: dict
|
||||
"""
|
||||
_LOGGER.debug(
|
||||
"Initializing calendar: %s with URL: %s",
|
||||
"Initializing calendar: %s with URL: %s, uniqueid: %s",
|
||||
device_data[CONF_NAME],
|
||||
device_data[CONF_URL],
|
||||
unique_id,
|
||||
)
|
||||
self.data = ICSCalendarData(device_data)
|
||||
self.data = ICSCalendarData(hass, device_data)
|
||||
self.entity_id = entity_id
|
||||
self._attr_unique_id = f"ICSCalendar.{unique_id}"
|
||||
self._event = None
|
||||
self._name = device_data[CONF_NAME]
|
||||
self._attr_name = device_data[CONF_NAME]
|
||||
self._last_call = None
|
||||
|
||||
@property
|
||||
@@ -131,12 +182,7 @@ class ICSCalendarEntity(CalendarEntity):
|
||||
return self._event
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the calendar."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
def should_poll(self) -> bool:
|
||||
"""Indicate if the calendar should be polled.
|
||||
|
||||
If the last call to update or get_api_events was not within the minimum
|
||||
@@ -168,25 +214,50 @@ class ICSCalendarEntity(CalendarEntity):
|
||||
_LOGGER.debug(
|
||||
"%s: async_get_events called; calling internal.", self.name
|
||||
)
|
||||
return await self.data.async_get_events(hass, start_date, end_date)
|
||||
return await self.data.async_get_events(start_date, end_date)
|
||||
|
||||
def update(self):
|
||||
async def async_update(self):
|
||||
"""Get the current or next event."""
|
||||
self.data.update()
|
||||
self._event = self.data.event
|
||||
await self.data.async_update()
|
||||
self._event: CalendarEvent | None = self.data.event
|
||||
self._attr_extra_state_attributes = {
|
||||
"offset_reached": is_offset_reached(
|
||||
self._event.start_datetime_local, self.data.offset
|
||||
"offset_reached": (
|
||||
is_offset_reached(
|
||||
self._event.start_datetime_local, self.data.offset
|
||||
)
|
||||
if self._event
|
||||
else False
|
||||
)
|
||||
if self._event
|
||||
else False
|
||||
}
|
||||
|
||||
async def async_create_event(self, **kwargs: Any):
|
||||
"""Raise error, this is a read-only calendar."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_delete_event(
|
||||
self,
|
||||
uid: str,
|
||||
recurrence_id: str | None = None,
|
||||
recurrence_range: str | None = None,
|
||||
) -> None:
|
||||
"""Raise error, this is a read-only calendar."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_update_event(
|
||||
self,
|
||||
uid: str,
|
||||
event: dict[str, Any],
|
||||
recurrence_id: str | None = None,
|
||||
recurrence_range: str | None = None,
|
||||
) -> None:
|
||||
"""Raise error, this is a read-only calendar."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ICSCalendarData: # pylint: disable=R0902
|
||||
"""Class to use the calendar ICS client object to get next event."""
|
||||
|
||||
def __init__(self, device_data):
|
||||
def __init__(self, hass: HomeAssistant, device_data):
|
||||
"""Set up how we are going to connect to the URL.
|
||||
|
||||
:param device_data Information about the calendar
|
||||
@@ -196,18 +267,25 @@ class ICSCalendarData: # pylint: disable=R0902
|
||||
self._offset_hours = device_data[CONF_OFFSET_HOURS]
|
||||
self.include_all_day = device_data[CONF_INCLUDE_ALL_DAY]
|
||||
self._summary_prefix: str = device_data[CONF_PREFIX]
|
||||
self.parser = ICalendarParser.get_instance(device_data[CONF_PARSER])
|
||||
self._summary_default: str = device_data[CONF_SUMMARY_DEFAULT]
|
||||
self.parser = GetParser.get_parser(device_data[CONF_PARSER])
|
||||
self.parser.set_filter(
|
||||
Filter(device_data[CONF_EXCLUDE], device_data[CONF_INCLUDE])
|
||||
)
|
||||
self.offset = None
|
||||
self.event = None
|
||||
self._hass = hass
|
||||
|
||||
self._calendar_data = CalendarData(
|
||||
get_async_client(hass),
|
||||
_LOGGER,
|
||||
self.name,
|
||||
device_data[CONF_URL],
|
||||
timedelta(minutes=device_data[CONF_DOWNLOAD_INTERVAL]),
|
||||
{
|
||||
"name": self.name,
|
||||
"url": device_data[CONF_URL],
|
||||
"min_update_time": timedelta(
|
||||
minutes=device_data[CONF_DOWNLOAD_INTERVAL]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
self._calendar_data.set_headers(
|
||||
@@ -217,22 +295,23 @@ class ICSCalendarData: # pylint: disable=R0902
|
||||
device_data[CONF_ACCEPT_HEADER],
|
||||
)
|
||||
|
||||
if device_data.get(CONF_SET_TIMEOUT):
|
||||
self._calendar_data.set_timeout(
|
||||
device_data[CONF_CONNECTION_TIMEOUT]
|
||||
)
|
||||
|
||||
async def async_get_events(
|
||||
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||
self, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all events in a specific time frame.
|
||||
|
||||
:param hass: Home Assistant object
|
||||
:type hass: HomeAssistant
|
||||
:param start_date: The first starting date to consider
|
||||
:type start_date: datetime
|
||||
:param end_date: The last starting date to consider
|
||||
:type end_date: datetime
|
||||
"""
|
||||
event_list = []
|
||||
if await hass.async_add_executor_job(
|
||||
self._calendar_data.download_calendar
|
||||
):
|
||||
event_list: list[ParserEvent] = []
|
||||
if await self._calendar_data.download_calendar():
|
||||
_LOGGER.debug("%s: Setting calendar content", self.name)
|
||||
self.parser.set_content(self._calendar_data.get())
|
||||
try:
|
||||
@@ -248,22 +327,27 @@ class ICSCalendarData: # pylint: disable=R0902
|
||||
self.name,
|
||||
exc_info=True,
|
||||
)
|
||||
event_list = []
|
||||
event_list: list[ParserEvent] = []
|
||||
|
||||
for event in event_list:
|
||||
event.summary = self._summary_prefix + event.summary
|
||||
if not event.summary:
|
||||
event.summary = self._summary_default
|
||||
# Since we skipped the validation code earlier, invoke it now,
|
||||
# before passing the object outside this component
|
||||
event.validate()
|
||||
|
||||
return event_list
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
async def async_update(self):
|
||||
"""Get the current or next event."""
|
||||
_LOGGER.debug("%s: Update was called", self.name)
|
||||
if self._calendar_data.download_calendar():
|
||||
parser_event: ParserEvent | None = None
|
||||
if await self._calendar_data.download_calendar():
|
||||
_LOGGER.debug("%s: Setting calendar content", self.name)
|
||||
self.parser.set_content(self._calendar_data.get())
|
||||
try:
|
||||
self.event = self.parser.get_current_event(
|
||||
parser_event: ParserEvent | None = self.parser.get_current_event(
|
||||
include_all_day=self.include_all_day,
|
||||
now=hanow(),
|
||||
days=self._days,
|
||||
@@ -273,18 +357,24 @@ class ICSCalendarData: # pylint: disable=R0902
|
||||
_LOGGER.error(
|
||||
"update: %s: Failed to parse ICS!", self.name, exc_info=True
|
||||
)
|
||||
if self.event is not None:
|
||||
if parser_event is not None:
|
||||
_LOGGER.debug(
|
||||
"%s: got event: %s; start: %s; end: %s; all_day: %s",
|
||||
self.name,
|
||||
self.event.summary,
|
||||
self.event.start,
|
||||
self.event.end,
|
||||
self.event.all_day,
|
||||
parser_event.summary,
|
||||
parser_event.start,
|
||||
parser_event.end,
|
||||
parser_event.all_day,
|
||||
)
|
||||
(summary, offset) = extract_offset(self.event.summary, OFFSET)
|
||||
self.event.summary = self._summary_prefix + summary
|
||||
(summary, offset) = extract_offset(parser_event.summary, OFFSET)
|
||||
parser_event.summary = self._summary_prefix + summary
|
||||
if not parser_event.summary:
|
||||
parser_event.summary = self._summary_default
|
||||
self.offset = offset
|
||||
# Invoke validation here, since it was skipped when creating the
|
||||
# ParserEvent
|
||||
parser_event.validate()
|
||||
self.event: CalendarEvent = parser_event
|
||||
return True
|
||||
|
||||
_LOGGER.debug("%s: No event found!", self.name)
|
||||
|
Reference in New Issue
Block a user