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+ "neofile.ini" ,
537+ "neofile.json" ,
538+ ])
539+
448540__file_format_multi_dict__ = {}
449541__file_format_default__ = "NeoFile"
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__ = "neofile.ini"
561+ __use_ini_name__ = os . path . join ( filecfgpath , "neofile.ini" )
470562__use_json_file__ = False
471- __use_json_name__ = "neofile.json"
563+ __use_json_name__ = os . path . join ( filecfgpath , "neofile.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__ ):
@@ -3298,45 +3390,34 @@ def UncompressFileAlt(fp, formatspecs=__file_format_multi_dict__, filestart=0):
32983390 if IsNestedDict (formatspecs ) and kind in formatspecs :
32993391 formatspecs = formatspecs [kind ]
33003392
3301- # Guard against detector side-effects: ensure we're back at filestart
3302- try :
3303- src .seek (filestart , 0 )
3304- except Exception :
3305- pass
3393+ src .seek (filestart , 0 )
33063394
33073395 # Build logical stream (or passthrough)
33083396 if kind == "gzip" and "gzip" in compressionsupport :
33093397 wrapped = gzip .GzipFile (fileobj = src , mode = "rb" )
3398+ wrapped .seek (0 , 0 )
33103399 elif kind == "bzip2" and ("bzip2" in compressionsupport or "bz2" in compressionsupport ):
33113400 wrapped = bz2 .BZ2File (src )
3401+ wrapped .seek (0 , 0 )
33123402 elif kind in ("lzma" ,"xz" ) and (("lzma" in compressionsupport ) or ("xz" in compressionsupport )):
33133403 wrapped = lzma .LZMAFile (src )
3404+ wrapped .seek (0 , 0 )
33143405 elif kind == "zstd" and ("zstd" in compressionsupport or "zstandard" in compressionsupport ):
33153406 if 'zstd' in compressionsupport :
33163407 wrapped = zstd .ZstdFile (src , mode = "rb" )
3408+ wrapped .seek (0 , 0 )
33173409 else :
33183410 return False
33193411 elif kind == "lz4" and "lz4" in compressionsupport :
33203412 wrapped = lz4 .frame .LZ4FrameFile (src , mode = "rb" )
3413+ wrapped .seek (0 , 0 )
33213414 elif kind == "zlib" and "zlib" in compressionsupport :
33223415 wrapped = ZlibFile (fileobj = src , mode = "rb" )
3416+ wrapped .seek (0 , 0 )
33233417 else :
33243418 # Passthrough
33253419 wrapped = src
3326- try :
3327- wrapped .seek (filestart , 0 )
3328- except Exception :
3329- pass
3330- kind = "" # treat as uncompressed for logic below
3331-
3332- # Positioning: start-of-member for compressed; filestart for passthrough
3333- try :
3334- if kind in compressionsupport :
3335- wrapped .seek (0 , 0 )
3336- else :
3337- wrapped .seek (filestart , 0 )
3338- except Exception :
3339- pass
3420+ wrapped .seek (filestart , 0 )
33403421
33413422 return wrapped
33423423
@@ -3351,33 +3432,34 @@ def UncompressFile(infile, formatspecs=__file_format_multi_dict__, mode="rb",
33513432 # Compressed branches
33523433 if (compresscheck == "gzip" and "gzip" in compressionsupport ):
33533434 fp = gzip .open (infile , mode )
3435+ fp .seek (0 , 0 )
33543436 elif (compresscheck == "bzip2" and "bzip2" in compressionsupport ):
33553437 fp = bz2 .open (infile , mode )
3438+ fp .seek (0 , 0 )
33563439 elif (compresscheck == "zstd" and "zstandard" in compressionsupport ):
33573440 if 'zstd' in compressionsupport :
33583441 fp = zstd .ZstdFile (infile , mode = mode )
3442+ fp .seek (0 , 0 )
33593443 else :
33603444 return False
33613445 elif (compresscheck == "lz4" and "lz4" in compressionsupport ):
33623446 fp = lz4 .frame .open (infile , mode )
3447+ fp .seek (0 , 0 )
33633448 elif ((compresscheck == "lzma" or compresscheck == "xz" ) and "xz" in compressionsupport ):
33643449 fp = lzma .open (infile , mode )
3450+ fp .seek (0 , 0 )
33653451 elif (compresscheck == "zlib" and "zlib" in compressionsupport ):
33663452 fp = ZlibFile (infile , mode = mode )
3453+ fp .seek (0 , 0 )
33673454
33683455 # Uncompressed (or unknown): open plain file
33693456 else :
33703457 fp = open (infile , mode )
3458+ fp .seek (filestart , 0 )
33713459
33723460 except FileNotFoundError :
33733461 return False
33743462
3375- # Position to filestart if caller requested it (mainly for fileobj-based headers)
3376- try :
3377- fp .seek (0 if compresscheck else filestart , 0 )
3378- except Exception :
3379- pass
3380-
33813463 return fp
33823464
33833465def CompressOpenFileAlt (fp , compression = "auto" , compressionlevel = None ,
@@ -4588,12 +4670,6 @@ def ReadFileDataWithContent(fp, filestart=0, listonly=False, contentasfile=False
45884670 return False
45894671 delimiter = formatspecs ['format_delimiter' ]
45904672 curloc = filestart
4591- try :
4592- fp .seek (0 , 2 )
4593- except (OSError , ValueError ):
4594- SeekToEndOfFile (fp )
4595- CatSize = fp .tell ()
4596- CatSizeEnd = CatSize
45974673 fp .seek (curloc , 0 )
45984674 inheaderver = str (int (formatspecs ['format_ver' ].replace ("." , "" )))
45994675 headeroffset = fp .tell ()
@@ -4652,6 +4728,8 @@ def ReadFileDataWithContent(fp, filestart=0, listonly=False, contentasfile=False
46524728 break
46534729 flist .append (HeaderOut )
46544730 countnum = countnum + 1
4731+ CatSize = fp .tell ()
4732+ CatSizeEnd = CatSize
46554733 return flist
46564734
46574735
@@ -4660,12 +4738,6 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
46604738 return False
46614739 delimiter = formatspecs ['format_delimiter' ]
46624740 curloc = filestart
4663- try :
4664- fp .seek (0 , 2 )
4665- except (OSError , ValueError ):
4666- SeekToEndOfFile (fp )
4667- CatSize = fp .tell ()
4668- CatSizeEnd = CatSize
46694741 fp .seek (curloc , 0 )
46704742 inheaderver = str (int (formatspecs ['format_ver' ].replace ("." , "" )))
46714743 headeroffset = fp .tell ()
@@ -4827,7 +4899,7 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
48274899 return False
48284900 formversions = re .search ('(.*?)(\\ d+)' , formstring ).groups ()
48294901 fcompresstype = ""
4830- 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' : []}
4902+ 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' : []}
48314903 if (seekstart < 0 ) or (seekstart > fnumfiles ):
48324904 seekstart = 0
48334905 if (seekend == 0 ) or (seekend > fnumfiles ) or (seekend < seekstart ):
@@ -4910,15 +4982,17 @@ def ReadFileDataWithContentToArray(fp, filestart=0, seekstart=0, seekend=0, list
49104982 il = il + 1
49114983 realidnum = 0
49124984 countnum = seekstart
4913- while (fp . tell () < CatSizeEnd ) if seektoend else ( countnum < seekend ):
4985+ while (countnum < seekend ):
49144986 HeaderOut = ReadFileHeaderDataWithContentToArray (fp , listonly , contentasfile , uncompress , skipchecksum , formatspecs , saltkey )
49154987 if (len (HeaderOut ) == 0 ):
49164988 break
49174989 HeaderOut .update ({'fid' : realidnum , 'fidalt' : realidnum })
49184990 outlist ['ffilelist' ].append (HeaderOut )
49194991 countnum = countnum + 1
49204992 realidnum = realidnum + 1
4921- outlist .update ({'fp' : fp })
4993+ CatSize = fp .tell ()
4994+ CatSizeEnd = CatSize
4995+ outlist .update ({'fp' : fp , 'fsize' : CatSizeEnd })
49224996 return outlist
49234997
49244998
@@ -4927,12 +5001,6 @@ def ReadFileDataWithContentToList(fp, filestart=0, seekstart=0, seekend=0, listo
49275001 return False
49285002 delimiter = formatspecs ['format_delimiter' ]
49295003 curloc = filestart
4930- try :
4931- fp .seek (0 , 2 )
4932- except (OSError , ValueError ):
4933- SeekToEndOfFile (fp )
4934- CatSize = fp .tell ()
4935- CatSizeEnd = CatSize
49365004 fp .seek (curloc , 0 )
49375005 inheaderver = str (int (formatspecs ['format_ver' ].replace ("." , "" )))
49385006 headeroffset = fp .tell ()
@@ -5180,13 +5248,15 @@ def ReadFileDataWithContentToList(fp, filestart=0, seekstart=0, seekend=0, listo
51805248 il = il + 1
51815249 realidnum = 0
51825250 countnum = seekstart
5183- while (fp . tell () < CatSizeEnd ) if seektoend else ( countnum < seekend ):
5251+ while (countnum < seekend ):
51845252 HeaderOut = ReadFileHeaderDataWithContentToList (fp , listonly , contentasfile , uncompress , skipchecksum , formatspecs , saltkey )
51855253 if (len (HeaderOut ) == 0 ):
51865254 break
51875255 outlist .append (HeaderOut )
51885256 countnum = countnum + 1
51895257 realidnum = realidnum + 1
5258+ CatSize = fp .tell ()
5259+ CatSizeEnd = CatSize
51905260 return outlist
51915261
51925262def 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 ):
@@ -5268,17 +5338,11 @@ def ReadInFileWithContentToArray(infile, fmttype="auto", filestart=0, seekstart=
52685338 currentfilepos = readfp .tell ()
52695339 else :
52705340 infp = UncompressFileAlt (readfp , formatspecs , currentfilepos )
5341+ if (not infp ):
5342+ break
52715343 infp .seek (0 , 0 )
52725344 currentinfilepos = infp .tell ()
5273- try :
5274- infp .seek (0 , 2 )
5275- except (OSError , ValueError ):
5276- SeekToEndOfFile (infp )
5277- outinfsize = infp .tell ()
5278- infp .seek (currentinfilepos , 0 )
52795345 while True :
5280- if currentinfilepos >= outinfsize : # stop when function signals False
5281- break
52825346 oldinfppos = infp .tell ()
52835347 compresscheck = CheckCompressionType (infp , formatspecs , currentinfilepos , False )
52845348 if (IsNestedDict (formatspecs ) and compresscheck in formatspecs ):
@@ -5385,17 +5449,11 @@ def ReadInFileWithContentToList(infile, fmttype="auto", filestart=0, seekstart=0
53855449 currentfilepos = readfp .tell ()
53865450 else :
53875451 infp = UncompressFileAlt (readfp , formatspecs , currentfilepos )
5452+ if (not infp ):
5453+ break
53885454 infp .seek (0 , 0 )
53895455 currentinfilepos = infp .tell ()
5390- try :
5391- infp .seek (0 , 2 )
5392- except (OSError , ValueError ):
5393- SeekToEndOfFile (infp )
5394- outinfsize = infp .tell ()
5395- infp .seek (currentinfilepos , 0 )
53965456 while True :
5397- if currentinfilepos >= outinfsize : # stop when function signals False
5398- break
53995457 oldinfppos = infp .tell ()
54005458 compresscheck = CheckCompressionType (infp , formatspecs , currentinfilepos , False )
54015459 if (IsNestedDict (formatspecs ) and compresscheck in formatspecs ):
@@ -8013,14 +8071,6 @@ def NeoFileValidate(infile, fmttype="auto", filestart=0, formatspecs=__file_form
80138071 return False
80148072 fp = UncompressFile (infile , formatspecs , "rb" , filestart )
80158073
8016- try :
8017- fp .seek (0 , 2 )
8018- except (OSError , ValueError ):
8019- SeekToEndOfFile (fp )
8020- CatSize = fp .tell ()
8021- CatSizeEnd = CatSize
8022- fp .seek (0 )
8023- fp .seek (curloc , 0 )
80248074 if (IsNestedDict (formatspecs )):
80258075 compresschecking = CheckCompressionType (fp , formatspecs , filestart , False )
80268076 if (compresschecking not in formatspecs ):
@@ -8117,7 +8167,7 @@ def NeoFileValidate(infile, fmttype="auto", filestart=0, formatspecs=__file_form
81178167 if (verbose ):
81188168 VerbosePrintOut ("" )
81198169 # Iterate either until EOF (seektoend) or fixed count
8120- while (fp . tell () < CatSizeEnd ) if seektoend else ( il < fnumfiles ):
8170+ while (il < fnumfiles ):
81218171 outfhstart = fp .tell ()
81228172 if (__use_new_style__ ):
81238173 inheaderdata = ReadFileHeaderDataBySize (fp , formatspecs ['format_delimiter' ])
0 commit comments