hass-core/homeassistant/components/google/calendar.py
Martin Hjelmare 177f5a35ae Rewrite calendar component (#24950)
* Correct google calendar test name

* Rewrite calendar component

* Save component in hass.data.
* Rename device_state_attributes to state_attributes.
* Remove offset attribute from base state_attributes.
* Extract offset helpers to calendar component.
* Clean imports.
* Remove stale constants.
* Remove name and add async_get_events.
* Add normalize_event helper function. Copied from #21495.
* Add event property to base entity.
* Use event property for calendar state.
* Ensure event start and end.
* Remove entity init.
* Add comment about event data class.
* Temporary keep old start and end datetime format.

* Convert demo calendar

* Convert google calendar

* Convert google calendar.
* Clean up google component.
* Keep offset feature by using offset helpers.

* Convert caldav calendar

* Clean up caldav calendar.
* Update caldav cal on addition.
* Bring back offset to caldav calendar.
* Copy caldav event on update.

* Convert todoist calendar
2019-07-10 20:59:37 -07:00

176 lines
5.7 KiB
Python

"""Support for Google Calendar Search binary sensors."""
import copy
from datetime import timedelta
import logging
from homeassistant.components.calendar import (
ENTITY_ID_FORMAT, CalendarEventDevice, calculate_offset, is_offset_reached)
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.util import Throttle, dt
from . import (
CONF_CAL_ID, CONF_DEVICE_ID, CONF_ENTITIES, CONF_IGNORE_AVAILABILITY,
CONF_MAX_RESULTS, CONF_NAME, CONF_OFFSET, CONF_SEARCH, CONF_TRACK,
DEFAULT_CONF_OFFSET, TOKEN_FILE, GoogleCalendarService)
_LOGGER = logging.getLogger(__name__)
DEFAULT_GOOGLE_SEARCH_PARAMS = {
'orderBy': 'startTime',
'maxResults': 5,
'singleEvents': True,
}
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
def setup_platform(hass, config, add_entities, disc_info=None):
"""Set up the calendar platform for event devices."""
if disc_info is None:
return
if not any(data[CONF_TRACK] for data in disc_info[CONF_ENTITIES]):
return
calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE))
entities = []
for data in disc_info[CONF_ENTITIES]:
if not data[CONF_TRACK]:
continue
entity_id = generate_entity_id(
ENTITY_ID_FORMAT, data[CONF_DEVICE_ID], hass=hass)
entity = GoogleCalendarEventDevice(
calendar_service, disc_info[CONF_CAL_ID], data, entity_id)
entities.append(entity)
add_entities(entities, True)
class GoogleCalendarEventDevice(CalendarEventDevice):
"""A calendar event device."""
def __init__(self, calendar_service, calendar, data, entity_id):
"""Create the Calendar event device."""
self.data = GoogleCalendarData(
calendar_service, calendar,
data.get(CONF_SEARCH), data.get(CONF_IGNORE_AVAILABILITY),
data.get(CONF_MAX_RESULTS))
self._event = None
self._name = data[CONF_NAME]
self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET)
self._offset_reached = False
self.entity_id = entity_id
@property
def device_state_attributes(self):
"""Return the device state attributes."""
return {
'offset_reached': self._offset_reached,
}
@property
def event(self):
"""Return the next upcoming event."""
return self._event
@property
def name(self):
"""Return the name of the entity."""
return self._name
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
def update(self):
"""Update event data."""
self.data.update()
event = copy.deepcopy(self.data.event)
if event is None:
self._event = event
return
event = calculate_offset(event, self._offset)
self._offset_reached = is_offset_reached(event)
self._event = event
class GoogleCalendarData:
"""Class to utilize calendar service object to get next event."""
def __init__(self, calendar_service, calendar_id, search,
ignore_availability, max_results):
"""Set up how we are going to search the google calendar."""
self.calendar_service = calendar_service
self.calendar_id = calendar_id
self.search = search
self.ignore_availability = ignore_availability
self.max_results = max_results
self.event = None
def _prepare_query(self):
# pylint: disable=import-error
from httplib2 import ServerNotFoundError
try:
service = self.calendar_service.get()
except ServerNotFoundError:
_LOGGER.error("Unable to connect to Google")
return None, None
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['calendarId'] = self.calendar_id
if self.max_results:
params['maxResults'] = self.max_results
if self.search:
params['q'] = self.search
return service, params
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
service, params = await hass.async_add_executor_job(
self._prepare_query)
if service is None:
return []
params['timeMin'] = start_date.isoformat('T')
params['timeMax'] = end_date.isoformat('T')
events = await hass.async_add_executor_job(service.events)
result = await hass.async_add_executor_job(
events.list(**params).execute)
items = result.get('items', [])
event_list = []
for item in items:
if (not self.ignore_availability
and 'transparency' in item.keys()):
if item['transparency'] == 'opaque':
event_list.append(item)
else:
event_list.append(item)
return event_list
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
service, params = self._prepare_query()
if service is None:
return
params['timeMin'] = dt.now().isoformat('T')
events = service.events()
result = events.list(**params).execute()
items = result.get('items', [])
new_event = None
for item in items:
if (not self.ignore_availability
and 'transparency' in item.keys()):
if item['transparency'] == 'opaque':
new_event = item
break
else:
new_event = item
break
self.event = new_event