fix(example-images): stop permanently blacklisting models on transient failures

The example-images download loop marked a model as failed in
.download_progress.json whenever its metadata had no civitai images or
processing raised any exception. Since failed_models is a permanent skip
list for non-force runs (and excluded from the pending pre-check), models
whose metadata simply had not been fetched yet — or that hit a transient
error — were never retried. Users had to delete the progress file to
unstick them, and the blacklist re-grew afterwards.

- Distinguish 'metadata not fetched yet' (no civitai payload) from
  confirmed absences (civitai entry present but image-less, or the model
  is known to be gone from CivitAI via civitai_deleted/from_civitai).
  Only confirmed absences are marked failed; the rest stay pending.
  Retrying pending models only re-reads local metadata, so no extra
  provider API calls are introduced.
- Transient processing errors no longer add the model to failed_models.
- Add unblock_failed_example_image_models() to remove hashes from the
  persisted failed list (library-scoped and legacy locations) and from
  the live download manager's in-memory state.
- Hook it into MetadataSyncService.update_model_metadata so a successful
  metadata fetch/relink immediately re-enables example image downloads
  for that model — no TTL polling needed.
This commit is contained in:
Will Miao
2026-10-10 08:09:08 +08:00
parent af90fe873d
commit b31e831c57
4 changed files with 420 additions and 6 deletions
+33
View File
@@ -241,8 +241,41 @@ class MetadataSyncService:
raise MetadataPersistError(
f"Failed to write metadata sidecar: {metadata_path}"
)
self._unblock_example_image_failures(local_metadata)
return local_metadata
@staticmethod
def _unblock_example_image_failures(local_metadata: Dict[str, Any]) -> None:
"""Drop the model from the example-images failed list, if present.
Fresh provider metadata invalidates any earlier "no example images"
verdict, so the next example-image run should retry the model. The
unblock only edits the local progress file — no provider API calls —
and must never break the metadata update itself.
"""
sha256 = (local_metadata.get("sha256") or "").lower()
if not sha256:
return
try:
# Lazy import: example_images_download_manager (via
# example_images_metadata) imports this service at module level.
from ..utils.example_images_download_manager import (
unblock_failed_example_image_models,
)
removed = unblock_failed_example_image_models([sha256])
if removed:
logger.info(
"Re-enabled example image downloads for %s after metadata update",
local_metadata.get("model_name", sha256),
)
except Exception as exc: # pragma: no cover - defensive logging
logger.debug(
"Failed to unblock example image failures for %s: %s", sha256, exc
)
async def fetch_and_update_model(
self,
*,