feat: Remove backup creation from metadata saving functions for streamlined operations

This commit is contained in:
Will Miao
2025-08-24 22:30:53 +08:00
parent 3079131337
commit 623c28bfc3
5 changed files with 39 additions and 82 deletions

View File

@@ -348,7 +348,7 @@ class LoraRoutes(BaseModelRoutes):
# Store creator in the civitai nested structure # Store creator in the civitai nested structure
metadata['civitai']['creator'] = creator metadata['civitai']['creator'] = creator
await MetadataManager.save_metadata(file_path, metadata, True) await MetadataManager.save_metadata(file_path, metadata)
except Exception as e: except Exception as e:
logger.error(f"Error saving model metadata: {e}") logger.error(f"Error saving model metadata: {e}")

View File

@@ -491,7 +491,7 @@ class DownloadManager:
metadata.update_file_info(save_path) metadata.update_file_info(save_path)
# 5. Final metadata update # 5. Final metadata update
await MetadataManager.save_metadata(save_path, metadata, True) await MetadataManager.save_metadata(save_path, metadata)
# 6. Update cache based on model type # 6. Update cache based on model type
if model_type == "checkpoint": if model_type == "checkpoint":

View File

@@ -585,6 +585,7 @@ class ModelScanner:
if entry.is_file(follow_symlinks=True) and any(entry.name.endswith(ext) for ext in self.file_extensions): if entry.is_file(follow_symlinks=True) and any(entry.name.endswith(ext) for ext in self.file_extensions):
file_path = entry.path.replace(os.sep, "/") file_path = entry.path.replace(os.sep, "/")
result = await self._process_model_file(file_path, original_root) result = await self._process_model_file(file_path, original_root)
# Only add to models if result is not None (skip corrupted metadata)
if result: if result:
models.append(result) models.append(result)
await asyncio.sleep(0) await asyncio.sleep(0)
@@ -624,7 +625,12 @@ class ModelScanner:
async def _process_model_file(self, file_path: str, root_path: str) -> Dict: async def _process_model_file(self, file_path: str, root_path: str) -> Dict:
"""Process a single model file and return its metadata""" """Process a single model file and return its metadata"""
metadata = await MetadataManager.load_metadata(file_path, self.model_class) metadata, should_skip = await MetadataManager.load_metadata(file_path, self.model_class)
if should_skip:
# Metadata file exists but cannot be parsed - skip this model
logger.warning(f"Skipping model {file_path} due to corrupted metadata file")
return None
if metadata is None: if metadata is None:
civitai_info_path = f"{os.path.splitext(file_path)[0]}.civitai.info" civitai_info_path = f"{os.path.splitext(file_path)[0]}.civitai.info"
@@ -640,7 +646,7 @@ class ModelScanner:
metadata = self.model_class.from_civitai_info(version_info, file_info, file_path) metadata = self.model_class.from_civitai_info(version_info, file_info, file_path)
metadata.preview_url = find_preview_file(file_name, os.path.dirname(file_path)) metadata.preview_url = find_preview_file(file_name, os.path.dirname(file_path))
await MetadataManager.save_metadata(file_path, metadata, True) await MetadataManager.save_metadata(file_path, metadata)
logger.debug(f"Created metadata from .civitai.info for {file_path}") logger.debug(f"Created metadata from .civitai.info for {file_path}")
except Exception as e: except Exception as e:
logger.error(f"Error creating metadata from .civitai.info for {file_path}: {e}") logger.error(f"Error creating metadata from .civitai.info for {file_path}: {e}")
@@ -667,7 +673,7 @@ class ModelScanner:
metadata.modelDescription = version_info['model']['description'] metadata.modelDescription = version_info['model']['description']
# Save the updated metadata # Save the updated metadata
await MetadataManager.save_metadata(file_path, metadata, True) await MetadataManager.save_metadata(file_path, metadata)
logger.debug(f"Updated metadata with civitai info for {file_path}") logger.debug(f"Updated metadata with civitai info for {file_path}")
except Exception as e: except Exception as e:
logger.error(f"Error restoring civitai data from .civitai.info for {file_path}: {e}") logger.error(f"Error restoring civitai data from .civitai.info for {file_path}: {e}")
@@ -747,7 +753,7 @@ class ModelScanner:
model_data['civitai']['creator'] = model_metadata['creator'] model_data['civitai']['creator'] = model_metadata['creator']
await MetadataManager.save_metadata(file_path, model_data, True) await MetadataManager.save_metadata(file_path, model_data)
except Exception as e: except Exception as e:
logger.error(f"Failed to update metadata from Civitai for {file_path}: {e}") logger.error(f"Failed to update metadata from Civitai for {file_path}: {e}")

View File

