162 lines
5.7 KiB
Python
162 lines
5.7 KiB
Python
|
|
"""
|
||
|
|
File Manager for DS_L10N
|
||
|
|
파일 자동 관리 및 정리
|
||
|
|
"""
|
||
|
|
import shutil
|
||
|
|
from pathlib import Path
|
||
|
|
from datetime import datetime, timedelta
|
||
|
|
from typing import List, Tuple
|
||
|
|
|
||
|
|
|
||
|
|
class FileManager:
|
||
|
|
"""파일 관리자"""
|
||
|
|
|
||
|
|
def __init__(self, config: dict, logger):
|
||
|
|
self.config = config
|
||
|
|
self.logger = logger
|
||
|
|
self.cleanup_config = config.get('cleanup', {})
|
||
|
|
|
||
|
|
def cleanup_old_files(self, target_dir: Path) -> Tuple[int, int]:
|
||
|
|
"""
|
||
|
|
오래된 파일 정리
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
(archived_count, deleted_count)
|
||
|
|
"""
|
||
|
|
if not self.cleanup_config.get('auto_archive', True):
|
||
|
|
self.logger.info('자동 정리가 비활성화되어 있습니다.')
|
||
|
|
return 0, 0
|
||
|
|
|
||
|
|
keep_recent = self.cleanup_config.get('keep_recent_files', 5)
|
||
|
|
archive_patterns = self.cleanup_config.get('archive_patterns', [])
|
||
|
|
archive_dir = Path(self.config.get('paths', {}).get('archive_dir', './archive'))
|
||
|
|
|
||
|
|
archived_count = 0
|
||
|
|
|
||
|
|
self.logger.info(f'파일 정리 시작: {target_dir}')
|
||
|
|
self.logger.info(f'최근 {keep_recent}개 파일 유지, 나머지는 보관')
|
||
|
|
|
||
|
|
for pattern in archive_patterns:
|
||
|
|
files = sorted(target_dir.glob(pattern), key=lambda p: p.stat().st_mtime, reverse=True)
|
||
|
|
|
||
|
|
if len(files) <= keep_recent:
|
||
|
|
continue
|
||
|
|
|
||
|
|
# 오래된 파일들
|
||
|
|
old_files = files[keep_recent:]
|
||
|
|
|
||
|
|
for file_path in old_files:
|
||
|
|
archived = self._archive_file(file_path, archive_dir)
|
||
|
|
if archived:
|
||
|
|
archived_count += 1
|
||
|
|
|
||
|
|
# 오래된 로그 파일 삭제
|
||
|
|
logs_dir = Path(self.config.get('paths', {}).get('logs_dir', './logs'))
|
||
|
|
deleted_count = self._delete_old_logs(logs_dir)
|
||
|
|
|
||
|
|
if archived_count > 0:
|
||
|
|
self.logger.success(f'✅ {archived_count}개 파일 보관 완료')
|
||
|
|
|
||
|
|
if deleted_count > 0:
|
||
|
|
self.logger.success(f'✅ {deleted_count}개 오래된 로그 파일 삭제')
|
||
|
|
|
||
|
|
return archived_count, deleted_count
|
||
|
|
|
||
|
|
def _archive_file(self, file_path: Path, archive_dir: Path) -> bool:
|
||
|
|
"""파일을 archive 폴더로 이동"""
|
||
|
|
try:
|
||
|
|
archive_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
# 날짜별 하위 폴더
|
||
|
|
date_folder = archive_dir / datetime.now().strftime('%Y%m')
|
||
|
|
date_folder.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
# 대상 경로
|
||
|
|
dest_path = date_folder / file_path.name
|
||
|
|
|
||
|
|
# 이미 존재하면 타임스탬프 추가
|
||
|
|
if dest_path.exists():
|
||
|
|
timestamp = datetime.now().strftime('%H%M%S')
|
||
|
|
dest_path = date_folder / f"{file_path.stem}_{timestamp}{file_path.suffix}"
|
||
|
|
|
||
|
|
shutil.move(str(file_path), str(dest_path))
|
||
|
|
self.logger.info(f' 📦 보관: {file_path.name} → {dest_path.relative_to(archive_dir)}')
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
self.logger.warning(f'파일 보관 실패: {file_path.name} - {e}')
|
||
|
|
return False
|
||
|
|
|
||
|
|
def _delete_old_logs(self, logs_dir: Path) -> int:
|
||
|
|
"""오래된 로그 파일 삭제"""
|
||
|
|
if not logs_dir.exists():
|
||
|
|
return 0
|
||
|
|
|
||
|
|
delete_days = self.cleanup_config.get('delete_old_logs_days', 30)
|
||
|
|
cutoff_date = datetime.now() - timedelta(days=delete_days)
|
||
|
|
|
||
|
|
deleted_count = 0
|
||
|
|
|
||
|
|
for log_file in logs_dir.glob('*.log'):
|
||
|
|
try:
|
||
|
|
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
|
||
|
|
|
||
|
|
if mtime < cutoff_date:
|
||
|
|
log_file.unlink()
|
||
|
|
self.logger.debug(f' 🗑️ 삭제: {log_file.name}')
|
||
|
|
deleted_count += 1
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
self.logger.warning(f'로그 파일 삭제 실패: {log_file.name} - {e}')
|
||
|
|
|
||
|
|
return deleted_count
|
||
|
|
|
||
|
|
def delete_old_backups(self, localization_root: Path) -> int:
|
||
|
|
"""오래된 백업 파일 삭제"""
|
||
|
|
keep_days = self.config.get('backup', {}).get('keep_backups_days', 7)
|
||
|
|
cutoff_date = datetime.now() - timedelta(days=keep_days)
|
||
|
|
|
||
|
|
deleted_count = 0
|
||
|
|
|
||
|
|
self.logger.info(f'백업 파일 정리 중 ({keep_days}일 이상 된 파일 삭제)...')
|
||
|
|
|
||
|
|
for lang_folder in localization_root.iterdir():
|
||
|
|
if not lang_folder.is_dir():
|
||
|
|
continue
|
||
|
|
|
||
|
|
for backup_file in lang_folder.glob('*.backup_*.po'):
|
||
|
|
try:
|
||
|
|
mtime = datetime.fromtimestamp(backup_file.stat().st_mtime)
|
||
|
|
|
||
|
|
if mtime < cutoff_date:
|
||
|
|
backup_file.unlink()
|
||
|
|
self.logger.debug(f' 🗑️ 백업 삭제: {backup_file.name}')
|
||
|
|
deleted_count += 1
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
self.logger.warning(f'백업 파일 삭제 실패: {backup_file.name} - {e}')
|
||
|
|
|
||
|
|
if deleted_count > 0:
|
||
|
|
self.logger.success(f'✅ {deleted_count}개 백업 파일 삭제 완료')
|
||
|
|
|
||
|
|
return deleted_count
|
||
|
|
|
||
|
|
def ensure_directories(self):
|
||
|
|
"""필요한 디렉토리 생성"""
|
||
|
|
paths = [
|
||
|
|
Path(self.config.get('paths', {}).get('output_dir', './output')),
|
||
|
|
Path(self.config.get('paths', {}).get('logs_dir', './logs')),
|
||
|
|
Path(self.config.get('paths', {}).get('archive_dir', './archive')),
|
||
|
|
Path(self.config.get('paths', {}).get('temp_dir', './temp')),
|
||
|
|
]
|
||
|
|
|
||
|
|
for path in paths:
|
||
|
|
if not path.is_absolute():
|
||
|
|
# 상대 경로를 절대 경로로 변환
|
||
|
|
base_dir = Path(__file__).parent.parent
|
||
|
|
path = (base_dir / path).resolve()
|
||
|
|
|
||
|
|
path.mkdir(parents=True, exist_ok=True)
|
||
|
|
self.logger.debug(f'디렉토리 확인: {path}')
|