@@ -696,6 +696,18 @@ def __init__(self, file, pos, close, lock, writing):
696
696
self ._close = close
697
697
self ._lock = lock
698
698
self ._writing = writing
699
+ self .seekable = file .seekable
700
+ self .tell = file .tell
701
+
702
+ def seek (self , offset , whence = 0 ):
703
+ with self ._lock :
704
+ if self .writing ():
705
+ raise ValueError ("Can't reposition in the ZIP file while "
706
+ "there is an open writing handle on it. "
707
+ "Close the writing handle before trying to read." )
708
+ self ._file .seek (self ._pos )
709
+ self ._pos = self ._file .tell ()
710
+ return self ._pos
699
711
700
712
def read (self , n = - 1 ):
701
713
with self ._lock :
@@ -746,6 +758,9 @@ class ZipExtFile(io.BufferedIOBase):
746
758
# Read from compressed files in 4k blocks.
747
759
MIN_READ_SIZE = 4096
748
760
761
+ # Chunk size to read during seek
762
+ MAX_SEEK_READ = 1 << 24
763
+
749
764
def __init__ (self , fileobj , mode , zipinfo , decrypter = None ,
750
765
close_fileobj = False ):
751
766
self ._fileobj = fileobj
@@ -778,6 +793,17 @@ def __init__(self, fileobj, mode, zipinfo, decrypter=None,
778
793
else :
779
794
self ._expected_crc = None
780
795
796
+ self ._seekable = False
797
+ try :
798
+ if fileobj .seekable ():
799
+ self ._orig_compress_start = fileobj .tell ()
800
+ self ._orig_compress_size = zipinfo .compress_size
801
+ self ._orig_file_size = zipinfo .file_size
802
+ self ._orig_start_crc = self ._running_crc
803
+ self ._seekable = True
804
+ except AttributeError :
805
+ pass
806
+
781
807
def __repr__ (self ):
782
808
result = ['<%s.%s' % (self .__class__ .__module__ ,
783
809
self .__class__ .__qualname__ )]
@@ -963,6 +989,62 @@ def close(self):
963
989
finally :
964
990
super ().close ()
965
991
992
+ def seekable (self ):
993
+ return self ._seekable
994
+
995
+ def seek (self , offset , whence = 0 ):
996
+ if not self ._seekable :
997
+ raise io .UnsupportedOperation ("underlying stream is not seekable" )
998
+ curr_pos = self .tell ()
999
+ if whence == 0 : # Seek from start of file
1000
+ new_pos = offset
1001
+ elif whence == 1 : # Seek from current position
1002
+ new_pos = curr_pos + offset
1003
+ elif whence == 2 : # Seek from EOF
1004
+ new_pos = self ._orig_file_size + offset
1005
+ else :
1006
+ raise ValueError ("whence must be os.SEEK_SET (0), "
1007
+ "os.SEEK_CUR (1), or os.SEEK_END (2)" )
1008
+
1009
+ if new_pos > self ._orig_file_size :
1010
+ new_pos = self ._orig_file_size
1011
+
1012
+ if new_pos < 0 :
1013
+ new_pos = 0
1014
+
1015
+ read_offset = new_pos - curr_pos
1016
+ buff_offset = read_offset + self ._offset
1017
+
1018
+ if buff_offset >= 0 and buff_offset < len (self ._readbuffer ):
1019
+ # Just move the _offset index if the new position is in the _readbuffer
1020
+ self ._offset = buff_offset
1021
+ read_offset = 0
1022
+ elif read_offset < 0 :
1023
+ # Position is before the current position. Reset the ZipExtFile
1024
+
1025
+ self ._fileobj .seek (self ._orig_compress_start )
1026
+ self ._running_crc = self ._orig_start_crc
1027
+ self ._compress_left = self ._orig_compress_size
1028
+ self ._left = self ._orig_file_size
1029
+ self ._readbuffer = b''
1030
+ self ._offset = 0
1031
+ self ._decompressor = zipfile ._get_decompressor (self ._compress_type )
1032
+ self ._eof = False
1033
+ read_offset = new_pos
1034
+
1035
+ while read_offset > 0 :
1036
+ read_len = min (self .MAX_SEEK_READ , read_offset )
1037
+ self .read (read_len )
1038
+ read_offset -= read_len
1039
+
1040
+ return self .tell ()
1041
+
1042
+ def tell (self ):
1043
+ if not self ._seekable :
1044
+ raise io .UnsupportedOperation ("underlying stream is not seekable" )
1045
+ filepos = self ._orig_file_size - self ._left - len (self ._readbuffer ) + self ._offset
1046
+ return filepos
1047
+
966
1048
967
1049
class _ZipWriteFile (io .BufferedIOBase ):
968
1050
def __init__ (self , zf , zinfo , zip64 ):
0 commit comments