diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 3864a2651f8..1ee5e19caa7 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -1,5 +1,4 @@ """Support for local control of entities by emulating a Philips Hue bridge.""" -from contextlib import suppress import logging from aiohttp import web @@ -12,9 +11,8 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv -from homeassistant.util.json import load_json, save_json from .hue_api import ( HueAllGroupsStateView, @@ -34,6 +32,9 @@ DOMAIN = "emulated_hue" _LOGGER = logging.getLogger(__name__) NUMBERS_FILE = "emulated_hue_ids.json" +DATA_KEY = "emulated_hue.ids" +DATA_VERSION = "1" +SAVE_DELAY = 60 CONF_ADVERTISE_IP = "advertise_ip" CONF_ADVERTISE_PORT = "advertise_port" @@ -155,6 +156,7 @@ async def async_setup(hass, yaml_config): nonlocal protocol nonlocal site nonlocal runner + await config.async_setup() _, protocol = await listen @@ -189,6 +191,7 @@ class Config: self.hass = hass self.type = conf.get(CONF_TYPE) self.numbers = None + self.store = None self.cached_states = {} self._exposed_cache = {} @@ -257,14 +260,21 @@ class Config: # for compatibility with older installations. self.lights_all_dimmable = conf.get(CONF_LIGHTS_ALL_DIMMABLE) + async def async_setup(self): + """Set up and migrate to storage.""" + self.store = storage.Store(self.hass, DATA_VERSION, DATA_KEY) + self.numbers = ( + await storage.async_migrator( + self.hass, self.hass.config.path(NUMBERS_FILE), self.store + ) + or {} + ) + def entity_id_to_number(self, entity_id): """Get a unique number for the entity id.""" if self.type == TYPE_ALEXA: return entity_id - if self.numbers is None: - self.numbers = _load_json(self.hass.config.path(NUMBERS_FILE)) - # Google Home for number, ent_id in self.numbers.items(): if entity_id == ent_id: @@ -274,7 +284,7 @@ class Config: if self.numbers: number = str(max(int(k) for k in self.numbers) + 1) self.numbers[number] = entity_id - save_json(self.hass.config.path(NUMBERS_FILE), self.numbers) + self.store.async_delay_save(lambda: self.numbers, SAVE_DELAY) return number def number_to_entity_id(self, number): @@ -282,9 +292,6 @@ class Config: if self.type == TYPE_ALEXA: return number - if self.numbers is None: - self.numbers = _load_json(self.hass.config.path(NUMBERS_FILE)) - # Google Home assert isinstance(number, str) return self.numbers.get(number) @@ -338,10 +345,3 @@ class Config: return True return False - - -def _load_json(filename): - """Load JSON, handling invalid syntax.""" - with suppress(HomeAssistantError): - return load_json(filename) - return {} diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 8e8c0f41249..da15fbfba30 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,106 +1,101 @@ """Test the Emulated Hue component.""" -from unittest.mock import MagicMock, Mock, patch +from datetime import timedelta -from homeassistant.components.emulated_hue import Config +from homeassistant.components.emulated_hue import ( + DATA_KEY, + DATA_VERSION, + SAVE_DELAY, + Config, +) +from homeassistant.util import utcnow + +from tests.common import async_fire_time_changed -def test_config_google_home_entity_id_to_number(): +async def test_config_google_home_entity_id_to_number(hass, hass_storage): """Test config adheres to the type.""" - mock_hass = Mock() - mock_hass.config.path = MagicMock("path", return_value="test_path") - conf = Config(mock_hass, {"type": "google_home"}) + conf = Config(hass, {"type": "google_home"}) + hass_storage[DATA_KEY] = { + "version": DATA_VERSION, + "key": DATA_KEY, + "data": {"1": "light.test2"}, + } - with patch( - "homeassistant.components.emulated_hue.load_json", - return_value={"1": "light.test2"}, - ) as json_loader, patch( - "homeassistant.components.emulated_hue.save_json" - ) as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "2" + await conf.async_setup() - assert json_saver.mock_calls[0][1][1] == { - "1": "light.test2", - "2": "light.test", - } + number = conf.entity_id_to_number("light.test") + assert number == "2" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + async_fire_time_changed(hass, utcnow() + timedelta(seconds=SAVE_DELAY)) + await hass.async_block_till_done() + assert hass_storage[DATA_KEY]["data"] == { + "1": "light.test2", + "2": "light.test", + } - number = conf.entity_id_to_number("light.test") - assert number == "2" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "2" - number = conf.entity_id_to_number("light.test2") - assert number == "1" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test2") + assert number == "1" - entity_id = conf.number_to_entity_id("1") - assert entity_id == "light.test2" + entity_id = conf.number_to_entity_id("1") + assert entity_id == "light.test2" -def test_config_google_home_entity_id_to_number_altered(): +async def test_config_google_home_entity_id_to_number_altered(hass, hass_storage): """Test config adheres to the type.""" - mock_hass = Mock() - mock_hass.config.path = MagicMock("path", return_value="test_path") - conf = Config(mock_hass, {"type": "google_home"}) + conf = Config(hass, {"type": "google_home"}) + hass_storage[DATA_KEY] = { + "version": DATA_VERSION, + "key": DATA_KEY, + "data": {"21": "light.test2"}, + } - with patch( - "homeassistant.components.emulated_hue.load_json", - return_value={"21": "light.test2"}, - ) as json_loader, patch( - "homeassistant.components.emulated_hue.save_json" - ) as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "22" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + await conf.async_setup() - assert json_saver.mock_calls[0][1][1] == { - "21": "light.test2", - "22": "light.test", - } + number = conf.entity_id_to_number("light.test") + assert number == "22" - number = conf.entity_id_to_number("light.test") - assert number == "22" - assert json_saver.call_count == 1 + async_fire_time_changed(hass, utcnow() + timedelta(seconds=SAVE_DELAY)) + await hass.async_block_till_done() + assert hass_storage[DATA_KEY]["data"] == { + "21": "light.test2", + "22": "light.test", + } - number = conf.entity_id_to_number("light.test2") - assert number == "21" - assert json_saver.call_count == 1 + number = conf.entity_id_to_number("light.test") + assert number == "22" - entity_id = conf.number_to_entity_id("21") - assert entity_id == "light.test2" + number = conf.entity_id_to_number("light.test2") + assert number == "21" + + entity_id = conf.number_to_entity_id("21") + assert entity_id == "light.test2" -def test_config_google_home_entity_id_to_number_empty(): +async def test_config_google_home_entity_id_to_number_empty(hass, hass_storage): """Test config adheres to the type.""" - mock_hass = Mock() - mock_hass.config.path = MagicMock("path", return_value="test_path") - conf = Config(mock_hass, {"type": "google_home"}) + conf = Config(hass, {"type": "google_home"}) + hass_storage[DATA_KEY] = {"version": DATA_VERSION, "key": DATA_KEY, "data": {}} - with patch( - "homeassistant.components.emulated_hue.load_json", return_value={} - ) as json_loader, patch( - "homeassistant.components.emulated_hue.save_json" - ) as json_saver: - number = conf.entity_id_to_number("light.test") - assert number == "1" - assert json_saver.call_count == 1 - assert json_loader.call_count == 1 + await conf.async_setup() - assert json_saver.mock_calls[0][1][1] == {"1": "light.test"} + number = conf.entity_id_to_number("light.test") + assert number == "1" - number = conf.entity_id_to_number("light.test") - assert number == "1" - assert json_saver.call_count == 1 + async_fire_time_changed(hass, utcnow() + timedelta(seconds=SAVE_DELAY)) + await hass.async_block_till_done() + assert hass_storage[DATA_KEY]["data"] == {"1": "light.test"} - number = conf.entity_id_to_number("light.test2") - assert number == "2" - assert json_saver.call_count == 2 + number = conf.entity_id_to_number("light.test") + assert number == "1" - entity_id = conf.number_to_entity_id("2") - assert entity_id == "light.test2" + number = conf.entity_id_to_number("light.test2") + assert number == "2" + + entity_id = conf.number_to_entity_id("2") + assert entity_id == "light.test2" def test_config_alexa_entity_id_to_number():