- docker-compose.yml: профили docs (Nextcloud), office (ONLYOFFICE Document Server), ai (Ollama + скачивание модели) - автонастройка: адреса Nextcloud, ONLYOFFICE, портала и модели подставляются из .env или по именам сервисов во внутренней сети Docker; значения из интерфейса важнее - локальная модель в своей сети (имя сервиса, частный IP) подключается без ключа API - .env.example, docs/DEPLOY.md (раздел 7а), README
64 lines
2.6 KiB
Python
64 lines
2.6 KiB
Python
"""Самоочистка сервера от старых фото нарядов и заданий.
|
|
|
|
Фото, которые нужно хранить дольше (у пункта чек-листа или задания отмечено «хранить более года»), с самого начала
|
|
кладутся в отдельный каталог keep/ и очисткой не трогаются. Остальные удаляются, когда старше заданного числа дней
|
|
(настройка photo_keep_days, 0 — не удалять). Проверка — раз в сутки из планировщика, вручную — из настроек сервера."""
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from . import config, media, settings_store as ss
|
|
from .models import Photo, TaskPhoto
|
|
|
|
log = logging.getLogger("photo_cleanup")
|
|
KEEP = "keep/"
|
|
EVERY = timedelta(hours=24)
|
|
|
|
|
|
def days(db: Session) -> int:
|
|
try:
|
|
return max(0, int(ss.get(db, "photo_keep_days") or 0))
|
|
except ValueError:
|
|
return 0
|
|
|
|
|
|
def _old(db: Session, model, cut: datetime):
|
|
return db.scalars(select(model).where(model.created_at < cut, model.path.not_like(KEEP + "%"))).all()
|
|
|
|
|
|
def run(db: Session, dry: bool = False, now: datetime | None = None) -> dict:
|
|
"""Удалить старые фото (dry — только посчитать). Возвращает число фото и освобождённые байты."""
|
|
n = days(db)
|
|
if n <= 0:
|
|
return {"enabled": False, "days": 0, "photos": 0, "bytes": 0}
|
|
cut = (now or config.now()) - timedelta(days=n)
|
|
root = ss.photo_root(db)
|
|
order_ph, task_ph = _old(db, Photo, cut), _old(db, TaskPhoto, cut)
|
|
out = {"enabled": True, "days": n, "before": config.iso(cut), "photos": len(order_ph) + len(task_ph),
|
|
"bytes": sum(p.size or 0 for p in order_ph + task_ph)}
|
|
if dry:
|
|
return out
|
|
for p in order_ph:
|
|
(root / p.path).unlink(missing_ok=True)
|
|
db.delete(p)
|
|
for p in task_ph:
|
|
media.remove(db, "tasks", p.path)
|
|
db.delete(p)
|
|
ss.set_(db, "photo_cleanup_at", config.now().isoformat())
|
|
db.commit()
|
|
if out["photos"]:
|
|
log.warning("Удалено старых фото: %s (%.1f МБ), старше %s дней", out["photos"], out["bytes"] / 1048576, n)
|
|
return out
|
|
|
|
|
|
def due(db: Session) -> bool:
|
|
if days(db) <= 0:
|
|
return False
|
|
at = ss.get(db, "photo_cleanup_at")
|
|
try:
|
|
return not at or config.now() - datetime.fromisoformat(at) >= EVERY
|
|
except ValueError:
|
|
return True
|