Skip to content

Commit

Permalink
Fix race issue. (#109)
Browse files Browse the repository at this point in the history
Fix reload issue.
  • Loading branch information
twrecked authored Jun 28, 2024
1 parent 20ecc5d commit d0b2654
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 35 deletions.
3 changes: 3 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
0.9.0b11:
Fix race condition in meta data write
Fix reload issue that just doubled up the devices
0.9.0b10:
Fix IO in event loop issues
0.9.0a9:
Expand Down
35 changes: 31 additions & 4 deletions custom_components/virtual/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import logging
import voluptuous as vol
import asyncio
from distutils import util

import homeassistant.helpers.config_validation as cv
Expand All @@ -29,7 +30,7 @@
from .cfg import BlendedCfg, UpgradeCfg


__version__ = '0.9.0b10'
__version__ = '0.9.0b11'

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -120,11 +121,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for device in vcfg.devices:
_LOGGER.debug(f"creating-device={device}")
await _async_get_or_create_virtual_device_in_registry(hass, entry, device)
await asyncio.sleep(1)

# Delete orphaned devices.
for switch, values in vcfg.orphaned_entities.items():
_LOGGER.debug(f"would try to delete {switch}/{values}")
# await _async_delete_momentary_device_from_registry(hass, entry, switch, values)

# Update the component data.
hass.data[COMPONENT_DOMAIN].update({
entry.data[ATTR_GROUP_NAME]: {
ATTR_ENTITIES: vcfg.entities,
ATTR_DEVICES: vcfg.devices,
ATTR_FILE_NAME: entry.data[ATTR_FILE_NAME]
}
})
Expand Down Expand Up @@ -153,13 +161,18 @@ async def async_virtual_service_set_available(call) -> None:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug(f"unloading virtual group {entry.data[ATTR_GROUP_NAME]}")
# _LOGGER.debug(f"before hass={hass.data[COMPONENT_DOMAIN]}")
_LOGGER.debug(f"before hass={hass.data[COMPONENT_DOMAIN]}")
unload_ok = await hass.config_entries.async_unload_platforms(entry, VIRTUAL_PLATFORMS)
if unload_ok:
bcfg = BlendedCfg(hass, entry.data)
await bcfg.async_delete()
hass.data[COMPONENT_DOMAIN].pop(entry.data[ATTR_GROUP_NAME])
# _LOGGER.debug(f"after hass={hass.data[COMPONENT_DOMAIN]}")
ocfg = hass.data[COMPONENT_DOMAIN].pop(entry.data[ATTR_GROUP_NAME])
_LOGGER.debug(f"ocfg={ocfg}")
for device in ocfg[ATTR_DEVICES]:
_LOGGER.debug(f"del-device={device}")
await _async_delete_momentary_device_from_registry(hass, entry, device[ATTR_DEVICE_ID], device[CONF_NAME])
await asyncio.sleep(1)
_LOGGER.debug(f"after hass={hass.data[COMPONENT_DOMAIN]}")

return unload_ok

Expand Down Expand Up @@ -203,3 +216,17 @@ async def async_virtual_set_availability_service(hass, call):
domain = entity_id.split(".")[0]
_LOGGER.info("{} set_avilable(value={})".format(entity_id, value))
get_entity_from_domain(hass, domain, entity_id).set_available(value)


async def _async_delete_momentary_device_from_registry(
hass: HomeAssistant, _entry: ConfigEntry, device_id, _name
) -> None:
device_registry = dr.async_get(hass)
device = device_registry.async_get_device(
identifiers={(COMPONENT_DOMAIN, device_id)},
)
if device:
_LOGGER.debug(f"found something to delete! {device.id}")
device_registry.async_remove_device(device.id)
else:
_LOGGER.info(f"have orphaned device in meta {device_id}")
74 changes: 44 additions & 30 deletions custom_components/virtual/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""

import aiofiles
import asyncio
import copy
import logging
import json
Expand Down Expand Up @@ -49,6 +50,8 @@
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default=""): cv.string,
}))

