mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
Merge pull request #603 from willmiao/codex/analyze-lora-manager-recipe-detection-issue
fix(recipes): normalize relocated preview paths
This commit is contained in:
@@ -384,16 +384,32 @@ class RecipeScanner:
|
|||||||
|
|
||||||
# Ensure the image file exists
|
# Ensure the image file exists
|
||||||
image_path = recipe_data.get('file_path')
|
image_path = recipe_data.get('file_path')
|
||||||
if not os.path.exists(image_path):
|
normalized_image_path = os.path.normpath(image_path) if image_path else image_path
|
||||||
|
path_updated = False
|
||||||
|
if image_path and normalized_image_path != image_path:
|
||||||
|
recipe_data['file_path'] = normalized_image_path
|
||||||
|
image_path = normalized_image_path
|
||||||
|
path_updated = True
|
||||||
|
|
||||||
|
if image_path and not os.path.exists(image_path):
|
||||||
logger.warning(f"Recipe image not found: {image_path}")
|
logger.warning(f"Recipe image not found: {image_path}")
|
||||||
# Try to find the image in the same directory as the recipe
|
# Try to find the image in the same directory as the recipe
|
||||||
recipe_dir = os.path.dirname(recipe_path)
|
recipe_dir = os.path.dirname(recipe_path)
|
||||||
image_filename = os.path.basename(image_path)
|
image_filename = os.path.basename(image_path)
|
||||||
alternative_path = os.path.join(recipe_dir, image_filename)
|
alternative_path = os.path.join(recipe_dir, image_filename)
|
||||||
if os.path.exists(alternative_path):
|
if os.path.exists(alternative_path):
|
||||||
recipe_data['file_path'] = alternative_path
|
normalized_alternative = os.path.normpath(alternative_path)
|
||||||
|
recipe_data['file_path'] = normalized_alternative
|
||||||
|
image_path = normalized_alternative
|
||||||
|
path_updated = True
|
||||||
|
logger.info(
|
||||||
|
"Updated recipe image path to %s after relocating asset", normalized_alternative
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Could not find alternative image path for {image_path}")
|
logger.warning(f"Could not find alternative image path for {image_path}")
|
||||||
|
|
||||||
|
if path_updated:
|
||||||
|
self._write_recipe_file(recipe_path, recipe_data)
|
||||||
|
|
||||||
# Ensure loras array exists
|
# Ensure loras array exists
|
||||||
if 'loras' not in recipe_data:
|
if 'loras' not in recipe_data:
|
||||||
@@ -413,18 +429,24 @@ class RecipeScanner:
|
|||||||
|
|
||||||
# Write updated recipe data back to file
|
# Write updated recipe data back to file
|
||||||
try:
|
try:
|
||||||
with open(recipe_path, 'w', encoding='utf-8') as f:
|
self._write_recipe_file(recipe_path, recipe_data)
|
||||||
json.dump(recipe_data, f, indent=4, ensure_ascii=False)
|
|
||||||
logger.info(f"Added fingerprint to recipe: {recipe_path}")
|
logger.info(f"Added fingerprint to recipe: {recipe_path}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error writing updated recipe with fingerprint: {e}")
|
logger.error(f"Error writing updated recipe with fingerprint: {e}")
|
||||||
|
|
||||||
return recipe_data
|
return recipe_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error loading recipe file {recipe_path}: {e}")
|
logger.error(f"Error loading recipe file {recipe_path}: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _write_recipe_file(recipe_path: str, recipe_data: Dict[str, Any]) -> None:
|
||||||
|
"""Persist ``recipe_data`` back to ``recipe_path`` with standard formatting."""
|
||||||
|
|
||||||
|
with open(recipe_path, 'w', encoding='utf-8') as file_obj:
|
||||||
|
json.dump(recipe_data, file_obj, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
async def _update_lora_information(self, recipe_data: Dict) -> bool:
|
async def _update_lora_information(self, recipe_data: Dict) -> bool:
|
||||||
"""Update LoRA information with hash and file_name
|
"""Update LoRA information with hash and file_name
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ class RecipePersistenceService:
|
|||||||
)
|
)
|
||||||
image_filename = f"{recipe_id}{extension}"
|
image_filename = f"{recipe_id}{extension}"
|
||||||
image_path = os.path.join(recipes_dir, image_filename)
|
image_path = os.path.join(recipes_dir, image_filename)
|
||||||
with open(image_path, "wb") as file_obj:
|
normalized_image_path = os.path.normpath(image_path)
|
||||||
|
with open(normalized_image_path, "wb") as file_obj:
|
||||||
file_obj.write(optimized_image)
|
file_obj.write(optimized_image)
|
||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
@@ -97,7 +98,7 @@ class RecipePersistenceService:
|
|||||||
fingerprint = calculate_recipe_fingerprint(loras_data)
|
fingerprint = calculate_recipe_fingerprint(loras_data)
|
||||||
recipe_data: Dict[str, Any] = {
|
recipe_data: Dict[str, Any] = {
|
||||||
"id": recipe_id,
|
"id": recipe_id,
|
||||||
"file_path": image_path,
|
"file_path": normalized_image_path,
|
||||||
"title": name,
|
"title": name,
|
||||||
"modified": current_time,
|
"modified": current_time,
|
||||||
"created_date": current_time,
|
"created_date": current_time,
|
||||||
@@ -116,10 +117,11 @@ class RecipePersistenceService:
|
|||||||
|
|
||||||
json_filename = f"{recipe_id}.recipe.json"
|
json_filename = f"{recipe_id}.recipe.json"
|
||||||
json_path = os.path.join(recipes_dir, json_filename)
|
json_path = os.path.join(recipes_dir, json_filename)
|
||||||
|
json_path = os.path.normpath(json_path)
|
||||||
with open(json_path, "w", encoding="utf-8") as file_obj:
|
with open(json_path, "w", encoding="utf-8") as file_obj:
|
||||||
json.dump(recipe_data, file_obj, indent=4, ensure_ascii=False)
|
json.dump(recipe_data, file_obj, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
self._exif_utils.append_recipe_metadata(image_path, recipe_data)
|
self._exif_utils.append_recipe_metadata(normalized_image_path, recipe_data)
|
||||||
|
|
||||||
matching_recipes = await self._find_matching_recipes(recipe_scanner, fingerprint, exclude_id=recipe_id)
|
matching_recipes = await self._find_matching_recipes(recipe_scanner, fingerprint, exclude_id=recipe_id)
|
||||||
await recipe_scanner.add_recipe(recipe_data)
|
await recipe_scanner.add_recipe(recipe_data)
|
||||||
@@ -128,7 +130,7 @@ class RecipePersistenceService:
|
|||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
"recipe_id": recipe_id,
|
"recipe_id": recipe_id,
|
||||||
"image_path": image_path,
|
"image_path": normalized_image_path,
|
||||||
"json_path": json_path,
|
"json_path": json_path,
|
||||||
"matching_recipes": matching_recipes,
|
"matching_recipes": matching_recipes,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
@@ -183,3 +184,35 @@ async def test_update_lora_entry_updates_cache_and_file(tmp_path: Path, recipe_s
|
|||||||
cached_recipe = next(item for item in cache.raw_data if item["id"] == recipe_id)
|
cached_recipe = next(item for item in cache.raw_data if item["id"] == recipe_id)
|
||||||
assert cached_recipe["loras"][0]["hash"] == target_hash
|
assert cached_recipe["loras"][0]["hash"] == target_hash
|
||||||
assert cached_recipe["fingerprint"] == expected_fingerprint
|
assert cached_recipe["fingerprint"] == expected_fingerprint
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_load_recipe_rewrites_missing_image_path(tmp_path: Path, recipe_scanner):
|
||||||
|
scanner, _ = recipe_scanner
|
||||||
|
recipes_dir = Path(config.loras_roots[0]) / "recipes"
|
||||||
|
recipes_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
recipe_id = "moved"
|
||||||
|
old_root = tmp_path / "old_root"
|
||||||
|
old_path = old_root / "recipes" / f"{recipe_id}.webp"
|
||||||
|
recipe_path = recipes_dir / f"{recipe_id}.recipe.json"
|
||||||
|
current_image = recipes_dir / f"{recipe_id}.webp"
|
||||||
|
current_image.write_bytes(b"image-bytes")
|
||||||
|
|
||||||
|
recipe_data = {
|
||||||
|
"id": recipe_id,
|
||||||
|
"file_path": str(old_path),
|
||||||
|
"title": "Relocated",
|
||||||
|
"modified": 0.0,
|
||||||
|
"created_date": 0.0,
|
||||||
|
"loras": [],
|
||||||
|
}
|
||||||
|
recipe_path.write_text(json.dumps(recipe_data))
|
||||||
|
|
||||||
|
loaded = await scanner._load_recipe_file(str(recipe_path))
|
||||||
|
|
||||||
|
expected_path = os.path.normpath(str(current_image))
|
||||||
|
assert loaded["file_path"] == expected_path
|
||||||
|
|
||||||
|
persisted = json.loads(recipe_path.read_text())
|
||||||
|
assert persisted["file_path"] == expected_path
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -148,3 +150,8 @@ async def test_save_recipe_reports_duplicates(tmp_path):
|
|||||||
assert scanner.last_fingerprint is not None
|
assert scanner.last_fingerprint is not None
|
||||||
assert os.path.exists(result.payload["json_path"])
|
assert os.path.exists(result.payload["json_path"])
|
||||||
assert scanner._cache.raw_data
|
assert scanner._cache.raw_data
|
||||||
|
|
||||||
|
stored = json.loads(Path(result.payload["json_path"]).read_text())
|
||||||
|
expected_image_path = os.path.normpath(result.payload["image_path"])
|
||||||
|
assert stored["file_path"] == expected_image_path
|
||||||
|
assert service._exif_utils.appended[0] == expected_image_path
|
||||||
|
|||||||
Reference in New Issue
Block a user