Compare commits

...

3 Commits

Author SHA1 Message Date
Will Miao
33e5f3d85d fix(#933): compute SHA256 locally when CivitAI API returns empty hashes 2026-05-18 18:30:33 +08:00
Will Miao
031d5e4f40 fix(doctor): exclude checkpoints/embeddings from duplicate filename detection (#934)
Duplicate filename detection is only relevant for LoRAs, which use
basename-only syntax (<lora:name:strength>). Checkpoints and diffusion
models reference files via relative paths with extensions, so filename
conflicts there are false positives — there is no resolution ambiguity.

Both _log_duplicate_filename_summary() and DoctorHandler's
_check_filename_conflicts() now skip scanners with model_type != 'lora'.
2026-05-18 13:57:28 +08:00
willmiao
4ff5774e34 docs: auto-update supporters list in README 2026-05-17 12:40:26 +00:00
5 changed files with 44 additions and 7 deletions

File diff suppressed because one or more lines are too long

View File

@@ -995,6 +995,12 @@ class DoctorHandler:
total_conflict_files = 0
for model_type, label, factory in self._scanner_factories:
# Duplicate filename detection targets LoRAs which use basename-only
# syntax (<lora:name:strength>). Checkpoints/embeddings reference
# models via relative paths with extensions, so conflicts there would
# be false positives.
if model_type != "lora":
continue
try:
scanner = await factory()
hash_index = getattr(scanner, "_hash_index", None)

View File

@@ -18,6 +18,7 @@ from ..utils.constants import (
VALID_LORA_TYPES,
)
from ..utils.civitai_utils import normalize_civitai_download_url, rewrite_preview_url
from ..utils.file_utils import calculate_sha256
from ..utils.preview_selection import resolve_mature_threshold, select_preview_media
from ..utils.utils import sanitize_folder_name
from ..utils.exif_utils import ExifUtils
@@ -2239,8 +2240,11 @@ class DownloadManager:
entry.file_name = os.path.splitext(os.path.basename(file_path))[0]
# Update size to actual downloaded file size
entry.size = os.path.getsize(file_path)
# Use SHA256 from API metadata (already set in from_civitai_info)
# Do not recalculate to avoid blocking during ComfyUI execution
# Compute SHA256 locally when the API response didn't include it
if not entry.sha256:
sha256 = await calculate_sha256(file_path)
if sha256:
entry.sha256 = sha256.lower()
entries.append(entry)
return entries

View File

@@ -9,7 +9,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Mapping, Optional, Set,
from ..utils.models import BaseModelMetadata
from ..config import config
from ..utils.file_utils import find_preview_file, get_preview_extension
from ..utils.file_utils import find_preview_file, get_preview_extension, calculate_sha256
from ..utils.metadata_manager import MetadataManager
from ..utils.civitai_utils import resolve_license_info
from .model_cache import ModelCache
@@ -1067,6 +1067,19 @@ class ModelScanner:
model_data = self._build_cache_entry(metadata, folder=normalized_folder)
# Compute SHA256 hash when metadata provided none (e.g., CivitAI API response has empty hashes)
if not model_data.get('sha256') and file_path:
try:
logger.info(f"Computing SHA256 hash for {file_path} (was empty from metadata)")
sha256 = await calculate_sha256(file_path)
if sha256:
model_data['sha256'] = sha256.lower()
if isinstance(metadata, BaseModelMetadata):
metadata.sha256 = sha256.lower()
await MetadataManager.save_metadata(file_path, metadata)
except Exception as e:
logger.error(f"Failed to compute SHA256 for {file_path}: {e}")
# Skip excluded models
if model_data.get('exclude', False):
excluded_models.append(model_data['file_path'])
@@ -1101,7 +1114,10 @@ class ModelScanner:
def _log_duplicate_filename_summary(self) -> None:
"""Log a batched summary of duplicate filename conflicts once per scan."""
if self._hash_index is None:
# Duplicate filename detection is only relevant for LoRAs, which use
# basename-only syntax (<lora:name:strength>). Checkpoints and embeddings
# use full relative paths for resolution, so conflicts are not ambiguous.
if self._hash_index is None or self.model_type != "lora":
return
duplicates = self._hash_index.get_duplicate_filenames()
@@ -1473,6 +1489,15 @@ class ModelScanner:
file_path_override=normalized_new_path,
)
# Ensure sha256 is populated even when metadata doesn't have it
if not cache_entry.get('sha256') and normalized_new_path and os.path.exists(normalized_new_path):
try:
sha256 = await calculate_sha256(normalized_new_path)
if sha256:
cache_entry['sha256'] = sha256.lower()
except Exception as e:
logger.error(f"Failed to compute SHA256 for {normalized_new_path}: {e}")
if recalculate_type:
cache_entry = self.adjust_cached_entry(cache_entry)

View File

@@ -636,6 +636,8 @@ async def test_log_duplicate_filename_summary_logs_warning(tmp_path: Path, caplo
root = tmp_path / "loras"
root.mkdir()
scanner = DummyScanner(root)
# Duplicate filename detection is only active for LoRAs
scanner.model_type = "lora"
# Simulate duplicate filenames in the hash index
scanner._hash_index.add_entry("aaa111", str(root / "model.safetensors"))
@@ -646,7 +648,7 @@ async def test_log_duplicate_filename_summary_logs_warning(tmp_path: Path, caplo
assert len(caplog.records) >= 1
log_msg = caplog.records[-1].message
assert "Duplicate filename conflict detected" in log_msg
assert "1 dummy filename(s)" in log_msg
assert "1 lora filename(s)" in log_msg
assert "2 files total" in log_msg