@@ -4944,7 +4944,7 @@ def AppendFilesWithContent(infiles, fp, dirlistfromtxt=False, filevalues=[], ext
49444944 curcompression = "none"
49454945 if not followlink and ftype in data_types:
49464946 with open(fname, "rb") as fpc:
4947- shutil.copyfileobj (fpc, fcontents)
4947+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
49484948 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
49494949 fcontents.seek(0, 0)
49504950 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -4991,7 +4991,7 @@ def AppendFilesWithContent(infiles, fp, dirlistfromtxt=False, filevalues=[], ext
49914991 return False
49924992 flstatinfo = os.stat(flinkname)
49934993 with open(flinkname, "rb") as fpc:
4994- shutil.copyfileobj (fpc, fcontents)
4994+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
49954995 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
49964996 fcontents.seek(0, 0)
49974997 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -6292,6 +6292,106 @@ def open_adapter(obj_or_path, mode="rb", use_mmap=False, mmap_size=None):
62926292
62936293# Assumes you already have: compressionsupport, outextlistwd, MkTempFile, etc.
62946294
6295+ def ensure_filelike(infile, mode="rb", use_mmap=False):
6296+ """
6297+ Accepts either a path or an existing file-like object.
6298+ Always returns a FileLikeAdapter (optionally mmap-backed).
6299+ """
6300+ if hasattr(infile, "read") or hasattr(infile, "write"):
6301+ # Already a file-like
6302+ fp = infile
6303+ else:
6304+ try:
6305+ fp = open(infile, mode)
6306+ except IOError: # covers FileNotFoundError on Py2
6307+ return False
6308+
6309+ # Wrap in FileLikeAdapter for consistent interface
6310+ return open_adapter(fp, mode=mode, use_mmap=use_mmap)
6311+
6312+ def fast_copy(infp, outfp, bufsize=1 << 20):
6313+ buf = bytearray(bufsize)
6314+ mv = memoryview(buf)
6315+ while True:
6316+ n = getattr(infp, "readinto", None)
6317+ if callable(n):
6318+ n = infp.readinto(mv)
6319+ if not n:
6320+ break
6321+ outfp.write(mv[:n])
6322+ else:
6323+ # Fallback if readinto is missing
6324+ data = infp.read(bufsize)
6325+ if not data:
6326+ break
6327+ outfp.write(data)
6328+
6329+ def copy_file_to_mmap_dest(src_path, outfp, chunk_size=8 << 20):
6330+ with open(src_path, "rb") as fp:
6331+ try:
6332+ mm = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
6333+ pos, size = 0, len(mm)
6334+ while pos < size:
6335+ end = min(pos + chunk_size, size)
6336+ outfp.write(mm[pos:end]) # outfp is your mmap-backed FileLikeAdapter
6337+ pos = end
6338+ mm.close()
6339+ except (ValueError, mmap.error, OSError):
6340+ # fall back
6341+ shutil.copyfileobj(fp, outfp, length=chunk_size)
6342+
6343+ def copy_opaque(src, dst, bufsize=1 << 20, grow_step=64 << 20):
6344+ """
6345+ Copy opaque bytes from 'src' (any readable file-like) to 'dst'
6346+ (your mmap-backed FileLikeAdapter or any writable file-like).
6347+ - Uses readinto when available (zero extra allocations).
6348+ - If dst is mmapped and size is exceeded, auto-grow via truncate().
6349+ Returns total bytes copied.
6350+ """
6351+ total = 0
6352+ buf = bytearray(bufsize)
6353+ mv = memoryview(buf)
6354+
6355+ # Best-effort: if src supports seek/tell, start from current position
6356+ # and do not disturb caller beyond what we read.
6357+ while True:
6358+ # Prefer readinto to avoid extra allocations
6359+ readinto = getattr(src, "readinto", None)
6360+ if callable(readinto):
6361+ n = src.readinto(mv)
6362+ if not n:
6363+ break
6364+ # write; if mmap too small, grow and retry once
6365+ try:
6366+ dst.write(mv[:n])
6367+ except IOError:
6368+ # likely "write past mapped size"; try to grow
6369+ try:
6370+ new_size = max(dst.tell() + n, dst.tell() + grow_step)
6371+ dst.truncate(new_size)
6372+ dst.write(mv[:n])
6373+ except Exception:
6374+ raise
6375+ total += n
6376+ else:
6377+ chunk = src.read(bufsize)
6378+ if not chunk:
6379+ break
6380+ try:
6381+ dst.write(chunk)
6382+ except IOError:
6383+ try:
6384+ new_size = max(dst.tell() + len(chunk), dst.tell() + grow_step)
6385+ dst.truncate(new_size)
6386+ dst.write(chunk)
6387+ except Exception:
6388+ raise
6389+ total += len(chunk)
6390+
6391+ # Your adapter's flush() already does mm.flush() + fp.flush() + fsync(fd) when possible
6392+ dst.flush()
6393+ return total
6394+
62956395def CompressOpenFileAlt(fp, compression="auto", compressionlevel=None,
62966396 compressionuselist=compressionlistalt,
62976397 formatspecs=__file_format_dict__):
@@ -6757,7 +6857,7 @@ def PackCatFile(infiles, outfile, dirlistfromtxt=False, fmttype="auto", compress
67576857 curcompression = "none"
67586858 if not followlink and ftype in data_types:
67596859 with open(fname, "rb") as fpc:
6760- shutil.copyfileobj (fpc, fcontents)
6860+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
67616861 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
67626862 fcontents.seek(0, 0)
67636863 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -6804,7 +6904,7 @@ def PackCatFile(infiles, outfile, dirlistfromtxt=False, fmttype="auto", compress
68046904 return False
68056905 flstatinfo = os.stat(flinkname)
68066906 with open(flinkname, "rb") as fpc:
6807- shutil.copyfileobj (fpc, fcontents)
6907+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
68086908 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
68096909 fcontents.seek(0, 0)
68106910 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -7098,7 +7198,7 @@ def PackCatFileFromTarFile(infile, outfile, fmttype="auto", compression="auto",
70987198 curcompression = "none"
70997199 if ftype in data_types:
71007200 fpc = tarfp.extractfile(member)
7101- shutil.copyfileobj (fpc, fcontents)
7201+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
71027202 fpc.close()
71037203 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
71047204 fcontents.seek(0, 0)
0 commit comments