custom_components/bahmcloud_store/core.py aktualisiert

This commit is contained in:
2026-01-15 16:21:14 +00:00
parent c4361cc8bd
commit ac5bc8a6f4

View File

@@ -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 "<html" in raw.lower() or "<!doctype html" in raw.lower():
fallback = self._add_cache_buster(self._gitea_src_to_raw(store_url))
if fallback != url:
_LOGGER.debug("BCS store index looked like HTML, retrying raw URL")
raw = await self._fetch_store_text(fallback)
except Exception as e:
raise BCSError(f"Failed fetching store index: {e}") from e
# ---- END CACHE BUSTING FIX ----
# ---- end Phase B fix ----
try:
data = ha_yaml.parse_yaml(raw)
@@ -192,16 +227,16 @@ class BCSCore:
for i, r in enumerate(repos):
if not isinstance(r, dict):
continue
url = str(r.get("url", "")).strip()
if not url:
repo_url = str(r.get("url", "")).strip()
if not repo_url:
continue
name = str(r.get("name") or url).strip()
name = str(r.get("name") or repo_url).strip()
items.append(
RepoItem(
id=f"index:{i}",
name=name,
url=url,
url=repo_url,
source="index",
)
)