feat(download): expose per-file downloadedFiles in check-model-exists (#1058)

The version branch of check-model-exists now returns
downloadedFiles: [{fileId, fileName, filePath}] so clients (e.g. the
browser extension) can tell a partially downloaded version apart from a
fully downloaded one. Reuses ModelCivitaiHandler._match_downloaded_files
(D2 rule) against the local cache; unmatchable local files are reported
with fileId: None. No CivitAI API call added.
This commit is contained in:
Will Miao
2026-08-20 20:59:19 +08:00
parent 6647c45731
commit 259e08e47c
2 changed files with 209 additions and 0 deletions
+66
View File
@@ -56,6 +56,7 @@ from ...utils.constants import (
)
from .hf_handlers import HfHandler
from .agent_handlers import AgentHandler
from .model_handlers import ModelCivitaiHandler
from ...utils.civitai_utils import rewrite_preview_url
from ...utils.example_images_paths import (
find_non_compliant_items_in_example_images_root,
@@ -2061,6 +2062,63 @@ class ModelLibraryHandler:
enriched.append(entry)
return enriched
@staticmethod
async def _get_downloaded_files(
scanner: Any, model_version_id: int
) -> list[dict[str, Any]]:
"""Return per-file downloaded state for a version in the library.
This handler has no CivitAI version payload, so the remote file list
is taken from the local entries' cached ``civitai`` metadata (the
full version payload persisted at download time, see
``BaseModelMetadata.from_civitai_info``) and matched with the same
D2 rule used by ``get_civitai_versions`` (#1058). Local entries that
cannot be matched to a known remote file (e.g. missing metadata or
renamed files) are still reported with ``fileId`` set to None.
Returns ``[{fileId, fileName, filePath}]``.
"""
try:
cache = await scanner.get_cached_data()
except Exception: # pragma: no cover - defensive fallback
logger.debug(
"Failed to read cache for downloaded files of version %s",
model_version_id,
exc_info=True,
)
return []
files_getter = getattr(cache, "get_files_by_version_id", None)
local_entries = files_getter(model_version_id) if files_getter else []
if not local_entries:
return []
version_payload: Mapping[str, Any] = {}
for entry in local_entries:
civitai = entry.get("civitai") if isinstance(entry, Mapping) else None
if isinstance(civitai, Mapping) and isinstance(civitai.get("files"), list):
version_payload = civitai
break
downloaded = ModelCivitaiHandler._match_downloaded_files(
version_payload, local_entries
)
# Surface local files that D2 could not map to a known remote file
matched_paths = {item.get("filePath") for item in downloaded}
for entry in local_entries:
if not isinstance(entry, Mapping):
continue
if entry.get("file_path") in matched_paths:
continue
downloaded.append(
{
"fileId": None,
"fileName": entry.get("file_name"),
"filePath": entry.get("file_path"),
}
)
return downloaded
async def check_model_exists(self, request: web.Request) -> web.Response:
try:
model_id_str = request.query.get("modelId")
@@ -2096,9 +2154,11 @@ class ModelLibraryHandler:
exists = False
model_type = None
matched_scanner = None
if await lora_scanner.check_model_version_exists(model_version_id):
exists = True
model_type = "lora"
matched_scanner = lora_scanner
elif (
checkpoint_scanner
and await checkpoint_scanner.check_model_version_exists(
@@ -2107,6 +2167,7 @@ class ModelLibraryHandler:
):
exists = True
model_type = "checkpoint"
matched_scanner = checkpoint_scanner
elif (
embedding_scanner
and await embedding_scanner.check_model_version_exists(
@@ -2115,6 +2176,7 @@ class ModelLibraryHandler:
):
exists = True
model_type = "embedding"
matched_scanner = embedding_scanner
if exists:
return web.json_response(
@@ -2123,6 +2185,9 @@ class ModelLibraryHandler:
"exists": True,
"modelType": model_type,
"hasBeenDownloaded": False,
"downloadedFiles": await self._get_downloaded_files(
matched_scanner, model_version_id
),
}
)
@@ -2144,6 +2209,7 @@ class ModelLibraryHandler:
"exists": False,
"modelType": history_type,
"hasBeenDownloaded": has_been_downloaded,
"downloadedFiles": [],
}
)