429 files
This commit is contained in:
1 parent
472c19bceb
commit
963cce81a7
566 files changed
+50311
-47616
No files matched your search
@@ -83,10 +83,39 @@ class DocumentStore:
|
||||
"""Load persisted metadata + blob registry."""
|
||||
raw = await self._store.async_load()
|
||||
if raw is not None:
|
||||
self._data = raw
|
||||
self._data = self._sanitize_loaded(raw)
|
||||
self._data.setdefault("documents", {})
|
||||
self._data.setdefault("blobs", {})
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_loaded(raw: Any) -> dict[str, Any]:
|
||||
"""Coerce a loaded payload to the ``{"documents": {}, "blobs": {}}`` shape.
|
||||
|
||||
Mirrors ``storage.MaintenanceStore._sanitize_loaded``: HA quarantines
|
||||
syntactically corrupt files, but a hand-edited/partially written file
|
||||
can be valid JSON of the wrong SHAPE — and this is a GLOBAL store, so
|
||||
an AttributeError here would fail the whole integration setup, not
|
||||
just one object. Wrong-shaped sections degrade to empty; non-dict
|
||||
records are dropped, the rest kept.
|
||||
"""
|
||||
if not isinstance(raw, dict):
|
||||
_LOGGER.warning("Document store payload is %s, expected dict — resetting", type(raw).__name__)
|
||||
return {"documents": {}, "blobs": {}}
|
||||
data = dict(raw)
|
||||
for section in ("documents", "blobs"):
|
||||
value = data.get(section)
|
||||
if value is None:
|
||||
continue
|
||||
if not isinstance(value, dict):
|
||||
_LOGGER.warning("Document store %r is %s, expected dict — resetting", section, type(value).__name__)
|
||||
data[section] = {}
|
||||
continue
|
||||
kept = {key: rec for key, rec in value.items() if isinstance(rec, dict)}
|
||||
if len(kept) != len(value):
|
||||
_LOGGER.warning("Dropping %d malformed document store %r record(s)", len(value) - len(kept), section)
|
||||
data[section] = kept
|
||||
return data
|
||||
|
||||
async def _async_save(self) -> None:
|
||||
# Doc mutations are infrequent → immediate save keeps metadata and the
|
||||
# on-disk blobs consistent (a crash between the two only ever leaves an
|
||||
@@ -378,6 +407,36 @@ class DocumentStore:
|
||||
await self._async_save()
|
||||
return freed
|
||||
|
||||
async def async_unlink_task(self, task_id: str) -> int:
|
||||
"""Drop every document link to a deleted task. Returns the docs touched.
|
||||
|
||||
The task-delete cleanup counterpart of :meth:`async_remove_object`:
|
||||
``task_ids`` / ``task_pages`` are task-id keyed and persistent, so
|
||||
without this a deleted task's id lingers in the document metadata
|
||||
forever (and the panel's "linked tasks" chips point at nothing).
|
||||
Task ids are uuids, so no object filter is needed.
|
||||
"""
|
||||
touched = 0
|
||||
for doc in self.documents.values():
|
||||
linked = doc.get("task_ids")
|
||||
pages = doc.get("task_pages")
|
||||
hit = False
|
||||
if isinstance(linked, list) and task_id in linked:
|
||||
doc["task_ids"] = [t for t in linked if t != task_id]
|
||||
hit = True
|
||||
if isinstance(pages, dict) and task_id in pages:
|
||||
remaining = {t: p for t, p in pages.items() if t != task_id}
|
||||
if remaining:
|
||||
doc["task_pages"] = remaining
|
||||
else:
|
||||
doc.pop("task_pages", None)
|
||||
hit = True
|
||||
if hit:
|
||||
touched += 1
|
||||
if touched:
|
||||
await self._async_save()
|
||||
return touched
|
||||
|
||||
async def async_remove_object(self, object_id: str) -> int:
|
||||
"""Remove every document of an object (on object delete). Bytes freed."""
|
||||
doc_ids = [did for did, d in self.documents.items() if d.get("object_id") == object_id]
|
||||
|
||||
Reference in new issue
Block a user