128 lines
5.0 KiB
Python
128 lines
5.0 KiB
Python
"""Model upload module, handling hunyuan3 model file upload and caching logic"""
|
|
|
|
from pathlib import Path
|
|
from typing import Dict, Optional, Tuple
|
|
import json
|
|
import logging
|
|
import time
|
|
import traceback
|
|
|
|
from ..upload.upload import upload_file
|
|
|
|
logger = logging.getLogger("LuxRealEngine")
|
|
|
|
# Upload cache validity period: 2 days (seconds)
|
|
CACHE_TTL_SECONDS = 2 * 24 * 60 * 60
|
|
# Key for "by path" mapping in cache JSON
|
|
CACHE_KEY_BY_PATH = "by_path"
|
|
|
|
|
|
def upload_models_with_cache(
|
|
file_paths: Dict[str, Optional[str]],
|
|
upload_cache: str,
|
|
base_api_path: str,
|
|
lux3d_api_key: Optional[str] = None,
|
|
) -> Tuple[Dict[str, str], str]:
|
|
"""
|
|
Handle model upload with caching support to avoid duplicate uploads.
|
|
Cache is stored by path, same file can be reused regardless of slot position or deletion and re-addition, no need to re-upload.
|
|
Cache validity period is 2 days, will re-upload after expiration.
|
|
|
|
Args:
|
|
file_paths: Mapping from field name to local path, e.g. {"file_input_1": "/path/to/file"}
|
|
upload_cache: JSON formatted cache string, format: {"by_path": {"path": {"url": "...", "ts": timestamp}, ...}}
|
|
base_api_path: API base path
|
|
lux3d_api_key: Invitation code (optional), used for OpenAPI signature
|
|
|
|
Returns:
|
|
Tuple[uploaded_urls, new_cache_json]:
|
|
- uploaded_urls: Mapping from field name to uploaded URL
|
|
- new_cache_json: Updated cache JSON string (stored by path, includes timestamp)
|
|
"""
|
|
now = time.time()
|
|
|
|
def _is_valid_entry(entry) -> Optional[str]:
|
|
"""Extract url from cache entry, return None if expired or incorrect format."""
|
|
if isinstance(entry, dict) and "url" in entry and "ts" in entry:
|
|
if (now - entry["ts"]) < CACHE_TTL_SECONDS:
|
|
return entry["url"]
|
|
return None
|
|
|
|
# Parse upload cache: by path -> {url, ts}
|
|
path_cache = {}
|
|
if upload_cache and upload_cache != "{}":
|
|
try:
|
|
raw = json.loads(upload_cache)
|
|
if isinstance(raw.get(CACHE_KEY_BY_PATH), dict):
|
|
for path, val in raw[CACHE_KEY_BY_PATH].items():
|
|
url = _is_valid_entry(val)
|
|
if url:
|
|
path_cache[path] = {"url": url, "ts": val["ts"]}
|
|
except (json.JSONDecodeError, TypeError):
|
|
logger.warning(f"Failed to parse upload_cache: {upload_cache}")
|
|
path_cache = {}
|
|
|
|
uploaded_urls = {}
|
|
|
|
for field_name, current_path in file_paths.items():
|
|
if not current_path:
|
|
continue
|
|
|
|
cached_url = _is_valid_entry(path_cache.get(current_path))
|
|
|
|
if cached_url:
|
|
logger.info(f">>> Skip upload for {field_name}, using cached URL {cached_url}")
|
|
uploaded_url = cached_url
|
|
else:
|
|
logger.info(f">>> Uploading {field_name}: {current_path}")
|
|
try:
|
|
file_path = Path(current_path)
|
|
if not file_path.exists():
|
|
logger.error(f"File not found: {current_path}")
|
|
continue
|
|
|
|
result = upload_file(
|
|
guid="comfyui-model-upload",
|
|
file_path=file_path,
|
|
base_api_path=base_api_path,
|
|
lux3d_api_key=lux3d_api_key,
|
|
on_progress=lambda current, total: logger.debug(
|
|
f"Upload progress: {current}/{total} ({current*100//total}%)"
|
|
)
|
|
)
|
|
|
|
# Extract url from response result
|
|
uploaded_url = result.get("d", {}).get("url")
|
|
if not uploaded_url:
|
|
logger.error(f"Upload failed for {field_name}, no URL in response: {result}")
|
|
continue
|
|
|
|
logger.info(f">>> Upload success for {field_name}: {uploaded_url}")
|
|
path_cache[current_path] = {"url": uploaded_url, "ts": time.time()}
|
|
|
|
except Exception as e:
|
|
logger.error(f">>> Upload failed for {field_name}: {current_path}")
|
|
logger.error(f">>> Error Type: {type(e).__name__}")
|
|
logger.error(f">>> Error Message: {str(e)}")
|
|
|
|
# If it's UploadError, record detailed information
|
|
if hasattr(e, 'code') and hasattr(e, 'message'):
|
|
logger.error(f">>> Error Code: {e.code}")
|
|
logger.error(f">>> Error Message: {e.message}")
|
|
if hasattr(e, 'extra') and e.extra:
|
|
logger.error(f">>> Error Extra Info: {json.dumps(e.extra, ensure_ascii=False)}")
|
|
|
|
# Record stack trace
|
|
logger.error(f">>> Traceback:\n{traceback.format_exc()}")
|
|
continue
|
|
|
|
uploaded_urls[field_name] = uploaded_url
|
|
|
|
# Only persist unexpired entries
|
|
by_path_out = {
|
|
path: val
|
|
for path, val in path_cache.items()
|
|
if isinstance(val, dict) and "ts" in val and (now - val["ts"]) < CACHE_TTL_SECONDS
|
|
}
|
|
return uploaded_urls, json.dumps({CACHE_KEY_BY_PATH: by_path_out})
|