df76fd75d8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
"""Best-effort archive member extraction (filesystem-import aid).
|
|
|
|
zip/cbz via stdlib zipfile; rar via rarfile (needs an unrar/bsdtar
|
|
binary); 7z via py7zr (pure-Python). NEVER raises: on any failure the
|
|
context yields an empty list, and the caller still preserves the archive
|
|
file itself as a PostAttachment, so nothing is lost.
|
|
"""
|
|
|
|
import logging
|
|
import zipfile
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
ARCHIVE_EXTS = {".zip", ".cbz", ".rar", ".7z"}
|
|
|
|
|
|
def is_archive(path: Path) -> bool:
|
|
return Path(path).suffix.lower() in ARCHIVE_EXTS
|
|
|
|
|
|
@contextmanager
|
|
def extract_archive(path: Path):
|
|
"""Context manager yielding a list of (member_name, extracted_path)
|
|
for every regular-file member. Temp dir is removed on exit. Members
|
|
are only ever under the temp dir (libs' safe extractall + rglob from
|
|
base). Fail-soft: any error → yields []."""
|
|
tmp = TemporaryDirectory(prefix="fc_arch_")
|
|
base = Path(tmp.name)
|
|
members: list[tuple[str, Path]] = []
|
|
try:
|
|
try:
|
|
ext = Path(path).suffix.lower()
|
|
if ext in (".zip", ".cbz"):
|
|
with zipfile.ZipFile(path) as zf:
|
|
zf.extractall(base)
|
|
elif ext == ".rar":
|
|
import rarfile
|
|
|
|
with rarfile.RarFile(path) as rf:
|
|
rf.extractall(base)
|
|
elif ext == ".7z":
|
|
import py7zr
|
|
|
|
with py7zr.SevenZipFile(path, "r") as zf:
|
|
zf.extractall(base)
|
|
for p in sorted(base.rglob("*")):
|
|
if p.is_file():
|
|
members.append((str(p.relative_to(base)), p))
|
|
except Exception as exc:
|
|
log.warning("archive extract failed for %s: %s", path, exc)
|
|
members = []
|
|
yield members
|
|
finally:
|
|
tmp.cleanup()
|