2026-05-07 17:15:56 +03:00
|
|
|
|
"""
|
|
|
|
|
|
FileService - сервис для работы с файлами
|
|
|
|
|
|
"""
|
|
|
|
|
|
import shutil
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FileService:
|
|
|
|
|
|
"""Сервис для перемещения файлов и ведения логов"""
|
|
|
|
|
|
|
|
|
|
|
|
SUPPORTED_EXTENSIONS = {'.cr2', '.dng', '.arw', '.jpg', '.jpeg', '.png', '.raw', '.tiff'}
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def is_photo(cls, file_path: Path) -> bool:
|
|
|
|
|
|
"""Проверяет, является ли файл фотографией"""
|
|
|
|
|
|
return file_path.suffix.lower() in cls.SUPPORTED_EXTENSIONS
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def resolve_conflict(cls, target_path: Path) -> Path:
|
|
|
|
|
|
"""Разрешает конфликт имён файлов"""
|
|
|
|
|
|
if not target_path.exists():
|
|
|
|
|
|
return target_path
|
|
|
|
|
|
|
|
|
|
|
|
counter = 1
|
|
|
|
|
|
stem = target_path.stem
|
|
|
|
|
|
suffix = target_path.suffix
|
|
|
|
|
|
parent = target_path.parent
|
|
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
|
new_name = f"{stem}_{counter}{suffix}"
|
|
|
|
|
|
new_path = parent / new_name
|
|
|
|
|
|
if not new_path.exists():
|
|
|
|
|
|
return new_path
|
|
|
|
|
|
counter += 1
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def write_object_log(cls, folder: Path, filename: str, camera: str, optics: str,
|
|
|
|
|
|
timestamp: Optional[datetime] = None) -> None:
|
|
|
|
|
|
"""Записывает запись в лог объекта"""
|
|
|
|
|
|
if timestamp is None:
|
|
|
|
|
|
timestamp = datetime.now()
|
|
|
|
|
|
|
|
|
|
|
|
log_file = folder / "ObjectLog.txt"
|
|
|
|
|
|
line = f"{timestamp} {camera if camera else 'Unknown'} {optics if optics else 'Unknown'} - {filename}\n"
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(log_file, 'a', encoding='utf-8') as f:
|
|
|
|
|
|
f.write(line)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Ошибка записи лога: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def move_file(cls, source: Path, target_folder: Path, camera: str, optics: str) -> bool:
|
|
|
|
|
|
"""Перемещает файл в целевую папку"""
|
|
|
|
|
|
if not source.exists():
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
if not cls.is_photo(source):
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
target_folder.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
creation_time = datetime.fromtimestamp(source.stat().st_ctime)
|
|
|
|
|
|
cls.write_object_log(target_folder, source.name, camera, optics, creation_time)
|
|
|
|
|
|
target_path = cls.resolve_conflict(target_folder / source.name)
|
|
|
|
|
|
shutil.move(str(source), str(target_path))
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Ошибка перемещения {source.name}: {e}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def clear_watch_folder(cls, folder: Path) -> int:
|
|
|
|
|
|
"""Очищает папку наблюдения"""
|
|
|
|
|
|
if not folder.exists():
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
count = 0
|
|
|
|
|
|
for file_path in folder.iterdir():
|
|
|
|
|
|
if file_path.is_file() and cls.is_photo(file_path):
|
|
|
|
|
|
try:
|
|
|
|
|
|
file_path.unlink()
|
|
|
|
|
|
count += 1
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Ошибка удаления {file_path.name}: {e}")
|
|
|
|
|
|
return count
|