diff --git a/py/services/cache_entry_validator.py b/py/services/cache_entry_validator.py index c4848856..df481139 100644 --- a/py/services/cache_entry_validator.py +++ b/py/services/cache_entry_validator.py @@ -58,6 +58,7 @@ class CacheEntryValidator: 'preview_nsfw_level': (0, False), 'notes': ('', False), 'usage_tips': ('', False), + 'hash_status': ('completed', False), } @classmethod @@ -92,11 +93,25 @@ class CacheEntryValidator: repaired = False working_entry = dict(entry) if auto_repair else entry + # First, ensure hash_status is present as it's used to validate sha256 + hash_status = working_entry.get('hash_status') + if hash_status is None: + working_entry['hash_status'] = 'completed' + repaired = True + hash_status = 'completed' + for field_name, (default_value, is_required) in cls.CORE_FIELDS.items(): value = working_entry.get(field_name) # Check if field is missing or None if value is None: + # Special case: sha256 can be None/empty if hash_status is pending + if field_name == 'sha256' and hash_status == 'pending': + if auto_repair: + working_entry[field_name] = '' + repaired = True + continue + if is_required: errors.append(f"Required field '{field_name}' is missing or None") if auto_repair: @@ -107,6 +122,10 @@ class CacheEntryValidator: # Validate field type and value field_error = cls._validate_field(field_name, value, default_value) if field_error: + # Special case: allow empty string for sha256 if pending + if field_name == 'sha256' and hash_status == 'pending' and value == '': + continue + errors.append(field_error) if auto_repair: working_entry[field_name] = cls._get_default_copy(default_value) @@ -127,10 +146,11 @@ class CacheEntryValidator: # Special validation: sha256 must not be empty for required field # BUT allow empty sha256 when hash_status is pending (lazy hash calculation) sha256 = working_entry.get('sha256', '') - hash_status = working_entry.get('hash_status', 'completed') + # Re-fetch hash_status in case it was changed during loop + current_hash_status = working_entry.get('hash_status', 'completed') if not sha256 or (isinstance(sha256, str) and not sha256.strip()): # Allow empty sha256 for lazy hash calculation (checkpoints) - if hash_status != 'pending': + if current_hash_status != 'pending': errors.append("Required field 'sha256' is empty") # Cannot repair empty sha256 - entry is invalid return ValidationResult( diff --git a/py/services/persistent_model_cache.py b/py/services/persistent_model_cache.py index 46a4cd51..e2cc0dd0 100644 --- a/py/services/persistent_model_cache.py +++ b/py/services/persistent_model_cache.py @@ -56,6 +56,7 @@ class PersistentModelCache: "exclude", "db_checked", "last_checked_at", + "hash_status", ) _MODEL_UPDATE_COLUMNS: Tuple[str, ...] = _MODEL_COLUMNS[2:] _instances: Dict[str, "PersistentModelCache"] = {} @@ -186,6 +187,7 @@ class PersistentModelCache: "civitai_deleted": bool(row["civitai_deleted"]), "skip_metadata_refresh": bool(row["skip_metadata_refresh"]), "license_flags": int(license_value), + "hash_status": row["hash_status"] or "completed", } raw_data.append(item) @@ -449,6 +451,7 @@ class PersistentModelCache: exclude INTEGER, db_checked INTEGER, last_checked_at REAL, + hash_status TEXT, PRIMARY KEY (model_type, file_path) ); @@ -496,6 +499,7 @@ class PersistentModelCache: "skip_metadata_refresh": "INTEGER DEFAULT 0", # Persisting without explicit flags should assume CivitAI's documented defaults (0b111001 == 57). "license_flags": f"INTEGER DEFAULT {DEFAULT_LICENSE_FLAGS}", + "hash_status": "TEXT DEFAULT 'completed'", } for column, definition in required_columns.items(): @@ -570,6 +574,7 @@ class PersistentModelCache: 1 if item.get("exclude") else 0, 1 if item.get("db_checked") else 0, float(item.get("last_checked_at") or 0.0), + item.get("hash_status", "completed"), ) def _insert_model_sql(self) -> str: