Skip to content

Commit a30a90a

Browse files
author
Kazuki Suzuki Przyborowski
committed
Update pycatfile.py
1 parent ef5ca7b commit a30a90a

1 file changed

Lines changed: 105 additions & 5 deletions

File tree

pycatfile.py

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
62956395
def 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

Comments
 (0)