pegavisao / gallery_storage.py
Roudrigus's picture
Upload 17 files
3168916 verified
# gallery_storage.py
# -*- coding: utf-8 -*-
import os
from pathlib import Path
from typing import List, Tuple, Optional
from PIL import Image, ImageOps
# Settings: aceita Config/ e config/; se não achar, usa defaults
try:
from Config.settings import GALLERY_DIR, THUMB_DIR, GALLERY_ALLOWED_EXT, THUMB_MAX
except Exception:
try:
from config.settings import GALLERY_DIR, THUMB_DIR, GALLERY_ALLOWED_EXT, THUMB_MAX
except Exception:
# Fallbacks seguros
BASE_DIR = Path(__file__).resolve().parent
DATA_DIR = BASE_DIR / "data"
GALLERY_DIR = DATA_DIR / "reports_photos"
THUMB_DIR = GALLERY_DIR / "_thumbs"
GALLERY_ALLOWED_EXT = {".png", ".jpg", ".jpeg", ".webp"}
THUMB_MAX = (512, 512)
GALLERY_DIR.mkdir(parents=True, exist_ok=True)
THUMB_DIR.mkdir(parents=True, exist_ok=True)
def _gallery_is_image(filename: str) -> bool:
ext = os.path.splitext(filename)[1].lower()
return ext in GALLERY_ALLOWED_EXT
def _gallery_safe_name(base: str) -> str:
return "".join(c for c in base if c.isalnum() or c in ("-", "_")).strip("_")
def _thumb_path_for(orig_path: Path) -> Path:
base = orig_path.stem
return THUMB_DIR / f"{base}-thumb-{THUMB_MAX[0]}x{THUMB_MAX[1]}.jpg"
def _generate_thumbnail(orig_path: Path, thumb_path: Path) -> None:
try:
with Image.open(orig_path) as im:
im = ImageOps.exif_transpose(im)
if im.mode in ("RGBA", "LA"):
bg = Image.new("RGB", im.size, (255, 255, 255))
bg.paste(im, mask=im.split()[-1])
im = bg
elif im.mode != "RGB":
im = im.convert("RGB")
im.thumbnail(THUMB_MAX)
thumb_path.parent.mkdir(parents=True, exist_ok=True)
im.save(str(thumb_path), "JPEG", quality=85, optimize=True, progressive=True)
except Exception as e:
print(f"[thumb] Falha ao gerar miniatura para {orig_path}: {e}")
def _ensure_thumbnail(orig_path: Path) -> Path:
tpath = _thumb_path_for(orig_path)
try:
orig_mtime = orig_path.stat().st_mtime
thumb_mtime = tpath.stat().st_mtime if tpath.exists() else -1
if thumb_mtime < orig_mtime:
_generate_thumbnail(orig_path, tpath)
except Exception as e:
print(f"[thumb] Erro: {e}")
return tpath
def gallery_list_images_sorted() -> List[Tuple[str, float, Path, Path]]:
"""
[(nome, mtime, caminho_original, caminho_thumb)] do mais novo ao mais antigo.
"""
if not GALLERY_DIR.is_dir():
return []
items = []
for name in os.listdir(GALLERY_DIR):
full = GALLERY_DIR / name
if full.is_file() and _gallery_is_image(name):
try:
mtime = full.stat().st_mtime
tpath = _ensure_thumbnail(full)
items.append((name, mtime, full, tpath))
except Exception:
continue
items.sort(key=lambda t: t[1], reverse=True)
return items
def save_report_image(uploaded_file, uploader_name: Optional[str] = None, uploader_email: Optional[str] = None) -> Optional[Path]:
if uploaded_file is None:
return None
base, ext = os.path.splitext(uploaded_file.name or "")
ext = ext.lower()
if ext not in GALLERY_ALLOWED_EXT:
raise ValueError("Formato não suportado. Use PNG/JPG/JPEG/WEBP.")
who_bits = []
if uploader_name: who_bits.append(_gallery_safe_name(uploader_name))
if uploader_email: who_bits.append(_gallery_safe_name((uploader_email or "").split("@")[0]))
who_str = ("-" + "-".join(filter(None, who_bits))) if who_bits else ""
from datetime import datetime
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
safe_base = _gallery_safe_name(base) or "relatorio"
fname = f"{ts}-{safe_base}{who_str}{ext}"
dest = GALLERY_DIR / fname
img = Image.open(uploaded_file)
img = ImageOps.exif_transpose(img)
if ext in (".jpg", ".jpeg") and img.mode not in ("RGB",):
img = img.convert("RGB")
img.save(dest)
_ensure_thumbnail(dest)
return dest
def delete_report_image(filename: str, is_admin: bool) -> None:
"""
Exclui (original + thumb) de forma segura e restrita a Admin.
- Protege contra path traversal (só apaga dentro de GALLERY_DIR)
"""
if not is_admin:
raise PermissionError("Somente Admin pode excluir imagens.")
if "/" in filename or "\\" in filename:
raise ValueError("Nome de arquivo inválido.")
target = (GALLERY_DIR / filename).resolve()
if GALLERY_DIR.resolve() not in target.parents and target != GALLERY_DIR.resolve():
raise ValueError("Caminho inválido.")
if target.exists():
target.unlink()
tpath = _thumb_path_for(target)
if tpath.exists():
tpath.unlink()