From 0f5504b67d5dfa8c21eb0fc33bd8c0196b1c25a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Bachmann?= Date: Tue, 20 Jan 2026 08:14:58 +0000 Subject: [PATCH] 0.7.2 --- custom_components/bahmcloud_store/core.py | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/custom_components/bahmcloud_store/core.py b/custom_components/bahmcloud_store/core.py index b50caff..47fc126 100644 --- a/custom_components/bahmcloud_store/core.py +++ b/custom_components/bahmcloud_store/core.py @@ -181,6 +181,108 @@ class BCSCore: return await self.hass.async_add_executor_job(_read) + async def _read_manifest_info_async(self) -> dict[str, str]: + """Read manifest.json fields that help identify this integration.""" + + def _read() -> dict[str, str]: + try: + manifest_path = Path(__file__).resolve().parent / "manifest.json" + data = json.loads(manifest_path.read_text(encoding="utf-8")) + out: dict[str, str] = {} + for k in ("version", "documentation", "name", "domain"): + v = data.get(k) + if v: + out[str(k)] = str(v) + return out + except Exception: + return {} + + return await self.hass.async_add_executor_job(_read) + + def _normalize_repo_base(self, url: str) -> str: + """Normalize repository URLs to a stable base for matching. + + Example: + https://git.example.tld/org/repo/raw/branch/main/store.yaml + becomes: + https://git.example.tld/org/repo + """ + try: + p = urlsplit(str(url or "").strip()) + parts = [x for x in (p.path or "").split("/") if x] + base_path = "/" + "/".join(parts[:2]) if len(parts) >= 2 else (p.path or "") + return urlunsplit((p.scheme, p.netloc, base_path.rstrip("/"), "", "")).lower() + except Exception: + return str(url or "").strip().lower() + + async def _ensure_self_marked_installed(self, repos: dict[str, RepoItem]) -> None: + """Ensure BCS is treated as installed when deployed via external installer. + + When users install BCS via an installer that places files into + /config/custom_components, our internal storage has no installed entry. + This breaks update detection for the BCS repo entry in the Store. + """ + try: + # Already tracked as installed? + items = await self.storage.list_installed_repos() + for it in items: + if DOMAIN in [str(d) for d in (it.domains or [])]: + return + + # Files must exist on disk. + cc_root = Path(self.hass.config.path("custom_components")) + manifest_path = cc_root / DOMAIN / "manifest.json" + if not manifest_path.exists(): + return + + info = await self._read_manifest_info_async() + doc = (info.get("documentation") or "").strip() + name = (info.get("name") or "").strip() + ver = (info.get("version") or self.version or "unknown").strip() + + doc_base = self._normalize_repo_base(doc) if doc else "" + + # Identify the matching repo entry in our current repo list. + chosen: RepoItem | None = None + if doc_base: + for r in repos.values(): + if self._normalize_repo_base(r.url) == doc_base: + chosen = r + break + + if not chosen and name: + for r in repos.values(): + if (r.name or "").strip().lower() == name.lower(): + chosen = r + break + + if not chosen: + for r in repos.values(): + if "bahmcloud_store" in (r.url or "").lower(): + chosen = r + break + + if not chosen: + _LOGGER.debug("BCS self-install reconcile: could not match repo entry") + return + + await self.storage.set_installed_repo( + repo_id=chosen.id, + url=chosen.url, + domains=[DOMAIN], + installed_version=ver if ver != "unknown" else None, + installed_manifest_version=ver if ver != "unknown" else None, + ref=ver if ver != "unknown" else None, + ) + + _LOGGER.info( + "BCS self-install reconcile: marked as installed (repo_id=%s version=%s)", + chosen.id, + ver, + ) + except Exception: + _LOGGER.debug("BCS self-install reconcile failed", exc_info=True) + def add_listener(self, cb) -> None: self._listeners.append(cb) @@ -324,6 +426,12 @@ class BCSCore: # Apply persisted per-repo enrichment cache (instant UI after restart). self._apply_repo_cache(merged) + # If BCS itself was installed via an external installer (i.e. files exist on disk + # but our storage has no installed entry yet), we still want update checks to work. + # Reconcile this once we have the current repo list. + await self._ensure_self_marked_installed(merged) + await self._refresh_installed_cache() + await self._enrich_installed_only(merged) self.repos = merged