"""임시 보관함 만료 정리. 라이다 원본은 수십 GB라 방치하면 디스크를 금방 채운다. 파일이 다 올라온 시각 기준 `TEMP_UPLOAD_RETENTION_DAYS`가 지난 묶음을 주기적으로 지운다(2026-08-08 사용자 지시). 보관 기간·주기는 `config/config_system.py`에서 조정한다. """ import asyncio import logging import os import shutil from B03_FileInput.B03_FileInput_Repository_Temp import ( delete_temp_batch, list_expired_temp_batches, ) from common_util.common_util_storage import resolve_temp_batch_path, temp_upload_root from config.config_db import get_db_pool from config.config_system import ( TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS, TEMP_UPLOAD_RETENTION_DAYS, ) logger = logging.getLogger(__name__) async def cleanup_expired_temp_uploads() -> int: """만료된 보관함 묶음을 폴더째 지우고 DB 행도 제거한다. 지운 개수를 돌려준다.""" pool = get_db_pool() removed = 0 try: async with pool.acquire() as connection: expired = await list_expired_temp_batches(connection) for batch in expired: batch_id = str(batch["id"]) user_id = int(batch["user_id"]) try: batch_root = resolve_temp_batch_path(user_id, batch_id, create=False) shutil.rmtree(batch_root, ignore_errors=True) except ValueError: logger.warning("보관함 경로 해석 실패(행만 삭제): batch_id=%s", batch_id) await delete_temp_batch(connection, batch_id=batch_id, user_id=user_id) removed += 1 logger.info( "임시 보관함 만료 삭제: batch_id=%s name=%s expires_at=%s", batch_id, batch.get("name"), batch.get("expires_at"), ) if removed: await connection.commit() _remove_empty_user_dirs() except Exception: logger.exception("임시 보관함 만료 정리 실패") return removed def _remove_empty_user_dirs() -> None: """묶음이 모두 사라진 사용자 폴더는 함께 정리한다.""" root = temp_upload_root() if not os.path.isdir(root): return for entry in os.listdir(root): path = os.path.join(root, entry) if os.path.isdir(path) and not os.listdir(path): try: os.rmdir(path) except OSError: pass async def cleanup_expired_temp_uploads_loop() -> None: """서버 시작 직후 한 번, 이후 설정된 주기마다 만료분을 정리한다.""" interval_seconds = max(1, TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS) * 3600 logger.info( "임시 보관함 정리 루프 시작: 보관 %d일, 주기 %d시간", TEMP_UPLOAD_RETENTION_DAYS, TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS, ) while True: removed = await cleanup_expired_temp_uploads() if removed: logger.info("임시 보관함 정리 완료: %d건 삭제", removed) await asyncio.sleep(interval_seconds)