@@ -675,23 +675,29 @@ def cancel_audit_run(session: Session, *, audit_id: int) -> None:
|
|||||||
_ARCHIVE_EXT_FOR_FORMAT = {"zip": ".zip", "rar": ".rar", "7z": ".7z"}
|
_ARCHIVE_EXT_FOR_FORMAT = {"zip": ".zip", "rar": ".rar", "7z": ".7z"}
|
||||||
|
|
||||||
|
|
||||||
def _reextract_archive_to_post(importer, archive_path: Path, post, source_row, artist) -> list[int]:
|
def _reextract_archive_to_post(
|
||||||
|
importer, archive_path: Path, post, source_row, artist, images_root: Path,
|
||||||
|
) -> list[int]:
|
||||||
"""Extract one stored archive and link its members to `post`.
|
"""Extract one stored archive and link its members to `post`.
|
||||||
|
|
||||||
The stored attachment has no adjacent sidecar (it lives in the sha-addressed
|
The stored attachment has no adjacent sidecar (it lives in the sha-addressed
|
||||||
attachment store), so reconstruct the post's sidecar from the DB and re-run
|
attachment store). Stage a copy + a reconstructed sidecar UNDER the artist's
|
||||||
the normal import path: `attach_in_place` extracts the members and
|
library dir (`images_root/<slug>/<platform>/<post>/`) — the importer
|
||||||
`_apply_sidecar` → `find_or_create_post` re-attaches them to the SAME Post
|
re-derives the artist from the path AND copies members relative to it, so the
|
||||||
(matched by source_id + external_post_id). Returns the new member image ids.
|
members land in the real library and resolve to the right artist — then re-run
|
||||||
|
`attach_in_place`: the archive extracts and `find_or_create_post` re-attaches
|
||||||
|
the members to the SAME Post (source_id + external_post_id). Removes only the
|
||||||
|
staged archive + sidecar afterward; the imported member files stay. Returns
|
||||||
|
the new member image ids.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from .archive_extractor import detect_archive_format
|
from .archive_extractor import detect_archive_format
|
||||||
|
|
||||||
fmt = detect_archive_format(archive_path)
|
fmt = detect_archive_format(archive_path)
|
||||||
ext = _ARCHIVE_EXT_FOR_FORMAT.get(fmt or "", ".zip")
|
ext = _ARCHIVE_EXT_FOR_FORMAT.get(fmt or "", ".zip")
|
||||||
|
platform = source_row.platform if source_row is not None else "imported"
|
||||||
sidecar = {
|
sidecar = {
|
||||||
"category": source_row.platform if source_row is not None
|
"category": source_row.platform if source_row is not None
|
||||||
else (post.raw_metadata or {}).get("category"),
|
else (post.raw_metadata or {}).get("category"),
|
||||||
@@ -701,12 +707,20 @@ def _reextract_archive_to_post(importer, archive_path: Path, post, source_row, a
|
|||||||
"published_at": post.post_date.isoformat() if post.post_date else None,
|
"published_at": post.post_date.isoformat() if post.post_date else None,
|
||||||
"url": post.post_url,
|
"url": post.post_url,
|
||||||
}
|
}
|
||||||
with tempfile.TemporaryDirectory(prefix="fc_reextract_") as td:
|
work = images_root / artist.slug / platform / str(post.external_post_id)
|
||||||
tmp = Path(td) / f"archive{ext}" # clean ext → is_archive + find_sidecar
|
work.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copy2(archive_path, tmp)
|
staged = work / f"archive{ext}" # clean ext → is_archive + find_sidecar
|
||||||
tmp.with_suffix(".json").write_text(json.dumps(sidecar))
|
sidecar_path = staged.with_suffix(".json")
|
||||||
res = importer.attach_in_place(tmp, artist=artist, source=source_row)
|
try:
|
||||||
|
shutil.copy2(archive_path, staged)
|
||||||
|
sidecar_path.write_text(json.dumps(sidecar))
|
||||||
|
res = importer.attach_in_place(staged, artist=artist, source=source_row)
|
||||||
return list(res.member_image_ids or [])
|
return list(res.member_image_ids or [])
|
||||||
|
finally:
|
||||||
|
# Drop only the staged archive + sidecar; the extracted member files
|
||||||
|
# were copied into the library alongside them and must stay.
|
||||||
|
staged.unlink(missing_ok=True)
|
||||||
|
sidecar_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def reextract_archive_attachments(session: Session, *, images_root: Path) -> dict:
|
def reextract_archive_attachments(session: Session, *, images_root: Path) -> dict:
|
||||||
@@ -727,7 +741,8 @@ def reextract_archive_attachments(session: Session, *, images_root: Path) -> dic
|
|||||||
|
|
||||||
summary = {
|
summary = {
|
||||||
"scanned": 0, "archives": 0, "members_imported": 0,
|
"scanned": 0, "archives": 0, "members_imported": 0,
|
||||||
"posts_touched": 0, "skipped_no_post": 0, "errors": 0,
|
"posts_touched": 0, "skipped_no_post": 0, "skipped_no_artist": 0,
|
||||||
|
"errors": 0,
|
||||||
}
|
}
|
||||||
settings = ImportSettings.load_sync(session)
|
settings = ImportSettings.load_sync(session)
|
||||||
importer = Importer(
|
importer = Importer(
|
||||||
@@ -756,9 +771,19 @@ def reextract_archive_attachments(session: Session, *, images_root: Path) -> dic
|
|||||||
summary["skipped_no_post"] += 1
|
summary["skipped_no_post"] += 1
|
||||||
continue
|
continue
|
||||||
artist = session.get(Artist, att.artist_id) if att.artist_id else None
|
artist = session.get(Artist, att.artist_id) if att.artist_id else None
|
||||||
|
if artist is None and post.artist_id:
|
||||||
|
artist = session.get(Artist, post.artist_id)
|
||||||
|
if artist is None or not artist.slug:
|
||||||
|
# The importer re-derives the artist from the staged path, so we need
|
||||||
|
# a real artist+slug to anchor under. (Shouldn't happen for
|
||||||
|
# subscription posts; skip rather than orphan the members.)
|
||||||
|
summary["skipped_no_artist"] += 1
|
||||||
|
continue
|
||||||
source_row = session.get(Source, post.source_id) if post.source_id else None
|
source_row = session.get(Source, post.source_id) if post.source_id else None
|
||||||
try:
|
try:
|
||||||
ids = _reextract_archive_to_post(importer, stored, post, source_row, artist)
|
ids = _reextract_archive_to_post(
|
||||||
|
importer, stored, post, source_row, artist, images_root,
|
||||||
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
except Exception as exc: # one bad archive must not strand the rest
|
except Exception as exc: # one bad archive must not strand the rest
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|||||||
Reference in New Issue
Block a user