Fix local todo list persistence for due dates (#110830)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Allen Porter 2024-02-18 03:59:50 -08:00 committed by GitHub
parent 9ac199a8f2
commit babb436512
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 152 additions and 18 deletions

View file

@ -1,5 +1,6 @@
"""A Local To-do todo platform."""
import datetime
import logging
from ical.calendar import Calendar
@ -24,7 +25,8 @@ from .store import LocalTodoListStore
_LOGGER = logging.getLogger(__name__)
PRODID = "-//homeassistant.io//local_todo 1.0//EN"
PRODID = "-//homeassistant.io//local_todo 2.0//EN"
PRODID_REQUIRES_MIGRATION = "-//homeassistant.io//local_todo 1.0//EN"
ICS_TODO_STATUS_MAP = {
TodoStatus.IN_PROCESS: TodoItemStatus.NEEDS_ACTION,
@ -38,6 +40,25 @@ ICS_TODO_STATUS_MAP_INV = {
}
def _migrate_calendar(calendar: Calendar) -> bool:
"""Upgrade due dates to rfc5545 format.
In rfc5545 due dates are exclusive, however we previously set the due date
as inclusive based on what the user set in the UI. A task is considered
overdue at midnight at the start of a date so we need to shift the due date
to the next day for old calendar versions.
"""
if calendar.prodid is None or calendar.prodid != PRODID_REQUIRES_MIGRATION:
return False
migrated = False
for todo in calendar.todos:
if todo.due is None or isinstance(todo.due, datetime.datetime):
continue
todo.due += datetime.timedelta(days=1)
migrated = True
return migrated
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
@ -48,12 +69,16 @@ async def async_setup_entry(
store = hass.data[DOMAIN][config_entry.entry_id]
ics = await store.async_load()
calendar = IcsCalendarStream.calendar_from_ics(ics)
migrated = _migrate_calendar(calendar)
calendar.prodid = PRODID
name = config_entry.data[CONF_TODO_LIST_NAME]
entity = LocalTodoListEntity(store, calendar, name, unique_id=config_entry.entry_id)
async_add_entities([entity], True)
if migrated:
await entity.async_save()
def _convert_item(item: TodoItem) -> Todo:
"""Convert a HomeAssistant TodoItem to an ical Todo."""
@ -65,6 +90,8 @@ def _convert_item(item: TodoItem) -> Todo:
if item.status:
todo.status = ICS_TODO_STATUS_MAP_INV[item.status]
todo.due = item.due
if todo.due and not isinstance(todo.due, datetime.datetime):
todo.due += datetime.timedelta(days=1)
todo.description = item.description
return todo
@ -99,31 +126,36 @@ class LocalTodoListEntity(TodoListEntity):
async def async_update(self) -> None:
"""Update entity state based on the local To-do items."""
self._attr_todo_items = [
TodoItem(
uid=item.uid,
summary=item.summary or "",
status=ICS_TODO_STATUS_MAP.get(
item.status or TodoStatus.NEEDS_ACTION, TodoItemStatus.NEEDS_ACTION
),
due=item.due,
description=item.description,
todo_items = []
for item in self._calendar.todos:
if (due := item.due) and not isinstance(due, datetime.datetime):
due -= datetime.timedelta(days=1)
todo_items.append(
TodoItem(
uid=item.uid,
summary=item.summary or "",
status=ICS_TODO_STATUS_MAP.get(
item.status or TodoStatus.NEEDS_ACTION,
TodoItemStatus.NEEDS_ACTION,
),
due=due,
description=item.description,
)
)
for item in self._calendar.todos
]
self._attr_todo_items = todo_items
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
todo = _convert_item(item)
TodoStore(self._calendar).add(todo)
await self._async_save()
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_update_todo_item(self, item: TodoItem) -> None:
"""Update an item to the To-do list."""
todo = _convert_item(item)
TodoStore(self._calendar).edit(todo.uid, todo)
await self._async_save()
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_delete_todo_items(self, uids: list[str]) -> None:
@ -131,7 +163,7 @@ class LocalTodoListEntity(TodoListEntity):
store = TodoStore(self._calendar)
for uid in uids:
store.delete(uid)
await self._async_save()
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def async_move_todo_item(
@ -156,10 +188,10 @@ class LocalTodoListEntity(TodoListEntity):
if dst_idx > src_idx:
dst_idx -= 1
todos.insert(dst_idx, src_item)
await self._async_save()
await self.async_save()
await self.async_update_ha_state(force_refresh=True)
async def _async_save(self) -> None:
async def async_save(self) -> None:
"""Persist the todo list to disk."""
content = IcsCalendarStream.calendar_to_ics(self._calendar)
await self._store.async_store(content)