@@ -1,7 +1,6 @@
from datetime import datetime from datetime import datetime
import os import os
import json import json
import shutil
import logging import logging
from typing import Dict, Optional, Type, Union from typing import Dict, Optional, Type, Union
@@ -17,7 +16,7 @@ class MetadataManager:
This class is responsible for: This class is responsible for:
1. Loading metadata safely with fallback mechanisms 1. Loading metadata safely with fallback mechanisms
2. Saving metadata with atomic operations and backups 2. Saving metadata with atomic operations
3. Creating default metadata for models 3. Creating default metadata for models
4. Handling unknown fields gracefully 4. Handling unknown fields gracefully
""" """
@@ -25,81 +24,44 @@ class MetadataManager:
@staticmethod @staticmethod
async def load_metadata(file_path: str, model_class: Type[BaseModelMetadata] = LoraMetadata) -> Optional[BaseModelMetadata]: async def load_metadata(file_path: str, model_class: Type[BaseModelMetadata] = LoraMetadata) -> Optional[BaseModelMetadata]:
""" """
Load metadata with robust error handling and data preservation. Load metadata safely.
Args:
file_path: Path to the model file
model_class: Class to instantiate (LoraMetadata, CheckpointMetadata, etc.)
Returns: Returns:
BaseModelMetadata instance or None if file doesn't exist tuple: (metadata, should_skip)
- metadata: BaseModelMetadata instance or None
- should_skip: True if corrupted metadata file exists and model should be skipped
""" """
metadata_path = f"{os.path.splitext(file_path)[0]}.metadata.json" metadata_path = f"{os.path.splitext(file_path)[0]}.metadata.json"
backup_path = f"{metadata_path}.bak"
# Try loading the main metadata file # Check if metadata file exists
if os.path.exists(metadata_path): if not os.path.exists(metadata_path):
try: return None, False
with open(metadata_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# Create model instance
metadata = model_class.from_dict(data)
# Normalize paths
await MetadataManager._normalize_metadata_paths(metadata, file_path)
return metadata
except json.JSONDecodeError:
# JSON parsing error - try to restore from backup
logger.warning(f"Invalid JSON in metadata file: {metadata_path}")
return await MetadataManager._restore_from_backup(backup_path, file_path, model_class)
except Exception as e:
# Other errors might be due to unknown fields or schema changes
logger.error(f"Error loading metadata from {metadata_path}: {str(e)}")
return await MetadataManager._restore_from_backup(backup_path, file_path, model_class)
return None try:
with open(metadata_path, 'r', encoding='utf-8') as f:
@staticmethod data = json.load(f)
async def _restore_from_backup(backup_path: str, file_path: str, model_class: Type[BaseModelMetadata]) -> Optional[BaseModelMetadata]:
"""
Try to restore metadata from backup file
Args:
backup_path: Path to backup file
file_path: Path to the original model file
model_class: Class to instantiate
Returns: # Create model instance
BaseModelMetadata instance or None if restoration fails metadata = model_class.from_dict(data)
"""
if os.path.exists(backup_path):
try:
logger.info(f"Attempting to restore metadata from backup: {backup_path}")
with open(backup_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# Process data similarly to normal loading # Normalize paths
metadata = model_class.from_dict(data) await MetadataManager._normalize_metadata_paths(metadata, file_path)
await MetadataManager._normalize_metadata_paths(metadata, file_path)
return metadata return metadata, False
except Exception as e:
logger.error(f"Failed to restore from backup: {str(e)}") except (json.JSONDecodeError, Exception) as e:
error_type = "Invalid JSON" if isinstance(e, json.JSONDecodeError) else "Parse error"
return None logger.error(f"{error_type} in metadata file: {metadata_path}. Error: {str(e)}. Skipping model to preserve existing data.")
return None, True # should_skip = True
@staticmethod @staticmethod
async def save_metadata(path: str, metadata: Union[BaseModelMetadata, Dict], create_backup: bool = False) -> bool: async def save_metadata(path: str, metadata: Union[BaseModelMetadata, Dict]) -> bool:
""" """
Save metadata with atomic write operations and backup creation. Save metadata with atomic write operations.
Args: Args:
path: Path to the model file or directly to the metadata file path: Path to the model file or directly to the metadata file
metadata: Metadata to save (either BaseModelMetadata object or dict) metadata: Metadata to save (either BaseModelMetadata object or dict)
create_backup: Whether to create a new backup of existing file if a backup doesn't already exist
Returns: Returns:
bool: Success or failure bool: Success or failure
@@ -112,19 +74,8 @@ class MetadataManager:
file_path = path file_path = path
metadata_path = f"{os.path.splitext(file_path)[0]}.metadata.json" metadata_path = f"{os.path.splitext(file_path)[0]}.metadata.json"
temp_path = f"{metadata_path}.tmp" temp_path = f"{metadata_path}.tmp"
backup_path = f"{metadata_path}.bak"
try: try:
# Create backup if file exists and either:
# 1. create_backup is True, OR
# 2. backup file doesn't already exist
if os.path.exists(metadata_path) and (create_backup or not os.path.exists(backup_path)):
try:
shutil.copy2(metadata_path, backup_path)
logger.debug(f"Created metadata backup at: {backup_path}")
except Exception as e:
logger.warning(f"Failed to create metadata backup: {str(e)}")
# Convert to dict if needed # Convert to dict if needed
if isinstance(metadata, BaseModelMetadata): if isinstance(metadata, BaseModelMetadata):
metadata_dict = metadata.to_dict() metadata_dict = metadata.to_dict()
@@ -240,7 +191,7 @@ class MetadataManager:
# await MetadataManager._enrich_metadata(metadata, real_path) # await MetadataManager._enrich_metadata(metadata, real_path)
# Save the created metadata # Save the created metadata
await MetadataManager.save_metadata(file_path, metadata, create_backup=False) await MetadataManager.save_metadata(file_path, metadata)
return metadata return metadata
@@ -310,4 +261,4 @@ class MetadataManager:
# If path attributes were changed, save the metadata back to disk # If path attributes were changed, save the metadata back to disk
if need_update: if need_update:
await MetadataManager.save_metadata(file_path, metadata, create_backup=False) await MetadataManager.save_metadata(file_path, metadata)

View File

@@ -156,7 +156,7 @@ class ModelRouteUtils:
local_metadata['preview_nsfw_level'] = first_preview.get('nsfwLevel', 0) local_metadata['preview_nsfw_level'] = first_preview.get('nsfwLevel', 0)
# Save updated metadata # Save updated metadata
await MetadataManager.save_metadata(metadata_path, local_metadata, True) await MetadataManager.save_metadata(metadata_path, local_metadata)
@staticmethod @staticmethod
async def fetch_and_update_model( async def fetch_and_update_model(