diff --git a/supervisor/misc/tasks.py b/supervisor/misc/tasks.py index 602526027..d8f199835 100644 --- a/supervisor/misc/tasks.py +++ b/supervisor/misc/tasks.py @@ -121,6 +121,12 @@ class Tasks(CoreSysAttributes): continue # Delay auto-updates for a day in case of issues if utcnow() < addon.latest_version_timestamp + timedelta(days=1): + _LOGGER.debug( + "Not updating add-on %s from %s to %s as the latest version is less than a day old", + addon.slug, + addon.version, + addon.latest_version, + ) continue if not addon.test_update_schema(): _LOGGER.warning( diff --git a/supervisor/store/data.py b/supervisor/store/data.py index 27a5606c3..d189e3a46 100644 --- a/supervisor/store/data.py +++ b/supervisor/store/data.py @@ -15,7 +15,6 @@ from ..const import ( ATTR_REPOSITORY, ATTR_SLUG, ATTR_TRANSLATIONS, - ATTR_VERSION, ATTR_VERSION_TIMESTAMP, FILE_SUFFIX_CONFIGURATION, REPOSITORY_CORE, @@ -25,7 +24,6 @@ from ..coresys import CoreSys, CoreSysAttributes from ..exceptions import ConfigurationFileError from ..resolution.const import ContextType, IssueType, SuggestionType, UnhealthyReason from ..utils.common import find_one_filetype, read_json_or_yaml_file -from ..utils.dt import utcnow from ..utils.json import read_json_file from .const import StoreType from .utils import extract_hash_from_path @@ -142,23 +140,12 @@ class StoreData(CoreSysAttributes): repositories[repo.slug] = repo.config addons.update(await self._read_addons_folder(repo.path, repo.slug)) - # Add a timestamp when we first see a new version - for slug, config in addons.items(): - old_config = self.addons.get(slug) - - if ( - not old_config - or ATTR_VERSION_TIMESTAMP not in old_config - or old_config.get(ATTR_VERSION) != config.get(ATTR_VERSION) - ): - config[ATTR_VERSION_TIMESTAMP] = utcnow().timestamp() - else: - config[ATTR_VERSION_TIMESTAMP] = old_config[ATTR_VERSION_TIMESTAMP] - self.repositories = repositories self.addons = addons - async def _find_addons(self, path: Path, repository: dict) -> list[Path] | None: + async def _find_addon_configs( + self, path: Path, repository: dict + ) -> list[Path] | None: """Find add-ons in the path.""" def _get_addons_list() -> list[Path]: @@ -200,14 +187,14 @@ class StoreData(CoreSysAttributes): self, path: Path, repository: str ) -> dict[str, dict[str, Any]]: """Read data from add-ons folder.""" - if not (addon_list := await self._find_addons(path, repository)): + if not (addon_config_list := await self._find_addon_configs(path, repository)): return {} def _process_addons_config() -> dict[str, dict[str, Any]]: - addons_config: dict[str, dict[str, Any]] = {} - for addon in addon_list: + addons: dict[str, dict[str, Any]] = {} + for addon_config in addon_config_list: try: - addon_config = read_json_or_yaml_file(addon) + addon = read_json_or_yaml_file(addon_config) except ConfigurationFileError: _LOGGER.warning( "Can't read %s from repository %s", addon, repository @@ -216,23 +203,24 @@ class StoreData(CoreSysAttributes): # validate try: - addon_config = SCHEMA_ADDON_CONFIG(addon_config) + addon = SCHEMA_ADDON_CONFIG(addon) except vol.Invalid as ex: _LOGGER.warning( - "Can't read %s: %s", addon, humanize_error(addon_config, ex) + "Can't read %s: %s", addon, humanize_error(addon, ex) ) continue # Generate slug - addon_slug = f"{repository}_{addon_config[ATTR_SLUG]}" + addon_slug = f"{repository}_{addon[ATTR_SLUG]}" # store - addon_config[ATTR_REPOSITORY] = repository - addon_config[ATTR_LOCATION] = str(addon.parent) - addon_config[ATTR_TRANSLATIONS] = _read_addon_translations(addon.parent) - addons_config[addon_slug] = addon_config + addon[ATTR_REPOSITORY] = repository + addon[ATTR_LOCATION] = str(addon_config.parent) + addon[ATTR_TRANSLATIONS] = _read_addon_translations(addon_config.parent) + addon[ATTR_VERSION_TIMESTAMP] = addon_config.stat().st_mtime + addons[addon_slug] = addon - return addons_config + return addons return await self.sys_run_in_executor(_process_addons_config) diff --git a/tests/store/test_reading_addons.py b/tests/store/test_reading_addons.py index 40d21dd40..a5a90583a 100644 --- a/tests/store/test_reading_addons.py +++ b/tests/store/test_reading_addons.py @@ -25,7 +25,7 @@ async def test_read_addon_files(coresys: CoreSys): Path(".circleci/config.yml"), ], ): - addon_list = await coresys.store.data._find_addons(Path("test"), {}) + addon_list = await coresys.store.data._find_addon_configs(Path("test"), {}) assert len(addon_list) == 1 assert str(addon_list[0]) == "addon/config.yml" @@ -38,14 +38,14 @@ async def test_reading_addon_files_error(coresys: CoreSys): with patch("pathlib.Path.glob", side_effect=(err := OSError())): err.errno = errno.EBUSY - assert (await coresys.store.data._find_addons(Path("test"), {})) is None + assert (await coresys.store.data._find_addon_configs(Path("test"), {})) is None assert corrupt_repo in coresys.resolution.issues assert reset_repo in coresys.resolution.suggestions assert coresys.core.healthy is True coresys.resolution.dismiss_issue(corrupt_repo) err.errno = errno.EBADMSG - assert (await coresys.store.data._find_addons(Path("test"), {})) is None + assert (await coresys.store.data._find_addon_configs(Path("test"), {})) is None assert corrupt_repo in coresys.resolution.issues assert reset_repo not in coresys.resolution.suggestions assert coresys.core.healthy is False diff --git a/tests/store/test_store_manager.py b/tests/store/test_store_manager.py index 14002b5cb..e1643cd84 100644 --- a/tests/store/test_store_manager.py +++ b/tests/store/test_store_manager.py @@ -1,5 +1,7 @@ """Test store manager.""" +from datetime import datetime +from types import SimpleNamespace from typing import Any from unittest.mock import PropertyMock, patch @@ -251,7 +253,11 @@ async def test_addon_version_timestamp(coresys: CoreSys, install_addon_example: # If a new version is seen processing repo, reset to utc now install_addon_example.data_store["version"] = "1.1.0" - # Signal the store repositories got updated - with patch("supervisor.store.repository.Repository.update", return_value=True): + with patch( + "pathlib.Path.stat", + return_value=SimpleNamespace( + st_mode=0o100644, st_mtime=datetime.now().timestamp() + ), + ): await coresys.store.reload() - assert timestamp < install_addon_example.latest_version_timestamp + assert timestamp < install_addon_example.latest_version_timestamp