@@ -4983,7 +4983,7 @@ def AppendFilesWithContent(infiles, fp, dirlistfromtxt=False, filevalues=[], ext
49834983 curcompression = "none"
49844984 if not followlink and ftype in data_types:
49854985 with open(fname, "rb") as fpc:
4986- shutil.copyfileobj (fpc, fcontents)
4986+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
49874987 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
49884988 fcontents.seek(0, 0)
49894989 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -5030,7 +5030,7 @@ def AppendFilesWithContent(infiles, fp, dirlistfromtxt=False, filevalues=[], ext
50305030 return False
50315031 flstatinfo = os.stat(flinkname)
50325032 with open(flinkname, "rb") as fpc:
5033- shutil.copyfileobj (fpc, fcontents)
5033+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
50345034 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
50355035 fcontents.seek(0, 0)
50365036 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -6330,6 +6330,106 @@ def open_adapter(obj_or_path, mode="rb", use_mmap=False, mmap_size=None):
63306330
63316331# Assumes you already have: compressionsupport, outextlistwd, MkTempFile, etc.
63326332
6333+ def ensure_filelike(infile, mode="rb", use_mmap=False):
6334+ """
6335+ Accepts either a path or an existing file-like object.
6336+ Always returns a FileLikeAdapter (optionally mmap-backed).
6337+ """
6338+ if hasattr(infile, "read") or hasattr(infile, "write"):
6339+ # Already a file-like
6340+ fp = infile
6341+ else:
6342+ try:
6343+ fp = open(infile, mode)
6344+ except IOError: # covers FileNotFoundError on Py2
6345+ return False
6346+
6347+ # Wrap in FileLikeAdapter for consistent interface
6348+ return open_adapter(fp, mode=mode, use_mmap=use_mmap)
6349+
6350+ def fast_copy(infp, outfp, bufsize=1 << 20):
6351+ buf = bytearray(bufsize)
6352+ mv = memoryview(buf)
6353+ while True:
6354+ n = getattr(infp, "readinto", None)
6355+ if callable(n):
6356+ n = infp.readinto(mv)
6357+ if not n:
6358+ break
6359+ outfp.write(mv[:n])
6360+ else:
6361+ # Fallback if readinto is missing
6362+ data = infp.read(bufsize)
6363+ if not data:
6364+ break
6365+ outfp.write(data)
6366+
6367+ def copy_file_to_mmap_dest(src_path, outfp, chunk_size=8 << 20):
6368+ with open(src_path, "rb") as fp:
6369+ try:
6370+ mm = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
6371+ pos, size = 0, len(mm)
6372+ while pos < size:
6373+ end = min(pos + chunk_size, size)
6374+ outfp.write(mm[pos:end]) # outfp is your mmap-backed FileLikeAdapter
6375+ pos = end
6376+ mm.close()
6377+ except (ValueError, mmap.error, OSError):
6378+ # fall back
6379+ shutil.copyfileobj(fp, outfp, length=chunk_size)
6380+
6381+ def copy_opaque(src, dst, bufsize=1 << 20, grow_step=64 << 20):
6382+ """
6383+ Copy opaque bytes from 'src' (any readable file-like) to 'dst'
6384+ (your mmap-backed FileLikeAdapter or any writable file-like).
6385+ - Uses readinto when available (zero extra allocations).
6386+ - If dst is mmapped and size is exceeded, auto-grow via truncate().
6387+ Returns total bytes copied.
6388+ """
6389+ total = 0
6390+ buf = bytearray(bufsize)
6391+ mv = memoryview(buf)
6392+
6393+ # Best-effort: if src supports seek/tell, start from current position
6394+ # and do not disturb caller beyond what we read.
6395+ while True:
6396+ # Prefer readinto to avoid extra allocations
6397+ readinto = getattr(src, "readinto", None)
6398+ if callable(readinto):
6399+ n = src.readinto(mv)
6400+ if not n:
6401+ break
6402+ # write; if mmap too small, grow and retry once
6403+ try:
6404+ dst.write(mv[:n])
6405+ except IOError:
6406+ # likely "write past mapped size"; try to grow
6407+ try:
6408+ new_size = max(dst.tell() + n, dst.tell() + grow_step)
6409+ dst.truncate(new_size)
6410+ dst.write(mv[:n])
6411+ except Exception:
6412+ raise
6413+ total += n
6414+ else:
6415+ chunk = src.read(bufsize)
6416+ if not chunk:
6417+ break
6418+ try:
6419+ dst.write(chunk)
6420+ except IOError:
6421+ try:
6422+ new_size = max(dst.tell() + len(chunk), dst.tell() + grow_step)
6423+ dst.truncate(new_size)
6424+ dst.write(chunk)
6425+ except Exception:
6426+ raise
6427+ total += len(chunk)
6428+
6429+ # Your adapter's flush() already does mm.flush() + fp.flush() + fsync(fd) when possible
6430+ dst.flush()
6431+ return total
6432+
63336433def CompressOpenFileAlt(fp, compression="auto", compressionlevel=None,
63346434 compressionuselist=compressionlistalt,
63356435 formatspecs=__file_format_dict__):
@@ -6795,7 +6895,7 @@ def PackFoxFile(infiles, outfile, dirlistfromtxt=False, fmttype="auto", compress
67956895 curcompression = "none"
67966896 if not followlink and ftype in data_types:
67976897 with open(fname, "rb") as fpc:
6798- shutil.copyfileobj (fpc, fcontents)
6898+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
67996899 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
68006900 fcontents.seek(0, 0)
68016901 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -6842,7 +6942,7 @@ def PackFoxFile(infiles, outfile, dirlistfromtxt=False, fmttype="auto", compress
68426942 return False
68436943 flstatinfo = os.stat(flinkname)
68446944 with open(flinkname, "rb") as fpc:
6845- shutil.copyfileobj (fpc, fcontents)
6945+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
68466946 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
68476947 fcontents.seek(0, 0)
68486948 fcencoding = GetFileEncoding(fcontents, 0, False)
@@ -7136,7 +7236,7 @@ def PackFoxFileFromTarFile(infile, outfile, fmttype="auto", compression="auto",
71367236 curcompression = "none"
71377237 if ftype in data_types:
71387238 fpc = tarfp.extractfile(member)
7139- shutil.copyfileobj (fpc, fcontents)
7239+ copy_opaque (fpc, fcontents, bufsize=1 << 20) # 1 MiB chunks, opaque copy
71407240 fpc.close()
71417241 typechecktest = CheckCompressionType(fcontents, filestart=0, closefp=False)
71427242 fcontents.seek(0, 0)
0 commit comments