diff --git a/custom_components/bahmcloud_store/core.py b/custom_components/bahmcloud_store/core.py index ebe667c..7bc2ca8 100644 --- a/custom_components/bahmcloud_store/core.py +++ b/custom_components/bahmcloud_store/core.py @@ -7,7 +7,7 @@ import time from dataclasses import dataclass from pathlib import Path from typing import Any -from urllib.parse import urlparse +from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -156,27 +156,62 @@ class BCSCore: await asyncio.gather(*(process_one(r) for r in merged.values()), return_exceptions=True) - async def _load_index_repos(self) -> tuple[list[RepoItem], int]: + def _add_cache_buster(self, url: str) -> str: + """Append a timestamp query parameter safely (works with existing query params).""" + parts = urlsplit(url) + q = dict(parse_qsl(parts.query, keep_blank_values=True)) + q["t"] = str(int(time.time())) + new_query = urlencode(q) + return urlunsplit((parts.scheme, parts.netloc, parts.path, new_query, parts.fragment)) + + def _gitea_src_to_raw(self, url: str) -> str: + """If the URL points to a Gitea 'src' page, convert it to a raw file URL.""" + # Example: + # /owner/repo/src/branch/main/store.yaml -> /owner/repo/raw/branch/main/store.yaml + parts = urlsplit(url) + path = parts.path + path2 = path.replace("/src/branch/", "/raw/branch/") + if path2 == path: + return url + return urlunsplit((parts.scheme, parts.netloc, path2, parts.query, parts.fragment)) + + async def _fetch_store_text(self, url: str) -> str: session = async_get_clientsession(self.hass) - # ---- CACHE BUSTING FIX (Phase B) ---- - # Force store.yaml to always be reloaded from remote: - # - add timestamp query parameter - # - disable HTTP cache via headers - url = f"{self.config.store_url}?t={int(time.time())}" headers = { - "Cache-Control": "no-cache", + "User-Agent": "BahmcloudStore (Home Assistant)", + "Cache-Control": "no-cache, no-store, max-age=0", "Pragma": "no-cache", + "Expires": "0", } + async with session.get(url, timeout=20, headers=headers) as resp: + if resp.status != 200: + raise BCSError(f"store_url returned {resp.status}") + return await resp.text() + + async def _load_index_repos(self) -> tuple[list[RepoItem], int]: + # ---- Phase B: force an actual remote reload of store.yaml ---- + store_url = (self.config.store_url or "").strip() + if not store_url: + raise BCSError("store_url is empty") + + url = self._add_cache_buster(store_url) + try: - async with session.get(url, timeout=20, headers=headers) as resp: - if resp.status != 200: - raise BCSError(f"store_url returned {resp.status}") - raw = await resp.text() + raw = await self._fetch_store_text(url) + + # If we accidentally fetched a HTML "src" page, try converting to raw. + # This makes the system robust even if someone configures a /src/branch/ URL. + if "