2525import hmac
2626import json
2727import stat
28+ import atexit
2829import shutil
2930import base64
3031import 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" )
472564if (__use_ini_file__ and __use_json_file__ ):
473565 __use_json_file__ = False
474566if ('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
33903472def 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
51995269def 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