# -*- coding: utf-8 -*- from __future__ import annotations from typing import TYPE_CHECKING, Callable import tarfile, io from tarfile import TarFile from ....lib.log import * if TYPE_CHECKING: from ....lib.ExecContext import ExecContext def filter(blob: bytes, path_filter: Callable[[str], bool]|None, matched: list[str]|None=None) -> bytes: ret = io.BytesIO() with tarfile.open(fileobj=ret, mode='w') as tf_out: tf_in = TarFile(fileobj=io.BytesIO(blob)) for info in tf_in.getmembers(): if path_filter is not None and not path_filter(info.name): continue log(DEBUG, f'Adding {info.name}') if matched is not None: matched.append(info.name) buf = tf_in.extractfile(info) tf_out.addfile(info, buf) return ret.getvalue() def rewrite(blob: bytes, rewrite_filter: Callable[[str], str]) -> bytes: ret = io.BytesIO() with tarfile.open(fileobj=ret, mode='w') as tf_out: tf_in = TarFile(fileobj=io.BytesIO(blob)) for info in tf_in.getmembers(): new_name = rewrite_filter(info.name) log(DEBUG, f'Rewriting {info.name} -> {new_name}') info.name = new_name buf = tf_in.extractfile(info) tf_out.addfile(info, buf) return ret.getvalue() def merge(blobs: Iterable[bytes], overwrite: bool=False) -> bytes: ret = io.BytesIO() with tarfile.open(fileobj=ret, mode='w') as tf_out: for blob in blobs: tf_in = TarFile(fileobj=io.BytesIO(blob)) existing_names = tf_out.getnames() for info in tf_in.getmembers(): if not overwrite and info.name in existing_names: continue buf = tf_in.extractfile(info) tf_out.addfile(info, buf) return ret.getvalue() async def extract(dst: ExecContext, blob: bytes, root: str|None=None, verbose: bool=False) -> None: cmd = ['tar'] if root is not None: cmd += ['-C', root] if verbose: cmd += '-v' cmd += ['-x', '-f', '-'] await dst.run(cmd, verbose=verbose, cmd_input=blob)