Tag-casing acronym fix, Patreon resolver hardening, archive diagnostics, post-card strip #74
@@ -269,6 +269,11 @@ class DownloadService:
|
|||||||
source_row = self.sync_session.get(Source, ctx["source_id"])
|
source_row = self.sync_session.get(Source, ctx["source_id"])
|
||||||
|
|
||||||
import_summary = {"attached": 0, "skipped": 0, "errors": 0}
|
import_summary = {"attached": 0, "skipped": 0, "errors": 0}
|
||||||
|
# Archives detected but captured WITHOUT extracting any image (probe
|
||||||
|
# rejected / corrupt / missing extractor backend). Surfaced on the event
|
||||||
|
# so a post showing "no images" beside a zip is diagnosable (plan
|
||||||
|
# follow-up 2026-06-06 — the recurring archive-association report).
|
||||||
|
unextracted_archives: list[dict] = []
|
||||||
bytes_downloaded = 0
|
bytes_downloaded = 0
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
@@ -322,6 +327,17 @@ class DownloadService:
|
|||||||
# mirror duplicate_hash cleanup so we don't keep two copies.
|
# mirror duplicate_hash cleanup so we don't keep two copies.
|
||||||
# Operator-flagged 2026-06-02 (Lustria OST zip).
|
# Operator-flagged 2026-06-02 (Lustria OST zip).
|
||||||
import_summary["attached"] += 1
|
import_summary["attached"] += 1
|
||||||
|
# An archive captured WITHOUT extracting any image carries a
|
||||||
|
# reason on result.error — record it so the event explains the
|
||||||
|
# "no images beside a zip" symptom instead of staying silent.
|
||||||
|
if result.error:
|
||||||
|
unextracted_archives.append(
|
||||||
|
{"file": path.name, "reason": result.error}
|
||||||
|
)
|
||||||
|
log.warning(
|
||||||
|
"archive captured unextracted (%s): %s",
|
||||||
|
path.name, result.error,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
bytes_downloaded += path.stat().st_size # noqa: ASYNC240
|
bytes_downloaded += path.stat().st_size # noqa: ASYNC240
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -403,6 +419,10 @@ class DownloadService:
|
|||||||
"duration_seconds": dl_result.duration_seconds,
|
"duration_seconds": dl_result.duration_seconds,
|
||||||
"quarantined_paths": dl_result.quarantined_paths or None,
|
"quarantined_paths": dl_result.quarantined_paths or None,
|
||||||
"import_summary": import_summary,
|
"import_summary": import_summary,
|
||||||
|
# Archives detected but captured without extracting an image — the
|
||||||
|
# recurring "post shows a zip but no images" report. Each entry is
|
||||||
|
# {file, reason}; None when every archive extracted cleanly.
|
||||||
|
"unextracted_archives": unextracted_archives or None,
|
||||||
}
|
}
|
||||||
await self._update_source_health(
|
await self._update_source_health(
|
||||||
source_id=ctx["source_id"], status=status, error_message=ev.error,
|
source_id=ctx["source_id"], status=status, error_message=ev.error,
|
||||||
|
|||||||
@@ -430,13 +430,17 @@ class Importer:
|
|||||||
self._capture_attachment(
|
self._capture_attachment(
|
||||||
source, post=post, artist=artist_use, resolved=True,
|
source, post=post, artist=artist_use, resolved=True,
|
||||||
)
|
)
|
||||||
return ImportResult(status="attached")
|
reason = f"archive probe rejected, captured unextracted: {probe.reason}"
|
||||||
|
log.warning("%s: %s", source.name, reason)
|
||||||
|
return ImportResult(status="attached", error=reason)
|
||||||
|
|
||||||
artist_use = artist if artist is not None else self._resolve_artist(source)
|
artist_use = artist if artist is not None else self._resolve_artist(source)
|
||||||
post = self._post_for_sidecar(source, artist_use)
|
post = self._post_for_sidecar(source, artist_use)
|
||||||
member_ids: list[int] = []
|
member_ids: list[int] = []
|
||||||
|
member_total = 0
|
||||||
with extract_archive(source) as members:
|
with extract_archive(source) as members:
|
||||||
for _name, member_path in members:
|
for _name, member_path in members:
|
||||||
|
member_total += 1
|
||||||
if not is_supported(member_path):
|
if not is_supported(member_path):
|
||||||
continue # non-media preserved via the stored archive
|
continue # non-media preserved via the stored archive
|
||||||
res = self._import_media(
|
res = self._import_media(
|
||||||
@@ -453,7 +457,19 @@ class Importer:
|
|||||||
status="imported", image_id=member_ids[0],
|
status="imported", image_id=member_ids[0],
|
||||||
member_image_ids=member_ids,
|
member_image_ids=member_ids,
|
||||||
)
|
)
|
||||||
return ImportResult(status="attached")
|
# No images landed — surface WHY so a post showing "no images" beside an
|
||||||
|
# archive is diagnosable instead of silent. Zero members usually means
|
||||||
|
# the extractor backend is missing/failed (unar for rar, py7zr for 7z)
|
||||||
|
# or the file is corrupt; non-zero-but-no-images means it held only
|
||||||
|
# non-media files.
|
||||||
|
reason = (
|
||||||
|
"archive extracted but held no supported image/video members"
|
||||||
|
if member_total
|
||||||
|
else "archive yielded no members (unsupported/corrupt, or the "
|
||||||
|
"extractor backend failed)"
|
||||||
|
)
|
||||||
|
log.warning("%s: %s", source.name, reason)
|
||||||
|
return ImportResult(status="attached", error=reason)
|
||||||
|
|
||||||
def _import_media(
|
def _import_media(
|
||||||
self, source: Path, attribution_path: Path,
|
self, source: Path, attribution_path: Path,
|
||||||
|
|||||||
Reference in New Issue
Block a user