_meta_lock = asyncio.Lock()


def _fix_value(value):
""" If needed, convert value into a type that can be stored in yaml.
Expand Down Expand Up @@ -107,49 +110,53 @@ async def _async_save_yaml(file_name, data):
async def _load_meta_data(hass, group_name: str):
"""Read in meta data for a particular group.
"""
data = await _async_load_json(default_meta_file(hass))
return data.get(ATTR_DEVICES, {}).get(group_name, {})
async with _meta_lock:
data = await _async_load_json(default_meta_file(hass))
return data.get(ATTR_DEVICES, {}).get(group_name, {})


async def _save_meta_data(hass, group_name, meta_data):
"""Save meta data for a particular group name.
"""
# Read in current meta data
devices = await _async_load_json(default_meta_file(hass))
devices = devices.get(ATTR_DEVICES, {})

# Update (or add) the group piece.
_LOGGER.debug(f"meta before {devices}")
devices.update({
group_name: meta_data
})
_LOGGER.debug(f"meta after {devices}")
async with _meta_lock:

# Write it back out.
await _async_save_json(default_meta_file(hass), {
ATTR_VERSION: 1,
ATTR_DEVICES: devices
})
# Read in current meta data
devices = await _async_load_json(default_meta_file(hass))
devices = devices.get(ATTR_DEVICES, {})

# Update (or add) the group piece.
_LOGGER.debug(f"meta before {devices}")
devices.update({
group_name: meta_data
})
_LOGGER.debug(f"meta after {devices}")

# Write it back out.
await _async_save_json(default_meta_file(hass), {
ATTR_VERSION: 1,
ATTR_DEVICES: devices
})


async def _delete_meta_data(hass, group_name):
"""Save meta data for a particular group name.
"""
async with _meta_lock:

# Read in current meta data
devices = await _async_load_json(default_meta_file(hass))
devices = devices.get(ATTR_DEVICES, {})
# Read in current meta data
devices = await _async_load_json(default_meta_file(hass))
devices = devices.get(ATTR_DEVICES, {})

# Delete the group piece.
_LOGGER.debug(f"meta before {devices}")
devices.pop(group_name)
_LOGGER.debug(f"meta after {devices}")
# Delete the group piece.
_LOGGER.debug(f"meta before {devices}")
devices.pop(group_name)
_LOGGER.debug(f"meta after {devices}")

# Write it back out.
await _async_save_json(default_meta_file(hass), {
ATTR_VERSION: 1,
ATTR_DEVICES: devices
})
# Write it back out.
await _async_save_json(default_meta_file(hass), {
ATTR_VERSION: 1,
ATTR_DEVICES: devices
})


async def _save_user_data(file_name, devices):
Expand Down Expand Up @@ -312,7 +319,8 @@ async def async_load(self):
unique_id = _make_unique_id()
meta_data.update({name: {
ATTR_UNIQUE_ID: unique_id,
ATTR_ENTITY_ID: _make_entity_id(platform, name)
ATTR_ENTITY_ID: _make_entity_id(platform, name),
ATTR_DEVICE_ID: device_name
}})
changed = True

Expand All @@ -323,6 +331,12 @@ async def async_load(self):
_LOGGER.info(f"problem creating {name}, no entity id")
continue

# Add device entry
if meta_data.get(name, {}).get(ATTR_DEVICE_ID, None) is None:
_LOGGER.info(f"problem creating {name}, no device id")
meta_data[name][ATTR_DEVICE_ID] = device_name
changed = True

# Update the entity.
entity.update({
CONF_NAME: _make_name(name),
Expand Down
2 changes: 1 addition & 1 deletion custom_components/virtual/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"documentation": "https://github.com/twrecked/hass-virtual/blob/master/README.md",
"iot_class": "local_push",
"issue_tracker": "https://github.com/twrecked/hass-virtual/issues",
"version": "0.9.0b10"
"version": "0.9.0b11"
}

0 comments on commit d0b2654

Please sign in to comment.