260705_2
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""B03 파일 입력 저장 엔진."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import UploadFile
|
||||
|
||||
from B03_FileInput.B03_FileInput_Schema import FileUploadDescriptor
|
||||
from common_util.common_util_storage import get_project_stage_path
|
||||
from config.config_system import UPLOAD_CHUNK_SIZE_BYTES, UPLOAD_MAX_MB
|
||||
|
||||
|
||||
def resolve_upload_destination(
|
||||
project_root: str | Path,
|
||||
descriptor: FileUploadDescriptor,
|
||||
) -> Path:
|
||||
"""검증된 파일의 B03 입력 저장 경로를 생성해 반환한다."""
|
||||
stage_root = Path(get_project_stage_path(str(project_root), "B03_FileInput")).resolve()
|
||||
file_type = Path(descriptor.original_filename).suffix.lower().lstrip(".")
|
||||
destination = (stage_root / "input" / file_type / descriptor.original_filename).resolve()
|
||||
|
||||
if os.path.commonpath((stage_root, destination)) != str(stage_root):
|
||||
raise ValueError("업로드 저장 경로가 B03 단계 폴더를 벗어났습니다.")
|
||||
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
return destination
|
||||
|
||||
|
||||
async def save_upload_stream(upload: UploadFile, destination: Path) -> int:
|
||||
"""업로드 스트림을 크기 제한 내에서 임시 파일에 기록한 뒤 교체한다."""
|
||||
maximum_bytes = UPLOAD_MAX_MB * 1024 * 1024
|
||||
written_bytes = 0
|
||||
temporary_path: Path | None = None
|
||||
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="wb",
|
||||
dir=destination.parent,
|
||||
prefix=f".{destination.name}.",
|
||||
suffix=".upload",
|
||||
delete=False,
|
||||
) as temporary:
|
||||
temporary_path = Path(temporary.name)
|
||||
while chunk := await upload.read(UPLOAD_CHUNK_SIZE_BYTES):
|
||||
written_bytes += len(chunk)
|
||||
if written_bytes > maximum_bytes:
|
||||
raise ValueError(f"파일 크기는 {UPLOAD_MAX_MB}MB를 초과할 수 없습니다.")
|
||||
temporary.write(chunk)
|
||||
temporary.flush()
|
||||
os.fsync(temporary.fileno())
|
||||
|
||||
if written_bytes == 0:
|
||||
raise ValueError("빈 파일은 업로드할 수 없습니다.")
|
||||
os.replace(temporary_path, destination)
|
||||
temporary_path = None
|
||||
return written_bytes
|
||||
finally:
|
||||
if temporary_path is not None:
|
||||
temporary_path.unlink(missing_ok=True)
|
||||
Reference in New Issue
Block a user