1 """A Local To-do todo platform."""
7 from ical.calendar
import Calendar
8 from ical.calendar_stream
import IcsCalendarStream
9 from ical.store
import TodoStore
10 from ical.todo
import Todo, TodoStatus
16 TodoListEntityFeature,
24 from .
import LocalTodoConfigEntry
25 from .const
import CONF_TODO_LIST_NAME
26 from .store
import LocalTodoListStore
28 _LOGGER = logging.getLogger(__name__)
31 PRODID =
"-//homeassistant.io//local_todo 2.0//EN"
32 PRODID_REQUIRES_MIGRATION =
"-//homeassistant.io//local_todo 1.0//EN"
34 ICS_TODO_STATUS_MAP = {
35 TodoStatus.IN_PROCESS: TodoItemStatus.NEEDS_ACTION,
36 TodoStatus.NEEDS_ACTION: TodoItemStatus.NEEDS_ACTION,
37 TodoStatus.COMPLETED: TodoItemStatus.COMPLETED,
38 TodoStatus.CANCELLED: TodoItemStatus.COMPLETED,
40 ICS_TODO_STATUS_MAP_INV = {
41 TodoItemStatus.COMPLETED: TodoStatus.COMPLETED,
42 TodoItemStatus.NEEDS_ACTION: TodoStatus.NEEDS_ACTION,
47 """Upgrade due dates to rfc5545 format.
49 In rfc5545 due dates are exclusive, however we previously set the due date
50 as inclusive based on what the user set in the UI. A task is considered
51 overdue at midnight at the start of a date so we need to shift the due date
52 to the next day for old calendar versions.
54 if calendar.prodid
is None or calendar.prodid != PRODID_REQUIRES_MIGRATION:
57 for todo
in calendar.todos:
58 if todo.due
is None or isinstance(todo.due, datetime.datetime):
60 todo.due += datetime.timedelta(days=1)
67 config_entry: LocalTodoConfigEntry,
68 async_add_entities: AddEntitiesCallback,
70 """Set up the local_todo todo platform."""
72 store = config_entry.runtime_data
73 ics = await store.async_load()
79 calendar: Calendar = await hass.async_add_import_executor_job(
80 IcsCalendarStream.calendar_from_ics, ics
83 calendar.prodid = PRODID
85 name = config_entry.data[CONF_TODO_LIST_NAME]
90 await entity.async_save()
94 """Convert a HomeAssistant TodoItem to an ical Todo."""
99 todo.summary = item.summary
101 todo.status = ICS_TODO_STATUS_MAP_INV[item.status]
103 if todo.due
and not isinstance(todo.due, datetime.datetime):
104 todo.due += datetime.timedelta(days=1)
105 todo.description = item.description
110 """A To-do List representation of the Shopping List."""
112 _attr_has_entity_name =
True
113 _attr_supported_features = (
114 TodoListEntityFeature.CREATE_TODO_ITEM
115 | TodoListEntityFeature.DELETE_TODO_ITEM
116 | TodoListEntityFeature.UPDATE_TODO_ITEM
117 | TodoListEntityFeature.MOVE_TODO_ITEM
118 | TodoListEntityFeature.SET_DUE_DATETIME_ON_ITEM
119 | TodoListEntityFeature.SET_DUE_DATE_ON_ITEM
120 | TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM
122 _attr_should_poll =
False
126 store: LocalTodoListStore,
131 """Initialize LocalTodoListEntity."""
139 return TodoStore(self.
_calendar_calendar, tzinfo=dt_util.get_default_time_zone())
142 """Update entity state based on the local To-do items."""
144 for item
in self.
_calendar_calendar.todos:
145 if (due := item.due)
and not isinstance(due, datetime.datetime):
146 due -= datetime.timedelta(days=1)
150 summary=item.summary
or "",
151 status=ICS_TODO_STATUS_MAP.get(
152 item.status
or TodoStatus.NEEDS_ACTION,
153 TodoItemStatus.NEEDS_ACTION,
156 description=item.description,
162 """Add an item to the To-do list."""
166 await self.
hasshass.async_add_executor_job(todo_store.add, todo)
171 """Update an item to the To-do list."""
175 await self.
hasshass.async_add_executor_job(todo_store.edit, todo.uid, todo)
180 """Delete an item from the To-do list."""
189 self, uid: str, previous_uid: str |
None =
None
191 """Re-order an item to the To-do list."""
192 if uid == previous_uid:
196 item_idx: dict[str, int] = {itm.uid: idx
for idx, itm
in enumerate(todos)}
197 if uid
not in item_idx:
199 "Item '{uid}' not found in todo list {self.entity_id}"
201 if previous_uid
and previous_uid
not in item_idx:
203 "Item '{previous_uid}' not found in todo list {self.entity_id}"
205 dst_idx = item_idx[previous_uid] + 1
if previous_uid
else 0
206 src_idx = item_idx[uid]
207 src_item = todos.pop(src_idx)
208 if dst_idx > src_idx:
210 todos.insert(dst_idx, src_item)
215 """Persist the todo list to disk."""
216 content = IcsCalendarStream.calendar_to_ics(self.
_calendar_calendar)
217 await self.
_store_store.async_store(content)
None __init__(self, LocalTodoListStore store, Calendar calendar, str name, str unique_id)
None async_delete_todo_items(self, list[str] uids)
None async_move_todo_item(self, str uid, str|None previous_uid=None)
TodoStore _new_todo_store(self)
None async_update_todo_item(self, TodoItem item)
None async_create_todo_item(self, TodoItem item)
None async_update_ha_state(self, bool force_refresh=False)
None async_setup_entry(HomeAssistant hass, LocalTodoConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Todo _convert_item(TodoItem item)
bool _migrate_calendar(Calendar calendar)
Generator[None] async_pause_setup(core.HomeAssistant hass, SetupPhases phase)