Enrich IQDB candidates; link downloads the e621 original
- IQDB responses carry no preview/file data, so candidates only showed an ID; the SPA now enriches them with one batched posts lookup (preview, rating, score, favourites, dimensions, tag preview) - Candidate tiles are selectable instead of instantly resolving: picking one shows its info and an explicit 'Link selected post' button - Linking a post now fetches the e621 original into the library and drops the staged upload; when the staged file's MD5 already equals the post's file, the staged copy is moved instead (identical bytes) - Keep the file URL in stored e621 metadata; sanitize the new candidate fields server-side
This commit is contained in:
@@ -12,6 +12,7 @@ Flow:
|
||||
import logging
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
@@ -31,20 +32,43 @@ from .serializers import TempUploadSerializer
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def complete_temp_upload(temp):
|
||||
"""Move a staged file into the watched folder and index it."""
|
||||
def complete_temp_upload(temp, download_url=None):
|
||||
"""Index the upload into the library.
|
||||
|
||||
With a ``download_url`` the e621 original is fetched and the staged copy
|
||||
is dropped; otherwise the staged file itself moves into the library.
|
||||
"""
|
||||
folder = Path(settings.WATCHED_FOLDER)
|
||||
if not temp.file:
|
||||
raise ValueError("staged file is missing")
|
||||
|
||||
destination = services.unique_destination(
|
||||
folder, temp.original_filename or "upload"
|
||||
)
|
||||
with temp.file.open("rb") as source, open(destination, "wb") as target:
|
||||
shutil.copyfileobj(source, target)
|
||||
if download_url:
|
||||
file_data = {}
|
||||
if isinstance(temp.e621_data, dict):
|
||||
file_data = temp.e621_data.get("file") or {}
|
||||
extension = Path(urlparse(download_url).path).suffix
|
||||
if not extension:
|
||||
extension = Path(temp.original_filename).suffix
|
||||
md5 = file_data.get("md5")
|
||||
name = f"{md5}{extension}" if md5 else (temp.original_filename or "download")
|
||||
destination = services.unique_destination(folder, name)
|
||||
try:
|
||||
services.download_file(download_url, destination)
|
||||
except Exception:
|
||||
destination.unlink(missing_ok=True)
|
||||
raise
|
||||
item, _, _ = services.index_file(destination, folder)
|
||||
if temp.file:
|
||||
temp.file.delete(save=False)
|
||||
else:
|
||||
if not temp.file:
|
||||
raise ValueError("staged file is missing")
|
||||
destination = services.unique_destination(
|
||||
folder, temp.original_filename or "upload"
|
||||
)
|
||||
with temp.file.open("rb") as source, open(destination, "wb") as target:
|
||||
shutil.copyfileobj(source, target)
|
||||
item, _, _ = services.index_file(destination, folder)
|
||||
temp.file.delete(save=False)
|
||||
|
||||
item, _, _ = services.index_file(destination, folder)
|
||||
temp.file.delete(save=False)
|
||||
temp.library_item = item
|
||||
temp.status = TempUpload.STATUS_COMPLETED
|
||||
|
||||
@@ -184,6 +208,7 @@ class TempUploadViewSet(
|
||||
)
|
||||
|
||||
mode = str(request.data.get("mode") or "").strip()
|
||||
download_url = None
|
||||
if mode == "link":
|
||||
post = request.data.get("post")
|
||||
post_id = request.data.get("post_id")
|
||||
@@ -213,6 +238,17 @@ class TempUploadViewSet(
|
||||
notes = request.data.get("notes")
|
||||
if notes is not None:
|
||||
temp.custom_notes = str(notes)
|
||||
|
||||
# Prefer the e621 original; fall back to the staged file when the
|
||||
# post has no URL or its file is byte-identical to the upload.
|
||||
file_data = (trimmed or {}).get("file") or {}
|
||||
candidate_url = str(
|
||||
request.data.get("file_url") or file_data.get("url") or ""
|
||||
).strip()
|
||||
post_md5 = str(file_data.get("md5") or "").strip().lower()
|
||||
if candidate_url and post_md5 and post_md5 == temp.md5.lower():
|
||||
candidate_url = ""
|
||||
download_url = candidate_url or None
|
||||
elif mode == "custom":
|
||||
rating = str(request.data.get("rating") or "")
|
||||
temp.custom_rating = rating if rating in {"s", "q", "e"} else ""
|
||||
@@ -227,7 +263,7 @@ class TempUploadViewSet(
|
||||
|
||||
temp.save()
|
||||
try:
|
||||
complete_temp_upload(temp)
|
||||
complete_temp_upload(temp, download_url=download_url)
|
||||
except Exception as exc: # noqa: BLE001 - report completion failures
|
||||
logger.exception("Could not complete staged upload %s", temp.id)
|
||||
temp.status = TempUpload.STATUS_ERROR
|
||||
|
||||
Reference in New Issue
Block a user