| 1 | import asyncio |
| 2 | import json |
| 3 | from datetime import datetime |
| 4 | from pathlib import Path |
| 5 | from typing import Any, Dict |
| 6 | |
| 7 | import aiofiles |
| 8 | |
| 9 | from utils.logger import setup_logger |
| 10 | |
| 11 | logger = setup_logger("MetadataHandler") |
| 12 | |
| 13 | |
| 14 | class MetadataHandler: |
| 15 | def __init__(self): |
| 16 | self._manifest_lock = asyncio.Lock() |
| 17 | |
| 18 | async def save_metadata(self, data: Dict[str, Any], save_path: Path) -> bool: |
| 19 | try: |
| 20 | async with aiofiles.open(save_path, "w", encoding="utf-8") as f: |
| 21 | await f.write(json.dumps(data, ensure_ascii=False, indent=2)) |
| 22 | return True |
| 23 | except Exception as e: |
| 24 | logger.error("Failed to save metadata: %s, error: %s", save_path, e) |
| 25 | return False |
| 26 | |
| 27 | async def append_download_manifest(self, base_path: Path, record: Dict[str, Any]) -> bool: |
| 28 | manifest_path = base_path / "download_manifest.jsonl" |
| 29 | normalized_record = { |
| 30 | "recorded_at": datetime.now().isoformat(timespec="seconds"), |
| 31 | **record, |
| 32 | } |
| 33 | |
| 34 | try: |
| 35 | async with self._manifest_lock: |
| 36 | async with aiofiles.open(manifest_path, "a", encoding="utf-8") as f: |
| 37 | await f.write(json.dumps(normalized_record, ensure_ascii=False)) |
| 38 | await f.write("\n") |
| 39 | return True |
| 40 | except Exception as e: |
| 41 | logger.error("Failed to append download manifest: %s, error: %s", manifest_path, e) |
| 42 | return False |
| 43 | |
| 44 | async def load_metadata(self, file_path: Path) -> Dict[str, Any]: |
| 45 | try: |
| 46 | async with aiofiles.open(file_path, "r", encoding="utf-8") as f: |
| 47 | content = await f.read() |
| 48 | return json.loads(content) |
| 49 | except Exception as e: |
| 50 | logger.error("Failed to load metadata: %s, error: %s", file_path, e) |
| 51 | return {} |
| 52 |