"""LocalBackend — 현행 NAS NFS(volume4) 마운트. /file 동작 불변 (plan D-1).""" from __future__ import annotations import os from collections.abc import AsyncIterator from pathlib import Path from .base import StatResult, StorageBackend _STREAM_CHUNK = 256 * 1024 class LocalBackend(StorageBackend): """루트(=settings.nas_mount_path) 하위 상대경로를 로컬 파일시스템으로 해석.""" is_local = True def __init__(self, root: str) -> None: self._root = Path(root) def local_path(self, rel_path: str) -> os.PathLike[str]: return self._root / rel_path async def stat(self, rel_path: str) -> StatResult: p = self._root / rel_path if not p.exists(): return StatResult(exists=False, size=0) return StatResult(exists=True, size=p.stat().st_size) async def stream( self, rel_path: str, *, start: int | None = None, end: int | None = None ) -> AsyncIterator[bytes]: """로컬 파일을 청크 stream (Range 지원). /file 의 로컬 경로는 FileResponse 가 Range 를 자동 처리하므로 이 메서드는 인터페이스 대칭/원격 동등성을 위한 구현.""" p = self._root / rel_path with p.open("rb") as f: if start: f.seek(start) remaining = None if end is None else (end - (start or 0) + 1) while True: to_read = _STREAM_CHUNK if remaining is None else min(_STREAM_CHUNK, remaining) if to_read <= 0: break data = f.read(to_read) if not data: break yield data if remaining is not None: remaining -= len(data)