feat(attachments): scan enumerates all; import_file maps 'attached' + member ML

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 11:14:49 -04:00
parent f97551e2f6
commit 89103c4570
4 changed files with 75 additions and 16 deletions
+16 -10
View File
@@ -26,11 +26,12 @@ IMAGES_ROOT = Path("/images")
def _map_result_to_status(result):
"""(ImportTask.status, should_requeue_ml_and_thumb) for an ImportResult.
'superseded' = the kept row's file/ML changed → complete + re-derive."""
if result.status == "imported":
return ("complete", True)
if result.status == "superseded":
'superseded' = the kept row's file/ML changed → complete + re-derive.
'attached' = a non-art file preserved → complete, no ML/thumb."""
if result.status in ("imported", "superseded"):
return ("complete", True)
if result.status == "attached":
return ("complete", False)
if result.status == "skipped":
return ("skipped", False)
return ("failed", False)
@@ -82,6 +83,10 @@ def import_media_file(self, import_task_id: int) -> dict:
task.result_image_id = result.image_id
counter_col_name = "imported"
counter_col = ImportBatch.imported
elif result.status == "attached":
task.status = "complete"
counter_col_name = "attachments"
counter_col = ImportBatch.attachments
elif result.status == "skipped":
task.status = "skipped"
task.error = (
@@ -109,15 +114,16 @@ def import_media_file(self, import_task_id: int) -> dict:
# Enqueue thumbnail + ML for newly imported AND superseded images
# (a superseded row has cleared ML + no thumbnail).
if (
result.status in ("imported", "superseded")
and result.image_id is not None
):
if result.status in ("imported", "superseded"):
from .ml import tag_and_embed
from .thumbnail import generate_thumbnail
generate_thumbnail.delay(result.image_id)
tag_and_embed.delay(result.image_id)
ids = list(result.member_image_ids)
if result.image_id is not None and result.image_id not in ids:
ids.append(result.image_id)
for img_id in ids:
generate_thumbnail.delay(img_id)
tag_and_embed.delay(img_id)
# If this was the last task in the batch, mark the batch complete.
remaining = session.execute(
+15 -6
View File
@@ -11,7 +11,20 @@ from sqlalchemy.orm import sessionmaker
from ..celery_app import celery
from ..config import get_config
from ..models import ImportBatch, ImportSettings, ImportTask
from ..services.importer import is_supported
def _iter_import_files(import_root: Path):
"""Every regular file except sidecar .json, dotfiles, and .partial
temp files. The Importer dispatches by kind (media / archive /
other) — scan no longer filters to media (FC-2d-iii)."""
for entry in import_root.rglob("*"):
if not entry.is_file():
continue
if entry.name.startswith("."):
continue
if entry.suffix.lower() in (".json", ".partial"):
continue
yield entry
def _sync_session_factory():
@@ -42,11 +55,7 @@ def scan_directory(self, triggered_by: str = "manual") -> int:
# Walk and enumerate.
files_seen = 0
for entry in import_root.rglob("*"):
if not entry.is_file():
continue
if not is_supported(entry):
continue
for entry in _iter_import_files(import_root):
try:
size = entry.stat().st_size
except OSError: