Skip to content

Commit

Permalink
Fix: Generate true Unique ID
Browse files Browse the repository at this point in the history
Using the name as the unique ID for integration configuration had issues
in that it made it impossible to rename the calendar / configuration
without removing and re-adding it

Issue: #66
Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
  • Loading branch information
tykeal committed Mar 23, 2022
1 parent 5370c8c commit 44079b8
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 28 deletions.
39 changes: 35 additions & 4 deletions custom_components/rental_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from .const import CONF_CHECKIN
from .const import CONF_CHECKOUT
from .const import CONF_CODE_GENERATION
from .const import CONF_CREATION_DATETIME
from .const import CONF_DAYS
from .const import CONF_EVENT_PREFIX
from .const import CONF_IGNORE_NON_RESERVED
Expand All @@ -43,6 +44,7 @@
from .const import DOMAIN
from .const import PLATFORMS
from .const import REQUEST_TIMEOUT
from .util import gen_uuid

_LOGGER = logging.getLogger(__name__)

Expand All @@ -67,7 +69,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
hass.data[DOMAIN][config.get(CONF_NAME)] = ICalEvents(hass=hass, config=config)
hass.data[DOMAIN][entry.unique_id] = ICalEvents(hass=hass, config=config)

for component in PLATFORMS:
hass.async_create_task(
Expand All @@ -92,11 +94,33 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
)
)
if unload_ok:
hass.data[DOMAIN].pop(config.get(CONF_NAME))
hass.data[DOMAIN].pop(entry.unique_id)

return unload_ok


async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate configuration."""

version = config_entry.version

# 1 -> 2: Migrate keys
if version == 1:
_LOGGER.debug("Migrating from version %s", version)
data = config_entry.data.copy()

data[CONF_CREATION_DATETIME] = str(dt.now())
hass.config_entries.async_update_entry(
entry=config_entry,
unique_id=gen_uuid(data[CONF_CREATION_DATETIME]),
data=data,
)
config_entry.version = 2
_LOGGER.debug("Migration of to version %s complete", config_entry.version)

return True


async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener."""
# No need to update if the options match the data
Expand All @@ -105,15 +129,21 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:

new_data = entry.options.copy()

old_data = hass.data[DOMAIN][entry.unique_id]

# do not update the creation datetime if it already exists (which it should)
new_data[CONF_CREATION_DATETIME] = old_data.created

hass.config_entries.async_update_entry(
entry=entry,
unique_id=entry.options[CONF_NAME],
unique_id=entry.unique_id,
data=new_data,
title=new_data[CONF_NAME],
options={},
)

# Update the calendar config
hass.data[DOMAIN][entry.data.get(CONF_NAME)].update_config(new_data)
hass.data[DOMAIN][entry.unique_id].update_config(new_data)


class ICalEvents:
Expand Down Expand Up @@ -151,6 +181,7 @@ def __init__(self, hass, config):
self.code_generator = config.get(CONF_CODE_GENERATION, DEFAULT_CODE_GENERATION)
self.event = None
self.all_day = False
self.created = config.get(CONF_CREATION_DATETIME, str(dt.now()))

async def async_get_events(
self, hass, start_date, end_date
Expand Down
2 changes: 1 addition & 1 deletion custom_components/rental_control/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):

entity_id = generate_entity_id(ENTITY_ID_FORMAT, DOMAIN + " " + name, hass=hass)

rental_control_events = hass.data[DOMAIN][name]
rental_control_events = hass.data[DOMAIN][config_entry.unique_id]

calendar = ICalCalendarEventDevice(hass, name, entity_id, rental_control_events)

