fix: allow genuinely new playlist ingest without fresh Player evidence
The evidence gate on enqueue_download blocked every download, including a URL that could only land in a slot store.reserve_download already proves is empty and unguarded. After a Controller-only restart with the Player still connected and idle, no fresh detailed status ever arrives, so this could deadlock all new ingest indefinitely even though nothing unsafe would occur. Narrow the gate to only require fresh evidence when the URL already occupies a non-empty slot (refresh/reuse). Deletion, expiry, and cleanup/recovery gating are unchanged.
This commit is contained in:
+20
-4
@@ -404,13 +404,15 @@ class ControllerService:
|
||||
def _handle_call(self, action: str, payload: Any) -> Any:
|
||||
if action == "enqueue_download":
|
||||
assert self.downloads is not None
|
||||
if not self._cleanup_allowed():
|
||||
url = payload["url"]
|
||||
if not self._cleanup_allowed() and self._ingest_requires_evidence(url):
|
||||
raise ControllerUnavailableError(
|
||||
"downloads are deferred until fresh Player evidence or "
|
||||
"administrative playback reconciliation"
|
||||
"refreshing or replacing existing playlist media is deferred "
|
||||
"until fresh Player evidence or administrative playback "
|
||||
"reconciliation"
|
||||
)
|
||||
reservation = self.downloads.enqueue(
|
||||
payload["url"], payload.get("submitter"), payload.get("note")
|
||||
url, payload.get("submitter"), payload.get("note")
|
||||
)
|
||||
result: dict[str, Any] = {
|
||||
"status": "queued" if reservation.status == "reserved" else reservation.status,
|
||||
@@ -1261,6 +1263,20 @@ class ControllerService:
|
||||
def _cleanup_allowed(self) -> bool:
|
||||
return self._player_evidence_ready or self._operator_cleanup_override
|
||||
|
||||
def _ingest_requires_evidence(self, url: Any) -> bool:
|
||||
"""True when ingesting ``url`` could reuse/refresh occupied media.
|
||||
|
||||
A URL that does not already occupy a slot can only land in a slot
|
||||
that ``reserve_download`` independently proves is empty and
|
||||
unguarded, so it cannot mutate, delete, or replace media the Player
|
||||
may currently be using. Only the reuse/refresh path needs the same
|
||||
conservative evidence gate as deletion and expiry.
|
||||
"""
|
||||
|
||||
if not isinstance(url, str):
|
||||
return False
|
||||
return self.store.find_occupied_slot_for_url(url) is not None
|
||||
|
||||
def _schedule_deletion_recovery(self) -> None:
|
||||
if not self._cleanup_allowed():
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user