diff --git a/custom_components/hacs/repositories/base.py b/custom_components/hacs/repositories/base.py index 950f52facd3..a0e50a4cb9f 100644 --- a/custom_components/hacs/repositories/base.py +++ b/custom_components/hacs/repositories/base.py @@ -916,6 +916,7 @@ async def _async_post_install(self) -> None: "repository_id": self.data.id, }, ) + self.hacs.data.async_schedule_write() self.logger.info("%s Post installation steps completed", self.string) async def async_install_repository(self, *, version: str | None = None, **_) -> None: diff --git a/custom_components/hacs/utils/data.py b/custom_components/hacs/utils/data.py index f540272e62d..4d33135af73 100644 --- a/custom_components/hacs/utils/data.py +++ b/custom_components/hacs/utils/data.py @@ -15,7 +15,9 @@ from ..repositories.base import TOPIC_FILTER, HacsManifest, HacsRepository from .logger import LOGGER from .path import is_safe -from .store import async_load_from_store, async_save_to_store +from .store import async_delay_save_to_store, async_load_from_store, async_save_to_store + +DELAYED_WRITE_DELAY = 30 EXPORTED_BASE_DATA = ( ("new", False), @@ -62,7 +64,7 @@ def __init__(self, hacs: HacsBase): """Initialize.""" self.logger = LOGGER self.hacs = hacs - self.content = {} + self.content: dict = {} async def async_force_write(self, _=None): """Force write.""" @@ -75,45 +77,67 @@ async def async_write(self, force: bool = False) -> None: self.logger.debug(" Saving data") - # Hacs - await async_save_to_store( - self.hacs.hass, - "hacs", - { - "archived_repositories": self.hacs.common.archived_repositories, - "renamed_repositories": self.hacs.common.renamed_repositories, - "ignored_repositories": self.hacs.common.ignored_repositories, - }, - ) - await self._async_store_experimental_content_and_repos() - await self._async_store_content_and_repos() + await async_save_to_store(self.hacs.hass, "hacs", self._build_hacs_data()) + await async_save_to_store(self.hacs.hass, "data", self._build_experimental_data()) + await async_save_to_store(self.hacs.hass, "repositories", self._build_repositories_data()) + for event in (HacsDispatchEvent.REPOSITORY, HacsDispatchEvent.CONFIG): + self.hacs.async_dispatch(event, {}) - async def _async_store_content_and_repos(self, _=None): # bb: ignore - """Store the main repos file and each repo that is out of date.""" - # Repositories - self.content = {} - for repository in self.hacs.repositories.list_all: - if repository.data.category in self.hacs.common.categories: - self.async_store_repository_data(repository) + @callback + def async_schedule_write(self) -> None: + """Schedule a debounced write of all HACS stores.""" + if self.hacs.system.disabled: + return + + self.logger.debug(" Scheduling delayed save") - await async_save_to_store(self.hacs.hass, "repositories", self.content) + async_delay_save_to_store( + self.hacs.hass, "hacs", self._build_hacs_data, DELAYED_WRITE_DELAY + ) + async_delay_save_to_store( + self.hacs.hass, "data", self._build_experimental_data, DELAYED_WRITE_DELAY + ) + async_delay_save_to_store( + self.hacs.hass, + "repositories", + self._build_repositories_data, + DELAYED_WRITE_DELAY, + ) for event in (HacsDispatchEvent.REPOSITORY, HacsDispatchEvent.CONFIG): self.hacs.async_dispatch(event, {}) - async def _async_store_experimental_content_and_repos(self, _=None): - """Store the main repos file and each repo that is out of date.""" - # Repositories - self.content = {} + @callback + def _build_hacs_data(self) -> dict: + return { + "archived_repositories": self.hacs.common.archived_repositories, + "renamed_repositories": self.hacs.common.renamed_repositories, + "ignored_repositories": self.hacs.common.ignored_repositories, + } + + @callback + def _build_repositories_data(self) -> dict: + content: dict[str, dict] = {} for repository in self.hacs.repositories.list_all: if repository.data.category in self.hacs.common.categories: - self.async_store_experimental_repository_data(repository) + content[str(repository.data.id)] = self._repository_export(repository) + return content - await async_save_to_store(self.hacs.hass, "data", {"repositories": self.content}) + @callback + def _build_experimental_data(self) -> dict: + content: dict[str, list] = {} + for repository in self.hacs.repositories.list_all: + if repository.data.category in self.hacs.common.categories: + content.setdefault(repository.data.category, []).append( + { + "id": str(repository.data.id), + **self._experimental_repository_export(repository), + } + ) + return {"repositories": content} @callback - def async_store_repository_data(self, repository: HacsRepository) -> dict: - """Store the repository data.""" - data = {"repository_manifest": repository.repository_manifest.manifest} + def _repository_export(self, repository: HacsRepository) -> dict: + data: dict[str, Any] = {"repository_manifest": repository.repository_manifest.manifest} for key, default in ( EXPORTED_DOWNLOADED_REPOSITORY_DATA @@ -128,13 +152,11 @@ def async_store_repository_data(self, repository: HacsRepository) -> dict: if repository.data.last_fetched: data["last_fetched"] = repository.data.last_fetched.timestamp() - self.content[str(repository.data.id)] = data + return data @callback - def async_store_experimental_repository_data(self, repository: HacsRepository) -> None: - """Store the experimental repository data for non downloaded repositories.""" - data = {} - self.content.setdefault(repository.data.category, []) + def _experimental_repository_export(self, repository: HacsRepository) -> dict: + data: dict[str, Any] = {} if repository.data.installed: data["repository_manifest"] = repository.repository_manifest.manifest @@ -151,7 +173,7 @@ def async_store_experimental_repository_data(self, repository: HacsRepository) - if (value := getattr(repository.data, key, default)) != default: data[key] = value - self.content[repository.data.category].append({"id": str(repository.data.id), **data}) + return data async def restore(self): """Restore saved data.""" diff --git a/custom_components/hacs/utils/store.py b/custom_components/hacs/utils/store.py index f0afa07b6bf..3570a76e189 100644 --- a/custom_components/hacs/utils/store.py +++ b/custom_components/hacs/utils/store.py @@ -1,5 +1,9 @@ """Storage handers.""" +from collections.abc import Callable +from typing import Any + +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.storage import Store from homeassistant.util import json as json_util @@ -72,6 +76,17 @@ async def async_save_to_store(hass, key, data): ) +@callback +def async_delay_save_to_store( + hass: HomeAssistant, + key: str, + data_func: Callable[[], Any], + delay: float, +) -> None: + """Schedule a debounced save to the store.""" + get_store_for_key(hass, key).async_delay_save(data_func, delay) + + async def async_remove_store(hass, key): """Remove a store element that should no longer be used.""" if "/" not in key: