Skip to content

Commit c79bbf5

Browse files
committed
Small update
1 parent a36ff3c commit c79bbf5

3 files changed

Lines changed: 124 additions & 74 deletions

File tree

File renamed without changes.
File renamed without changes.

pyfoxfile/pyfile.py

Lines changed: 124 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import hmac
2626
import json
2727
import stat
28+
import atexit
2829
import shutil
2930
import base64
3031
import logging
@@ -445,6 +446,97 @@ def add_format(reg, key, magic, ext, name=None, ver="001",
445446
"format_extension": ext,
446447
}
447448

449+
# Process-lifetime extract dir (only used when resources aren't on the filesystem)
450+
_EXTRACT_DIR = None
451+
452+
def _get_extract_dir():
453+
global _EXTRACT_DIR
454+
if _EXTRACT_DIR is None:
455+
_EXTRACT_DIR = tempfile.mkdtemp(prefix="gm2k-cfg-")
456+
atexit.register(lambda: shutil.rmtree(_EXTRACT_DIR, ignore_errors=True))
457+
return _EXTRACT_DIR
458+
459+
def _atomic_write(path, data):
460+
tmp = path + ".tmp"
461+
f = open(tmp, "wb")
462+
try:
463+
f.write(data)
464+
finally:
465+
f.close()
466+
467+
try:
468+
# Py3
469+
os.replace(tmp, path)
470+
except Exception:
471+
# Py2 / fallback
472+
try:
473+
if os.path.exists(path):
474+
os.remove(path)
475+
except Exception:
476+
pass
477+
os.rename(tmp, path)
478+
479+
def resource_path(package_name, filename):
480+
"""
481+
Return a REAL filesystem path to a resource inside this package.
482+
483+
- If package is installed normally on disk: returns the existing path (no copy).
484+
- If package is imported from zip/egg: extracts to a temp dir once and returns that path.
485+
- Falls back to pkg_resources if needed.
486+
"""
487+
files = None
488+
try:
489+
try:
490+
from importlib.resources import files as _files # Py3.9+ (and sometimes available)
491+
files = _files
492+
except Exception:
493+
from importlib_resources import files as _files # backport
494+
files = _files
495+
except Exception:
496+
files = None
497+
498+
if files is not None:
499+
try:
500+
ref = files(package_name).joinpath(filename)
501+
502+
# If backed by filesystem, return that path
503+
try:
504+
return os.fspath(ref) # Py3 only
505+
except Exception:
506+
# zipped/non-path traversable -> extract bytes
507+
out = os.path.join(_get_extract_dir(), filename)
508+
if not os.path.exists(out):
509+
data = ref.read_bytes()
510+
_atomic_write(out, data)
511+
return out
512+
except Exception:
513+
pass
514+
515+
# setuptools fallback
516+
try:
517+
import pkg_resources
518+
return pkg_resources.resource_filename(package_name, filename)
519+
except Exception:
520+
pass
521+
522+
# last resort: __file__ relative (works only when not zipped)
523+
mod = sys.modules.get(package_name)
524+
base = os.path.dirname(getattr(mod, "__file__", __file__))
525+
return os.path.join(base, filename)
526+
527+
def resource_dir(package_name, filenames):
528+
"""
529+
Ensure a set of resources exist as real files in one directory.
530+
Returns that directory path.
531+
"""
532+
paths = [resource_path(package_name, fn) for fn in filenames]
533+
return os.path.dirname(paths[0])
534+
535+
filecfgpath = resource_dir(__name__, [
536+
"foxfile.ini",
537+
"foxfile.json",
538+
])
539+
448540
__file_format_multi_dict__ = {}
449541
__file_format_default__ = "FoxFile"
450542
__include_defaults__ = True
@@ -466,9 +558,9 @@ def add_format(reg, key, magic, ext, name=None, ver="001",
466558
__program_name__ = "Py"+__file_format_default__
467559
__use_env_file__ = True
468560
__use_ini_file__ = True
469-
__use_ini_name__ = "foxfile.ini"
561+
__use_ini_name__ = os.path.join(filecfgpath, "foxfile.ini")
470562
__use_json_file__ = False
471-
__use_json_name__ = "foxfile.json"
563+
__use_json_name__ = os.path.join(filecfgpath, "foxfile.json")
472564
if(__use_ini_file__ and __use_json_file__):
473565
__use_json_file__ = False
474566
if('PYARCHIVEFILE_CONFIG_FILE' in os.environ and os.path.exists(os.environ['PYARCHIVEFILE_CONFIG_FILE']) and __use_env_file__):
@@ -3305,45 +3397,34 @@ def UncompressFileAlt(fp, formatspecs=__file_format_multi_dict__, filestart=0):
33053397
if IsNestedDict(formatspecs) and kind in formatspecs:
33063398
formatspecs = formatspecs[kind]
33073399

3308-
# Guard against detector side-effects: ensure we're back at filestart
3309-
try:
3310-
src.seek(filestart, 0)
3311-
except Exception:
3312-
pass
3400+
src.seek(filestart, 0)
33133401

33143402
# Build logical stream (or passthrough)
33153403
if kind == "gzip" and "gzip" in compressionsupport:
33163404
wrapped = gzip.GzipFile(fileobj=src, mode="rb")
3405+
wrapped.seek(0, 0)
33173406
elif kind == "bzip2" and ("bzip2" in compressionsupport or "bz2" in compressionsupport):
33183407
wrapped = bz2.BZ2File(src)
3408+
wrapped.seek(0, 0)
33193409
elif kind in ("lzma","xz") and (("lzma" in compressionsupport) or ("xz" in compressionsupport)):
33203410
wrapped = lzma.LZMAFile(src)
3411+
wrapped.seek(0, 0)
33213412
elif kind == "zstd" and ("zstd" in compressionsupport or "zstandard" in compressionsupport):
33223413
if 'zstd' in compressionsupport:
33233414
wrapped = zstd.ZstdFile(src, mode="rb")
3415+
wrapped.seek(0, 0)
33243416
else:
33253417
return False
33263418
elif kind == "lz4" and "lz4" in compressionsupport:
33273419
wrapped = lz4.frame.LZ4FrameFile(src, mode="rb")
3420+
wrapped.seek(0, 0)
33283421
elif kind == "zlib" and "zlib" in compressionsupport:
33293422
wrapped = ZlibFile(fileobj=src, mode="rb")
3423+
wrapped.seek(0, 0)
33303424
else:
33313425
# Passthrough
33323426
wrapped = src
3333-
try:
3334-
wrapped.seek(filestart, 0)
3335-
except Exception:
3336-
pass
3337-
kind = "" # treat as uncompressed for logic below
3338-
3339-
# Positioning: start-of-member for compressed; filestart for passthrough
3340-
try:
3341-
if kind in compressionsupport:
3342-
wrapped.seek(0, 0)
3343-
else:
3344-
wrapped.seek(filestart, 0)
3345-
except Exception:
3346-
pass
3427+
wrapped.seek(filestart, 0)
33473428

33483429
return wrapped
33493430

@@ -3358,33 +3439,34 @@ def UncompressFile(infile, formatspecs=__file_format_multi_dict__, mode="rb",
33583439
# Compressed branches
33593440
if (compresscheck == "gzip" and "gzip" in compressionsupport):
33603441
fp = gzip.open(infile, mode)
3442+
fp.seek(0, 0)
33613443
elif (compresscheck == "bzip2" and "bzip2" in compressionsupport):
33623444
fp = bz2.open(infile, mode)
3445+
fp.seek(0, 0)
33633446
elif (compresscheck == "zstd" and "zstandard" in compressionsupport):
33643447
if 'zstd' in compressionsupport:
33653448
fp = zstd.ZstdFile(infile, mode=mode)
3449+
fp.seek(0, 0)
33663450
else:
33673451
return False
33683452
elif (compresscheck == "lz4" and "lz4" in compressionsupport):
33693453
fp = lz4.frame.open(infile, mode)
3454+
fp.seek(0, 0)
33703455
elif ((compresscheck == "lzma" or compresscheck == "xz") and "xz" in compressionsupport):
33713456
fp = lzma.open(infile, mode)
3457+
fp.seek(0, 0)
33723458
elif (compresscheck == "zlib" and "zlib" in compressionsupport):
33733459
fp = ZlibFile(infile, mode=mode)
3460+
fp.seek(0, 0)
33743461

33753462
# Uncompressed (or unknown): open plain file
33763463
else:
33773464
fp = open(infile, mode)
3465+
fp.seek(filestart, 0)
33783466

33793467
except FileNotFoundError:
33803468
return False
33813469

3382-
# Position to filestart if caller requested it (mainly for fileobj-based headers)
3383-
try:
3384-
fp.seek(0 if compresscheck else filestart, 0)
3385-
except Exception:
3386-
pass
3387-
33883470
return fp
33893471

33903472
def CompressOpenFileAlt(fp, compression="auto", compressionlevel=None,
@@ -4595,12 +4677,6 @@ def ReadFileDataWithContent(fp, filestart=0, listonly=False, contentasfile=False
45954677
return False
45964678
delimiter = formatspecs['format_delimiter']
45974679
curloc = filestart
4598-
try:
4599-
fp.seek(0, 2)
4600-
except (OSError, ValueError):
4601-
SeekToEndOfFile(fp)
4602-
CatSize = fp.tell()
4603-
CatSizeEnd = CatSize
46044680
fp.seek(curloc, 0)
46054681
inheaderver = str(int(formatspecs['format_ver'].replace(".", "")))
46064682
headeroffset = fp.tell()
@@ -4659,6 +4735,8 @@ def ReadFileDataWithContent(fp, filestart=0, listonly=False, contentasfile=False
46594735
break
46604736
flist.append(HeaderOut)
46614737
countnum = countnum + 1
4738+
CatSize = fp.tell()
4739+
CatSizeEnd = CatSize
46624740
return flist
46634741

46644742

@@ -4667,12 +4745,6 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
46674745
return False
46684746
delimiter = formatspecs['format_delimiter']
46694747
curloc = filestart
4670-
try:
4671-
fp.seek(0, 2)
4672-
except (OSError, ValueError):
4673-
SeekToEndOfFile(fp)
4674-
CatSize = fp.tell()
4675-
CatSizeEnd = CatSize
46764748
fp.seek(curloc, 0)
46774749
inheaderver = str(int(formatspecs['format_ver'].replace(".", "")))
46784750
headeroffset = fp.tell()
@@ -4834,7 +4906,7 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
48344906
return False
48354907
formversions = re.search('(.*?)(\\d+)', formstring).groups()
48364908
fcompresstype = ""
4837-
outlist = {'fnumfiles': fnumfiles, 'ffilestart': filestart, 'fformat': formversions[0], 'fcompression': fcompresstype, 'fencoding': fhencoding, 'fmtime': fheadmtime, 'fctime': fheadctime, 'fversion': formversions[1], 'fostype': fostype, 'fprojectname': fprojectname, 'fimptype': fpythontype, 'fheadersize': fheadsize, 'fsize': CatSizeEnd, 'fnumfields': fnumfields + 2, 'fformatspecs': formatspecs, 'fseeknextfile': fseeknextfile, 'fchecksumtype': fprechecksumtype, 'fheaderchecksum': fprechecksum, 'fjsonchecksumtype': fjsonchecksumtype, 'fjsontype': fjsontype, 'fjsonlen': fjsonlen, 'fjsonsize': fjsonsize, 'fjsonrawdata': fjsonrawcontent, 'fjsondata': fjsoncontent, 'fjstart': fjstart, 'fjend': fjend, 'fjsonchecksum': fjsonchecksum, 'frawheader': [formstring] + inheader, 'fextrafields': fnumextrafields, 'fextrafieldsize': fnumextrafieldsize, 'fextradata': fextrafieldslist, 'fvendorfields': fvendorfields, 'fvendordata': fvendorfieldslist, 'ffilelist': []}
4909+
outlist = {'fnumfiles': fnumfiles, 'ffilestart': filestart, 'fformat': formversions[0], 'fcompression': fcompresstype, 'fencoding': fhencoding, 'fmtime': fheadmtime, 'fctime': fheadctime, 'fversion': formversions[1], 'fostype': fostype, 'fprojectname': fprojectname, 'fimptype': fpythontype, 'fheadersize': fheadsize, 'fnumfields': fnumfields + 2, 'fformatspecs': formatspecs, 'fseeknextfile': fseeknextfile, 'fchecksumtype': fprechecksumtype, 'fheaderchecksum': fprechecksum, 'fjsonchecksumtype': fjsonchecksumtype, 'fjsontype': fjsontype, 'fjsonlen': fjsonlen, 'fjsonsize': fjsonsize, 'fjsonrawdata': fjsonrawcontent, 'fjsondata': fjsoncontent, 'fjstart': fjstart, 'fjend': fjend, 'fjsonchecksum': fjsonchecksum, 'frawheader': [formstring] + inheader, 'fextrafields': fnumextrafields, 'fextrafieldsize': fnumextrafieldsize, 'fextradata': fextrafieldslist, 'fvendorfields': fvendorfields, 'fvendordata': fvendorfieldslist, 'ffilelist': []}
48384910
if (seekstart < 0) or (seekstart > fnumfiles):
48394911
seekstart = 0
48404912
if (seekend == 0) or (seekend > fnumfiles) or (seekend < seekstart):
@@ -4917,15 +4989,17 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
49174989
il = il + 1
49184990
realidnum = 0
49194991
countnum = seekstart
4920-
while (fp.tell() < CatSizeEnd) if seektoend else (countnum < seekend):
4992+
while (countnum < seekend):
49214993
HeaderOut = ReadFileHeaderDataWithContentToArray(fp, listonly, contentasfile, uncompress, skipchecksum, formatspecs, saltkey)
49224994
if(len(HeaderOut) == 0):
49234995
break
49244996
HeaderOut.update({'fid': realidnum, 'fidalt': realidnum})
49254997
outlist['ffilelist'].append(HeaderOut)
49264998
countnum = countnum + 1
49274999
realidnum = realidnum + 1
4928-
outlist.update({'fp': fp})
5000+
CatSize = fp.tell()
5001+
CatSizeEnd = CatSize
5002+
outlist.update({'fp': fp, 'fsize': CatSizeEnd})
49295003
return outlist
49305004

49315005

@@ -4934,12 +5008,6 @@ def ReadFileDataWithContentToList(fp, filestart=0, seekstart=0, seekend=0, listo
49345008
return False
49355009
delimiter = formatspecs['format_delimiter']
49365010
curloc = filestart
4937-
try:
4938-
fp.seek(0, 2)
4939-
except (OSError, ValueError):
4940-
SeekToEndOfFile(fp)
4941-
CatSize = fp.tell()
4942-
CatSizeEnd = CatSize
49435011
fp.seek(curloc, 0)
49445012
inheaderver = str(int(formatspecs['format_ver'].replace(".", "")))
49455013
headeroffset = fp.tell()
@@ -5187,13 +5255,15 @@ def ReadFileDataWithContentToList(fp, filestart=0, seekstart=0, seekend=0, listo
51875255
il = il + 1
51885256
realidnum = 0
51895257
countnum = seekstart
5190-
while (fp.tell() < CatSizeEnd) if seektoend else (countnum < seekend):
5258+
while (countnum < seekend):
51915259
HeaderOut = ReadFileHeaderDataWithContentToList(fp, listonly, contentasfile, uncompress, skipchecksum, formatspecs, saltkey)
51925260
if(len(HeaderOut) == 0):
51935261
break
51945262
outlist.append(HeaderOut)
51955263
countnum = countnum + 1
51965264
realidnum = realidnum + 1
5265+
CatSize = fp.tell()
5266+
CatSizeEnd = CatSize
51975267
return outlist
51985268

51995269
def ReadInFileWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=0, seekend=0, listonly=False, contentasfile=True, uncompress=True, skipchecksum=False, formatspecs=__file_format_multi_dict__, saltkey=None, seektoend=False):
@@ -5275,17 +5345,11 @@ def ReadInFileWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=
52755345
currentfilepos = readfp.tell()
52765346
else:
52775347
infp = UncompressFileAlt(readfp, formatspecs, currentfilepos)
5348+
if(not infp):
5349+
break
52785350
infp.seek(0, 0)
52795351
currentinfilepos = infp.tell()
5280-
try:
5281-
infp.seek(0, 2)
5282-
except (OSError, ValueError):
5283-
SeekToEndOfFile(infp)
5284-
outinfsize = infp.tell()
5285-
infp.seek(currentinfilepos, 0)
52865352
while True:
5287-
if currentinfilepos >= outinfsize: # stop when function signals False
5288-
break
52895353
oldinfppos = infp.tell()
52905354
compresscheck = CheckCompressionType(infp, formatspecs, currentinfilepos, False)
52915355
if(IsNestedDict(formatspecs) and compresscheck in formatspecs):
@@ -5392,17 +5456,11 @@ def ReadInFileWithContentToList(infile, fmttype="auto", filestart=0, seekstart=0
53925456
currentfilepos = readfp.tell()
53935457
else:
53945458
infp = UncompressFileAlt(readfp, formatspecs, currentfilepos)
5459+
if(not infp):
5460+
break
53955461
infp.seek(0, 0)
53965462
currentinfilepos = infp.tell()
5397-
try:
5398-
infp.seek(0, 2)
5399-
except (OSError, ValueError):
5400-
SeekToEndOfFile(infp)
5401-
outinfsize = infp.tell()
5402-
infp.seek(currentinfilepos, 0)
54035463
while True:
5404-
if currentinfilepos >= outinfsize: # stop when function signals False
5405-
break
54065464
oldinfppos = infp.tell()
54075465
compresscheck = CheckCompressionType(infp, formatspecs, currentinfilepos, False)
54085466
if(IsNestedDict(formatspecs) and compresscheck in formatspecs):
@@ -8020,14 +8078,6 @@ def FoxFileValidate(infile, fmttype="auto", filestart=0, formatspecs=__file_form
80208078
return False
80218079
fp = UncompressFile(infile, formatspecs, "rb", filestart)
80228080

8023-
try:
8024-
fp.seek(0, 2)
8025-
except (OSError, ValueError):
8026-
SeekToEndOfFile(fp)
8027-
CatSize = fp.tell()
8028-
CatSizeEnd = CatSize
8029-
fp.seek(0)
8030-
fp.seek(curloc, 0)
80318081
if(IsNestedDict(formatspecs)):
80328082
compresschecking = CheckCompressionType(fp, formatspecs, filestart, False)
80338083
if(compresschecking not in formatspecs):
@@ -8124,7 +8174,7 @@ def FoxFileValidate(infile, fmttype="auto", filestart=0, formatspecs=__file_form
81248174
if(verbose):
81258175
VerbosePrintOut("")
81268176
# Iterate either until EOF (seektoend) or fixed count
8127-
while (fp.tell() < CatSizeEnd) if seektoend else (il < fnumfiles):
8177+
while (il < fnumfiles):
81288178
outfhstart = fp.tell()
81298179
if(__use_new_style__):
81308180
inheaderdata = ReadFileHeaderDataBySize(fp, formatspecs['format_delimiter'])

0 commit comments

Comments
 (0)