Expand Down
38 changes: 16 additions & 22 deletions custom_components/rental_control/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Config flow for Rental Control integration."""
import asyncio
import logging
import re
from typing import Any
Expand All @@ -25,6 +24,7 @@
from .const import CONF_CHECKIN
from .const import CONF_CHECKOUT
from .const import CONF_CODE_GENERATION
from .const import CONF_CREATION_DATETIME
from .const import CONF_DAYS
from .const import CONF_EVENT_PREFIX
from .const import CONF_IGNORE_NON_RESERVED
Expand All @@ -44,6 +44,7 @@
from .const import DOMAIN
from .const import LOCK_MANAGER
from .const import REQUEST_TIMEOUT
from .util import gen_uuid

_LOGGER = logging.getLogger(__name__)

Expand All @@ -55,7 +56,7 @@
class RentalControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the config flow for Rental Control."""

VERSION = 1
VERSION = 2

DEFAULTS = {
CONF_CHECKIN: DEFAULT_CHECKIN,
Expand All @@ -69,11 +70,14 @@ class RentalControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
CONF_VERIFY_SSL: True,
}

async def _get_unique_name_error(self, user_input) -> Dict[str, str]:
"""Check if name is unique, returning dictionary if so."""
# Validate that Rental control is unique
def __init__(self):
"""Setup the RentalControlFlowHandler."""
self.created = str(dt.now())

async def _get_unique_id(self, user_input) -> Dict[str, str]:
"""Generate the unique_id."""
existing_entry = await self.async_set_unique_id(
user_input[CONF_NAME], raise_on_progress=True
gen_uuid(self.created), raise_on_progress=True
)
if existing_entry:
return {CONF_NAME: "same_name"}
Expand Down Expand Up @@ -103,16 +107,6 @@ def __init__(self, config_entry: config_entries.ConfigEntry):
"""Initialize Options Flow."""
self.config_entry = config_entry

def _get_unique_name_error(self, user_input) -> Dict[str, str]:
"""Check if name is unique, returning dictionary if so."""
# If name has changed, make sure new name isn't already being used
# otherwise show an error
if self.config_entry.unique_id != user_input[CONF_NAME]:
for entry in self.hass.config_entries.async_entries(DOMAIN):
if entry.unique_id == user_input[CONF_NAME]:
return {CONF_NAME: "same_name"}
return {}

async def async_step_init(
self,
user_input: Dict[str, Any] = None,
Expand Down Expand Up @@ -273,12 +267,9 @@ async def _start_config_flow(
description_placeholders = {}

if user_input is not None:
# Regular flow has an async function, options flow has a sync function
# so we need to handle them conditionally
if asyncio.iscoroutinefunction(cls._get_unique_name_error):
errors.update(await cls._get_unique_name_error(user_input))
else:
errors.update(cls._get_unique_name_error(user_input))
# Regular flow has an async function
if hasattr(cls, "_get_unique_id"):
errors.update(await cls._get_unique_id(user_input))

# Validate user input
try:
Expand Down Expand Up @@ -331,6 +322,9 @@ async def _start_config_flow(
ident=user_input[CONF_CODE_GENERATION], to_type=True
)

if hasattr(cls, "created"):
user_input[CONF_CREATION_DATETIME] = cls.created

return cls.async_create_entry(title=title, data=user_input)

return _show_config_form(
Expand Down
1 change: 1 addition & 0 deletions custom_components/rental_control/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
CONF_REFRESH_FREQUENCY = "refresh_frequency"
CONF_START_SLOT = "start_slot"
CONF_TIMEZONE = "timezone"
CONF_CREATION_DATETIME = "creation_datetime"

# Defaults
DEFAULT_CHECKIN = "16:00"
Expand Down
2 changes: 1 addition & 1 deletion custom_components/rental_control/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
name = config.get(CONF_NAME)
max_events = config.get(CONF_MAX_EVENTS)

rental_control_events = hass.data[DOMAIN][name]
rental_control_events = hass.data[DOMAIN][config_entry.unique_id]
await rental_control_events.update()
if rental_control_events.calendar is None:
_LOGGER.error("Unable to fetch iCal")
Expand Down

0 comments on commit 44079b8

Please sign in to comment.