Inbox because ZipArchiveMember >> #extractInDirectory:overwrite: is a breaking change that *hypothetically* could break any of your automated extraction workflows, even though I do not really expect that. Please check. If no one objects, I will move this to the Trunk after one week or so. :-)<br>
<br>
Best,<br>
Christoph<br>
<br>
<font color="#808080">---<br>
</font><font color="#808080"><i>Sent from </i></font><font color="#808080"><i><a href="https://github.com/hpi-swa-lab/squeak-inbox-talk"><u><font color="#808080">Squeak Inbox Talk</font></u></a></i></font><br>
<br>
On 2022-01-03T18:07:40+00:00, commits@source.squeak.org wrote:<br>
<br>
> A new version of Compression was added to project The Inbox:<br>
> http://source.squeak.org/inbox/Compression-ct.56.mcz<br>
> <br>
> ==================== Summary ====================<br>
> <br>
> Name: Compression-ct.56<br>
> Author: ct<br>
> Time: 3 January 2022, 7:07:37.018703 pm<br>
> UUID: af841abb-44db-044e-b8d8-a6e8231c690e<br>
> Ancestors: Compression-dtl.55<br>
> <br>
> Makes the Compression package 100% multilingual.<br>
> Rewrites ZipArchiveMember >> #extractInDirectory:overwrite: to use new UIManager >> #chooseFromLabeledValues:title: and avoid infinite loops if the user closes the dialog window or #valueSuppressingAllMessages is used.<br>
> Also includes some recategorizations.<br>
> <br>
> =============== Diff against Compression-dtl.55 ===============<br>
> <br>
> Item was changed:<br>
> ----- Method: Archive>>writeToFileNamed: (in category 'archive operations') -----<br>
> writeToFileNamed: aFileName<br>
> | stream |<br>
> "Catch attempts to overwrite existing zip file"<br>
> (self canWriteToFileNamed: aFileName)<br>
> + ifFalse: [ ^self error: ('{1} is needed by one or more members in this archive' translated format: {aFileName}) ].<br>
> - ifFalse: [ ^self error: (aFileName, ' is needed by one or more members in this archive') ].<br>
> stream := StandardFileStream forceNewFileNamed: aFileName.<br>
> self writeTo: stream.<br>
> stream close.!<br>
> <br>
> Item was changed:<br>
> + ----- Method: ArchiveMember class>>newDirectoryNamed: (in category 'instance creation') -----<br>
> - ----- Method: ArchiveMember class>>newDirectoryNamed: (in category 'as yet unclassified') -----<br>
> newDirectoryNamed: aString<br>
> self subclassResponsibility!<br>
> <br>
> Item was changed:<br>
> + ----- Method: ArchiveMember class>>newFromFile: (in category 'instance creation') -----<br>
> - ----- Method: ArchiveMember class>>newFromFile: (in category 'as yet unclassified') -----<br>
> newFromFile: aFileName<br>
> self subclassResponsibility!<br>
> <br>
> Item was changed:<br>
> + ----- Method: ArchiveMember class>>newFromString: (in category 'instance creation') -----<br>
> - ----- Method: ArchiveMember class>>newFromString: (in category 'as yet unclassified') -----<br>
> newFromString: aString<br>
> self subclassResponsibility!<br>
> <br>
> Item was changed:<br>
> + ----- Method: CRCError>>isResumable (in category 'priv handling') -----<br>
> - ----- Method: CRCError>>isResumable (in category 'as yet unclassified') -----<br>
> isResumable<br>
> ^true!<br>
> <br>
> Item was changed:<br>
> + ----- Method: CompressedSourceStream class>>on: (in category 'instance creation') -----<br>
> - ----- Method: CompressedSourceStream class>>on: (in category 'as yet unclassified') -----<br>
> on: aFile<br>
> ^ self basicNew openOn: aFile!<br>
> <br>
> Item was changed:<br>
> ----- Method: CompressedSourceStream>>binary (in category 'open/close') -----<br>
> binary<br>
> + self error: 'Compressed source files are ascii to the user (though binary underneath)' translated!<br>
> - self error: 'Compressed source files are ascii to the user (though binary underneath)'!<br>
> <br>
> Item was changed:<br>
> ----- Method: CompressedSourceStream>>position: (in category 'access') -----<br>
> position: newPosition<br>
> | compressedBuffer newSegmentIndex |<br>
> newPosition > endOfFile ifTrue:<br>
> + [self error: 'Attempt to position beyond the end of file' translated].<br>
> - [self error: 'Attempt to position beyond the end of file'].<br>
> newSegmentIndex := (newPosition // segmentSize) + 1.<br>
> newSegmentIndex ~= segmentIndex ifTrue:<br>
> [self flush.<br>
> segmentIndex := newSegmentIndex.<br>
> newSegmentIndex > nSegments ifTrue:<br>
> + [self error: 'file size limit exceeded' translated].<br>
> - [self error: 'file size limit exceeded'].<br>
> segmentFile position: (segmentTable at: segmentIndex).<br>
> (segmentTable at: segmentIndex+1) = 0<br>
> ifTrue:<br>
> [newPosition ~= endOfFile ifTrue:<br>
> + [self error: 'Internal logic error' translated].<br>
> - [self error: 'Internal logic error'].<br>
> collection size = segmentSize ifFalse:<br>
> + [self error: 'Internal logic error' translated].<br>
> - [self error: 'Internal logic error'].<br>
> "just leave garbage beyond end of file"]<br>
> ifFalse:<br>
> [compressedBuffer := segmentFile next: ((segmentTable at: segmentIndex+1) - (segmentTable at: segmentIndex)).<br>
> collection := (GZipReadStream on: compressedBuffer) upToEnd asString].<br>
> readLimit := collection size min: endOfFile - self segmentOffset].<br>
> + position := newPosition \\ segmentSize.!<br>
> - position := newPosition \\ segmentSize.<br>
> - !<br>
> <br>
> Item was changed:<br>
> ----- Method: CompressedSourceStream>>readHeaderInfo (in category 'open/close') -----<br>
> readHeaderInfo<br>
> | valid a b |<br>
> segmentFile position: 0.<br>
> segmentSize := segmentFile nextNumber: 4.<br>
> nSegments := segmentFile nextNumber: 4.<br>
> endOfFile := segmentFile nextNumber: 4.<br>
> segmentFile size < (nSegments+1 + 3 * 4) ifTrue: "Check for reasonable segment info"<br>
> + [self error: 'This file is not in valid compressed source format' translated].<br>
> - [self error: 'This file is not in valid compressed source format'].<br>
> segmentTable := (1 to: nSegments+1) collect: [:x | segmentFile nextNumber: 4].<br>
> segmentTable first ~= self firstSegmentLoc ifTrue:<br>
> + [self error: 'This file is not in valid compressed source format' translated].<br>
> - [self error: 'This file is not in valid compressed source format'].<br>
> valid := true.<br>
> 1 to: nSegments do: "Check that segment offsets are ascending"<br>
> [:i | a := segmentTable at: i. b := segmentTable at: i+1.<br>
> (a = 0 and: [b ~= 0]) ifTrue: [valid := false].<br>
> (a ~= 0 and: [b ~= 0]) ifTrue: [b <= a ifTrue: [valid := false]]].<br>
> valid ifFalse:<br>
> + [self error: 'This file is not in valid compressed source format' translated].<br>
> - [self error: 'This file is not in valid compressed source format'].<br>
> dirty := false.<br>
> self position: 0.!<br>
> <br>
> Item was changed:<br>
> ----- Method: CompressedSourceStream>>segmentSize:maxSize: (in category 'private') -----<br>
> segmentSize: segSize maxSize: maxSize<br>
> "Note that this method can be called after the initial open, provided that no<br>
> writing has yet taken place. This is how to override the default segmentation."<br>
> + self size = 0 ifFalse: [self error: 'Cannot set parameters after the first write' translated].<br>
> - self size = 0 ifFalse: [self error: 'Cannot set parameters after the first write'].<br>
> segmentFile position: 0.<br>
> segmentFile nextNumber: 4 put: (segmentSize := segSize).<br>
> segmentFile nextNumber: 4 put: (nSegments := maxSize // segSize + 2).<br>
> segmentFile nextNumber: 4 put: (endOfFile := 0).<br>
> segmentTable := Array new: nSegments+1 withAll: 0.<br>
> segmentTable at: 1 put: self firstSegmentLoc. "Loc of first segment, always."<br>
> segmentTable do: [:i | segmentFile nextNumber: 4 put: i].<br>
> segmentIndex := 1.<br>
> collection := String new: segmentSize.<br>
> writeLimit := segmentSize.<br>
> readLimit := 0.<br>
> position := 0.<br>
> endOfFile := 0.<br>
> + self writeSegment.!<br>
> - self writeSegment.<br>
> - !<br>
> <br>
> Item was changed:<br>
> ----- Method: DeflateStream>>validateMatchAt:from:to: (in category 'deflating') -----<br>
> validateMatchAt: pos from: startPos to: endPos<br>
> | here |<br>
> here := pos.<br>
> startPos+1 to: endPos+1 do:[:i|<br>
> (collection at: i) = (collection at: (here := here + 1))<br>
> + ifFalse:[^self error: 'Not a match' translated]].<br>
> - ifFalse:[^self error:'Not a match']].<br>
> ^true!<br>
> <br>
> Item was changed:<br>
> ----- Method: GZipReadStream class>>saveContents: (in category 'fileIn/Out') -----<br>
> saveContents: fullFileName<br>
> "Save the contents of a gzipped file"<br>
> | zipped buffer unzipped newName |<br>
> newName := fullFileName copyUpToLast: FileDirectory extensionDelimiter.<br>
> unzipped := FileStream newFileNamed: newName.<br>
> unzipped binary.<br>
> zipped := GZipReadStream on: (FileStream readOnlyFileNamed: fullFileName).<br>
> buffer := ByteArray new: 50000.<br>
> + ('Extracting {1}' translated format: {fullFileName})<br>
> - 'Extracting ' , fullFileName<br>
> displayProgressFrom: 0<br>
> to: zipped sourceStream size<br>
> during: <br>
> [:bar | <br>
> [zipped atEnd]<br>
> whileFalse: <br>
> [bar value: zipped sourceStream position.<br>
> unzipped nextPutAll: (zipped nextInto: buffer)].<br>
> zipped close.<br>
> unzipped close].<br>
> ^ newName!<br>
> <br>
> Item was changed:<br>
> ----- Method: GZipReadStream>>on:from:to: (in category 'initialize') -----<br>
> on: aCollection from: firstIndex to: lastIndex<br>
> "Check the header of the GZIP stream."<br>
> | method magic flags length |<br>
> super on: aCollection from: firstIndex to: lastIndex.<br>
> crc := 16rFFFFFFFF.<br>
> magic := self nextBits: 16.<br>
> (magic = GZipMagic) <br>
> + ifFalse:[^self error: 'Not a GZipped stream' translated].<br>
> - ifFalse:[^self error:'Not a GZipped stream'].<br>
> method := self nextBits: 8.<br>
> (method = GZipDeflated)<br>
> + ifFalse:[^self error: 'Bad compression method' translated].<br>
> - ifFalse:[^self error:'Bad compression method'].<br>
> flags := self nextBits: 8.<br>
> (flags anyMask: GZipEncryptFlag) <br>
> + ifTrue:[^self error: 'Cannot decompress encrypted stream' translated].<br>
> - ifTrue:[^self error:'Cannot decompress encrypted stream'].<br>
> (flags anyMask: GZipReservedFlags)<br>
> + ifTrue:[^self error: 'Cannot decompress stream with unknown flags' translated].<br>
> - ifTrue:[^self error:'Cannot decompress stream with unknown flags'].<br>
> "Ignore stamp, extra flags, OS type"<br>
> self nextBits: 16; nextBits: 16. "stamp"<br>
> self nextBits: 8. "extra flags"<br>
> self nextBits: 8. "OS type"<br>
> (flags anyMask: GZipContinueFlag) "Number of multi-part archive - ignored"<br>
> ifTrue:[self nextBits: 16]. <br>
> (flags anyMask: GZipExtraField) "Extra fields - ignored"<br>
> ifTrue:[ length := self nextBits: 16.<br>
> 1 to: length do:[:i| self nextBits: 8]].<br>
> (flags anyMask: GZipNameFlag) "Original file name - ignored"<br>
> ifTrue:[[(self nextBits: 8) = 0] whileFalse].<br>
> (flags anyMask: GZipCommentFlag) "Comment - ignored"<br>
> + ifTrue:[[(self nextBits: 8) = 0] whileFalse].!<br>
> - ifTrue:[[(self nextBits: 8) = 0] whileFalse].<br>
> - !<br>
> <br>
> Item was changed:<br>
> ----- Method: GZipReadStream>>verifyCrc (in category 'crc') -----<br>
> verifyCrc<br>
> | stored |<br>
> stored := 0.<br>
> 0 to: 24 by: 8 do: [ :i |<br>
> + sourcePos >= sourceLimit ifTrue: [ ^ self crcError: 'No checksum (proceed to ignore)' translated ].<br>
> - sourcePos >= sourceLimit ifTrue: [ ^ self crcError: 'No checksum (proceed to ignore)' ].<br>
> stored := stored + (self nextByte bitShift: i) ].<br>
> stored := stored bitXor: 16rFFFFFFFF.<br>
> + stored = crc ifFalse: [ ^ self crcError: 'Wrong checksum (proceed to ignore)' translated ].<br>
> - stored = crc ifFalse: [ ^ self crcError: 'Wrong checksum (proceed to ignore)' ].<br>
> ^stored!<br>
> <br>
> Item was changed:<br>
> + ----- Method: GZipSurrogateStream class>>newFileNamed:inDirectory: (in category 'instance creation') -----<br>
> - ----- Method: GZipSurrogateStream class>>newFileNamed:inDirectory: (in category 'as yet unclassified') -----<br>
> newFileNamed: fName inDirectory: aDirectory<br>
> <br>
> ^self new newFileNamed: fName inDirectory: aDirectory!<br>
> <br>
> Item was changed:<br>
> ----- Method: InflateStream>>decodeDynamicTable:from: (in category 'huffman trees') -----<br>
> decodeDynamicTable: nItems from: aHuffmanTable<br>
> "Decode the code length of the literal/length and distance table<br>
> in a block compressed with dynamic huffman trees"<br>
> | values index value repCount theValue |<br>
> values := Array new: nItems.<br>
> index := 1.<br>
> theValue := 0.<br>
> [index <= nItems] whileTrue:[<br>
> value := self decodeValueFrom: aHuffmanTable.<br>
> value < 16 ifTrue:[<br>
> "Immediate values"<br>
> theValue := value.<br>
> values at: index put: value.<br>
> index := index+1.<br>
> ] ifFalse:[<br>
> "Repeated values"<br>
> value = 16 ifTrue:[<br>
> "Repeat last value"<br>
> repCount := (self nextBits: 2) + 3.<br>
> ] ifFalse:[<br>
> "Repeat zero value"<br>
> theValue := 0.<br>
> value = 17 <br>
> ifTrue:[repCount := (self nextBits: 3) + 3]<br>
> ifFalse:[value = 18 <br>
> ifTrue:[repCount := (self nextBits: 7) + 11]<br>
> + ifFalse:[^self error: 'Invalid bits tree value' translated]]].<br>
> - ifFalse:[^self error:'Invalid bits tree value']]].<br>
> 0 to: repCount-1 do:[:i| values at: index+i put: theValue].<br>
> index := index + repCount].<br>
> ].<br>
> ^values!<br>
> <br>
> Item was changed:<br>
> ----- Method: InflateStream>>decodeValueFrom: (in category 'inflating') -----<br>
> decodeValueFrom: table<br>
> "Decode the next value in the receiver using the given huffman table."<br>
> | bits bitsNeeded tableIndex value |<br>
> bitsNeeded := (table at: 1) bitShift: -24. "Initial bits needed"<br>
> tableIndex := 2. "First real table"<br>
> [bits := self nextSingleBits: bitsNeeded. "Get bits"<br>
> value := table at: (tableIndex + bits). "Lookup entry in table"<br>
> (value bitAnd: 16r3F000000) = 0] "Check if it is a non-leaf node"<br>
> whileFalse:["Fetch sub table"<br>
> tableIndex := value bitAnd: 16rFFFF. "Table offset in low 16 bit"<br>
> bitsNeeded := (value bitShift: -24) bitAnd: 255. "Additional bits in high 8 bit"<br>
> + bitsNeeded > MaxBits ifTrue:[^self error: 'Invalid huffman table entry' translated]].<br>
> - bitsNeeded > MaxBits ifTrue:[^self error:'Invalid huffman table entry']].<br>
> ^value!<br>
> <br>
> Item was changed:<br>
> ----- Method: InflateStream>>proceedStoredBlock (in category 'inflating') -----<br>
> proceedStoredBlock<br>
> "Proceed decompressing a stored (e.g., uncompressed) block"<br>
> | length decoded |<br>
> "Literal table must be nil for a stored block"<br>
> + litTable == nil ifFalse:[^self error: 'Bad state' translated].<br>
> - litTable == nil ifFalse:[^self error:'Bad state'].<br>
> length := distTable.<br>
> [length > 0 and:[readLimit < collection size and:[sourcePos < sourceLimit]]] <br>
> whileTrue:[<br>
> collection at: (readLimit := readLimit + 1) put: <br>
> (source at: (sourcePos := sourcePos + 1)).<br>
> length := length - 1].<br>
> length = 0 ifTrue:[state := state bitAnd: StateNoMoreData].<br>
> decoded := length - distTable.<br>
> distTable := length.<br>
> ^decoded!<br>
> <br>
> Item was changed:<br>
> ----- Method: InflateStream>>processStoredBlock (in category 'inflating') -----<br>
> processStoredBlock<br>
> | chkSum length |<br>
> "Skip to byte boundary"<br>
> self nextBits: (bitPos bitAnd: 7).<br>
> length := self nextBits: 16.<br>
> chkSum := self nextBits: 16.<br>
> (chkSum bitXor: 16rFFFF) = length<br>
> + ifFalse:[^self error: 'Bad block length' translated].<br>
> - ifFalse:[^self error:'Bad block length'].<br>
> litTable := nil.<br>
> distTable := length.<br>
> state := state bitOr: BlockProceedBit.<br>
> ^self proceedStoredBlock!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZLibReadStream>>on:from:to: (in category 'initialize') -----<br>
> on: aCollection from: firstIndex to: lastIndex<br>
> "Check the header of the ZLib stream."<br>
> | method byte |<br>
> super on: aCollection from: firstIndex to: lastIndex.<br>
> crc := 1.<br>
> method := self nextBits: 8.<br>
> + (method bitAnd: 15) = 8 ifFalse:[^self error: 'Unknown compression method' translated].<br>
> + (method bitShift: -4) + 8 > 15 ifTrue:[^self error: 'Invalid window size' translated].<br>
> - (method bitAnd: 15) = 8 ifFalse:[^self error:'Unknown compression method'].<br>
> - (method bitShift: -4) + 8 > 15 ifTrue:[^self error:'Invalid window size'].<br>
> byte := self nextBits: 8.<br>
> + (method bitShift: 8) + byte \\ 31 = 0 ifFalse:[^self error: 'Incorrect header' translated].<br>
> + (byte anyMask: 32) ifTrue:[^self error: 'Need preset dictionary' translated].!<br>
> - (method bitShift: 8) + byte \\ 31 = 0 ifFalse:[^self error:'Incorrect header'].<br>
> - (byte anyMask: 32) ifTrue:[^self error:'Need preset dictionary'].<br>
> - !<br>
> <br>
> Item was changed:<br>
> ----- Method: ZLibReadStream>>verifyCrc (in category 'crc') -----<br>
> verifyCrc<br>
> | stored |<br>
> stored := 0.<br>
> 24 to: 0 by: -8 do: [ :i |<br>
> + sourcePos >= sourceLimit ifTrue: [ ^ self crcError: 'No checksum (proceed to ignore)' translated ].<br>
> - sourcePos >= sourceLimit ifTrue: [ ^ self crcError: 'No checksum (proceed to ignore)' ].<br>
> stored := stored + (self nextByte bitShift: i) ].<br>
> + stored = crc ifFalse: [ ^ self crcError: 'Wrong checksum (proceed to ignore)' translated ].<br>
> - stored = crc ifFalse: [ ^ self crcError: 'Wrong checksum (proceed to ignore)' ].<br>
> ^stored!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive class>>findEndOfCentralDirectoryFrom: (in category 'constants') -----<br>
> findEndOfCentralDirectoryFrom: stream<br>
> "Seek in the given stream to the end, then read backwards until we find the<br>
> signature of the central directory record. Leave the file positioned right<br>
> before the signature.<br>
> <br>
> Answers the file position of the EOCD, or 0 if not found."<br>
> <br>
> | data fileLength seekOffset pos maxOffset |<br>
> stream setToEnd.<br>
> fileLength := stream position.<br>
> "If the file length is less than 18 for the EOCD length plus 4 for the signature, we have a problem"<br>
> + fileLength < 22 ifTrue: [^ self error: ('file is too short: {1}' translated format: {stream name})].<br>
> - fileLength < 22 ifTrue: [^ self error: 'file is too short: ', stream name].<br>
> <br>
> seekOffset := 0.<br>
> pos := 0.<br>
> data := ByteArray new: 4100.<br>
> maxOffset := 40960 min: fileLength. "limit search range to 40K"<br>
> <br>
> [<br>
> seekOffset := (seekOffset + 4096) min: fileLength.<br>
> stream position: fileLength - seekOffset.<br>
> data := stream next: (4100 min: seekOffset) into: data startingAt: 1.<br>
> pos := self lastIndexOfPKSignature: EndOfCentralDirectorySignature in: data.<br>
> pos = 0 and: [seekOffset < maxOffset]<br>
> ] whileTrue.<br>
> <br>
> ^ pos > 0<br>
> ifTrue: [ | newPos | stream position: (newPos := (stream position + pos - seekOffset - 1)). newPos]<br>
> ifFalse: [0]!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>extractAllTo:informing:overwrite: (in category 'archive operations') -----<br>
> extractAllTo: aDirectory informing: bar overwrite: allOverwrite<br>
> "Extract all elements to the given directory"<br>
> | overwriteAll |<br>
> overwriteAll := allOverwrite.<br>
> self members do:[:entry| | dir |<br>
> entry isDirectory ifTrue:[<br>
> + bar ifNotNil: [bar value: ('Creating {1}' translated format: {entry fileName})].<br>
> - bar ifNotNil:[bar value: 'Creating ', entry fileName].<br>
> dir := (entry fileName findTokens:'/') <br>
> inject: aDirectory into:[:base :part| base directoryNamed: part].<br>
> dir assureExistence.<br>
> ].<br>
> ].<br>
> self members do:[:entry| | response |<br>
> entry isDirectory ifFalse:[<br>
> + bar ifNotNil: [bar value: ('Extracting {1}' translated format: {entry fileName})].<br>
> - bar ifNotNil:[bar value: 'Extracting ', entry fileName].<br>
> response := entry extractInDirectory: aDirectory overwrite: overwriteAll.<br>
> response == #retryWithOverwrite ifTrue:[<br>
> overwriteAll := true.<br>
> response := entry extractInDirectory: aDirectory overwrite: overwriteAll.<br>
> ].<br>
> response == #abort ifTrue:[^self].<br>
> response == #failed ifTrue:[<br>
> + (self confirm: ('Failed to extract {1}. Proceed?' translated format: {entry fileName})) ifFalse: [^self].<br>
> - (self confirm: 'Failed to extract ', entry fileName, '. Proceed?') ifFalse:[^self].<br>
> ].<br>
> ].<br>
> + ].!<br>
> - ].<br>
> - !<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>readEndOfCentralDirectoryFrom: (in category 'private') -----<br>
> readEndOfCentralDirectoryFrom: aStream<br>
> "Read EOCD, starting from position before signature."<br>
> | signature zipFileCommentLength |<br>
> signature := self readSignatureFrom: aStream.<br>
> + signature = EndOfCentralDirectorySignature ifFalse: [ ^self error: ('bad signature at {1}' translated format: {aStream position}) ].<br>
> - signature = EndOfCentralDirectorySignature ifFalse: [ ^self error: 'bad signature at ', aStream position printString ].<br>
> <br>
> aStream nextLittleEndianNumber: 2. "# of this disk"<br>
> aStream nextLittleEndianNumber: 2. "# of disk with central dir start"<br>
> aStream nextLittleEndianNumber: 2. "# of entries in central dir on this disk"<br>
> aStream nextLittleEndianNumber: 2. "total # of entries in central dir"<br>
> centralDirectorySize := aStream nextLittleEndianNumber: 4. "size of central directory"<br>
> centralDirectoryOffsetWRTStartingDiskNumber := aStream nextLittleEndianNumber: 4. "offset of start of central directory"<br>
> zipFileCommentLength := aStream nextLittleEndianNumber: 2. "zip file comment"<br>
> + zipFileComment := aStream next: zipFileCommentLength.!<br>
> - zipFileComment := aStream next: zipFileCommentLength.<br>
> - !<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>readFrom: (in category 'reading') -----<br>
> readFrom: aStreamOrFileName<br>
> | stream name eocdPosition |<br>
> stream := aStreamOrFileName isStream<br>
> ifTrue: [name := aStreamOrFileName name. aStreamOrFileName]<br>
> ifFalse: [StandardFileStream readOnlyFileNamed: (name := aStreamOrFileName)].<br>
> stream binary.<br>
> eocdPosition := self class findEndOfCentralDirectoryFrom: stream.<br>
> + eocdPosition <= 0 ifTrue: [self error: ('{1} cannot find EOCD position in {2}' translated format: {self class name. aStreamOrFileName name})].<br>
> - eocdPosition <= 0 ifTrue: [self error: self class name, ' cannot find EOCD position in ', aStreamOrFileName name].<br>
> self readEndOfCentralDirectoryFrom: stream.<br>
> stream position: eocdPosition - centralDirectorySize.<br>
> self readMembersFrom: stream named: name!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>readMembersFrom:named: (in category 'private') -----<br>
> readMembersFrom: stream named: fileName<br>
> [<br>
> | newMember signature |<br>
> newMember := self memberClass newFromZipFile: stream named: fileName.<br>
> signature := self readSignatureFrom: stream.<br>
> signature = EndOfCentralDirectorySignature ifTrue: [ ^self ].<br>
> signature = CentralDirectoryFileHeaderSignature<br>
> + ifFalse: [ self error: ('bad CD signature at {1}' translated format: {(stream position - 4) printStringHex}) ].<br>
> - ifFalse: [ self error: 'bad CD signature at ', (stream position - 4) printStringHex ].<br>
> newMember readFrom: stream.<br>
> newMember looksLikeDirectory ifTrue: [ newMember := newMember asDirectory ].<br>
> self addMember: newMember.<br>
> ] repeat.!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>readSignatureFrom: (in category 'private') -----<br>
> readSignatureFrom: stream<br>
> "Returns next signature from given stream, leaves stream positioned afterwards."<br>
> <br>
> | signatureData | <br>
> signatureData := ByteArray new: 4.<br>
> stream next: 4 into: signatureData.<br>
> ({ CentralDirectoryFileHeaderSignature . LocalFileHeaderSignature . EndOfCentralDirectorySignature }<br>
> includes: signatureData)<br>
> + ifFalse: [ ^self error: ('bad signature {1} at position {2}' translated format: {signatureData asString asHex. stream position - 4}) ].<br>
> - ifFalse: [ ^self error: 'bad signature ', signatureData asString asHex, ' at position ', (stream position - 4) asString ].<br>
> ^signatureData<br>
> !<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>writeToFileNamed:prepending: (in category 'writing') -----<br>
> writeToFileNamed: aFileName prepending: aString<br>
> | stream |<br>
> "Catch attempts to overwrite existing zip file"<br>
> (self canWriteToFileNamed: aFileName)<br>
> + ifFalse: [ ^self error: ('{1} is needed by one or more members in this archive' translated format: {aFileName}) ].<br>
> - ifFalse: [ ^self error: (aFileName, ' is needed by one or more members in this archive') ].<br>
> stream := StandardFileStream forceNewFileNamed: aFileName.<br>
> self writeTo: stream prepending: aString.<br>
> stream close.!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchive>>writeToFileNamed:prependingFileNamed: (in category 'writing') -----<br>
> writeToFileNamed: aFileName prependingFileNamed: anotherFileName<br>
> | stream |<br>
> "Catch attempts to overwrite existing zip file"<br>
> (self canWriteToFileNamed: aFileName)<br>
> + ifFalse: [ ^self error: ('{1} is needed by one or more members in this archive' translated format: {aFileName}) ].<br>
> - ifFalse: [ ^self error: (aFileName, ' is needed by one or more members in this archive') ].<br>
> stream := StandardFileStream forceNewFileNamed: aFileName.<br>
> self writeTo: stream prependingFileNamed: anotherFileName.<br>
> stream close.!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchiveMember>>extractInDirectory:overwrite: (in category 'extraction') -----<br>
> extractInDirectory: aDirectory overwrite: overwriteAll<br>
> "Extract this entry into the given directory. Answer #okay, #failed, #abort, or #retryWithOverwrite."<br>
> + | path fileDir file localName |<br>
> + path := fileName findTokens: '/'.<br>
> - | path fileDir file index localName |<br>
> - path := fileName findTokens:'/'.<br>
> localName := path last.<br>
> + fileDir := path allButLast inject: aDirectory into: [:base :part | base directoryNamed: part].<br>
> - fileDir := path allButLast inject: aDirectory into:[:base :part| base directoryNamed: part].<br>
> fileDir assureExistence.<br>
> + <br>
> + overwriteAll ifFalse: [<br>
> + [file := fileDir newFileNamed: localName]<br>
> + on: FileExistsException<br>
> + do: [<br>
> + (Project uiManager<br>
> + chooseFromLabeledValues: (OrderedDictionary new<br>
> + at: 'Yes, overwrite' translated put: [#overwrite];<br>
> + at: 'No, don''t overwrite' translated put: [^ #okay];<br>
> + at: 'Overwrite ALL files' translated put: [^ #retryWithOverwrite];<br>
> + at: 'Cancel operation' translated put: [];<br>
> + yourself)<br>
> + title: ('{1} already exists. Overwrite?' translated format: {fileName})) value<br>
> + ifNil: [^ #abort]]].<br>
> + file ifNil: [<br>
> + file := ([fileDir forceNewFileNamed: localName]<br>
> + on: Error do: [])<br>
> + ifNil: [^ #failed]].<br>
> + <br>
> - file := [fileDir newFileNamed: localName] on: FileExistsException do:[:ex| ex return: nil].<br>
> - file ifNil:[<br>
> - overwriteAll ifFalse:[<br>
> - [index := UIManager default chooseFrom: {<br>
> - 'Yes, overwrite'. <br>
> - 'No, don''t overwrite'. <br>
> - 'Overwrite ALL files'.<br>
> - 'Cancel operation'<br>
> - } lines: #(2) title: fileName, ' already exists. Overwrite?'.<br>
> - index == nil] whileTrue.<br>
> - index = 4 ifTrue:[^#abort].<br>
> - index = 3 ifTrue:[^#retryWithOverwrite].<br>
> - index = 2 ifTrue:[^#okay].<br>
> - ].<br>
> - file := [fileDir forceNewFileNamed: localName] on: Error do:[:ex| ex return].<br>
> - file ifNil:[^#failed].<br>
> - ].<br>
> self extractTo: file.<br>
> file close.<br>
> + ^ #okay!<br>
> - ^#okay!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchiveMember>>extractTo: (in category 'extraction') -----<br>
> extractTo: aStream<br>
> | oldCompression |<br>
> + self isEncrypted ifTrue: [ self error: 'encryption is unsupported' translated ].<br>
> - self isEncrypted ifTrue: [ self error: 'encryption is unsupported' ].<br>
> aStream binary.<br>
> oldCompression := self desiredCompressionMethod: CompressionStored.<br>
> self rewindData.<br>
> self writeDataTo: aStream.<br>
> self desiredCompressionMethod: oldCompression.<br>
> self endRead.!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchiveMember>>extractTo:from:to: (in category 'extraction') -----<br>
> extractTo: aStream from: start to: finish<br>
> | oldCompression |<br>
> + self isEncrypted ifTrue: [ self error: 'encryption is unsupported' translated ].<br>
> - self isEncrypted ifTrue: [ self error: 'encryption is unsupported' ].<br>
> aStream binary.<br>
> oldCompression := self desiredCompressionMethod: CompressionStored.<br>
> self rewindData.<br>
> self writeDataTo: aStream from: start to: finish.<br>
> self desiredCompressionMethod: oldCompression.<br>
> self endRead.!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchiveMember>>extractToFileNamed:inDirectory: (in category 'accessing') -----<br>
> extractToFileNamed: aLocalFileName inDirectory: dir<br>
> | stream fullName fullDir |<br>
> + self isEncrypted ifTrue: [ ^self error: 'encryption unsupported' translated ].<br>
> - self isEncrypted ifTrue: [ ^self error: 'encryption unsupported' ].<br>
> fullName := dir fullNameFor: aLocalFileName.<br>
> fullDir := FileDirectory forFileName: fullName.<br>
> fullDir assureExistence.<br>
> self isDirectory ifFalse: [<br>
> stream := fullDir forceNewFileNamed: (FileDirectory localNameFor: fullName).<br>
> self extractTo: stream.<br>
> stream close.<br>
> ] ifTrue: [ fullDir assureExistence ]<br>
> !<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipArchiveMember>>writeDataTo:from:to: (in category 'private-writing') -----<br>
> writeDataTo: aStream from: start to: finish<br>
> "Copy my (possibly inflated or deflated) data to the given stream.<br>
> But only the specified byte range.<br>
> This might do decompression, or straight copying, depending<br>
> on the values of compressionMethod and desiredCompressionMethod"<br>
> <br>
> uncompressedSize = 0 ifTrue: [ ^self ]. "nothing to do because no data"<br>
> start > finish ifTrue: [ ^self ].<br>
> start > uncompressedSize ifTrue: [ ^self ].<br>
> <br>
> (compressionMethod = CompressionStored and: [ desiredCompressionMethod = CompressionDeflated ])<br>
> + ifTrue: [ ^self error: 'only supports uncompression or copying right now' translated ].<br>
> - ifTrue: [ ^self error: 'only supports uncompression or copying right now' ].<br>
> <br>
> (compressionMethod = CompressionDeflated and: [ desiredCompressionMethod = CompressionStored ])<br>
> ifTrue: [ ^self uncompressDataTo: aStream from: start to: finish ].<br>
> <br>
> self copyRawDataTo: aStream from: start to: finish.!<br>
> <br>
> Item was changed:<br>
> + ----- Method: ZipDirectoryMember class>>newNamed: (in category 'instance creation') -----<br>
> - ----- Method: ZipDirectoryMember class>>newNamed: (in category 'as yet unclassified') -----<br>
> newNamed: aFileName<br>
> ^(self new) localFileName: aFileName; yourself!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipEncoderNode>>encodeBitLength:from: (in category 'encoding') -----<br>
> encodeBitLength: blCounts from: aTree<br>
> | index |<br>
> "Note: If bitLength is not nil then the tree must be broken"<br>
> + bitLength ifNotNil: [self error: 'Huffman tree is broken' translated].<br>
> - bitLength ifNotNil: [self error:'Huffman tree is broken'].<br>
> parent <br>
> ifNil: [bitLength := 0]<br>
> ifNotNil: [bitLength := parent bitLength + 1].<br>
> self isLeaf ifTrue:[<br>
> index := bitLength + 1.<br>
> blCounts at: index put: (blCounts at: index) + 1.<br>
> ] ifFalse:[<br>
> left encodeBitLength: blCounts from: aTree.<br>
> right encodeBitLength: blCounts from: aTree.<br>
> ].!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipEncoderTree>>buildTree:maxDepth: (in category 'encoding') -----<br>
> buildTree: nodeList maxDepth: depth<br>
> "Build either the literal or the distance tree"<br>
> | heap rootNode blCounts |<br>
> heap := Heap new: nodeList size // 3.<br>
> heap sortBlock: self nodeSortBlock.<br>
> "Find all nodes with non-zero frequency and add to heap"<br>
> maxCode := 0.<br>
> nodeList do:[:dNode|<br>
> dNode frequency = 0 ifFalse:[<br>
> maxCode := dNode value.<br>
> heap add: dNode]].<br>
> "The pkzip format requires that at least one distance code exists,<br>
> and that at least one bit should be sent even if there is only one<br>
> possible code. So to avoid special checks later on we force at least<br>
> two codes of non zero frequency."<br>
> heap size = 0 ifTrue:[<br>
> self assert:[maxCode = 0].<br>
> heap add: nodeList first.<br>
> heap add: nodeList second.<br>
> maxCode := 1].<br>
> heap size = 1 ifTrue:[<br>
> nodeList first frequency = 0<br>
> ifTrue:[heap add: nodeList first]<br>
> ifFalse:[heap add: nodeList second].<br>
> maxCode := maxCode max: 1].<br>
> rootNode := self buildHierarchyFrom: heap.<br>
> rootNode height > depth ifTrue:[<br>
> rootNode := rootNode rotateToHeight: depth.<br>
> + rootNode height > depth ifTrue: [self error: 'Cannot encode tree' translated]].<br>
> - rootNode height > depth ifTrue:[self error:'Cannot encode tree']].<br>
> blCounts := WordArray new: depth+1.<br>
> rootNode encodeBitLength: blCounts from: self.<br>
> self buildCodes: nodeList counts: blCounts maxDepth: depth.<br>
> self setValuesFrom: nodeList.!<br>
> <br>
> Item was changed:<br>
> + ----- Method: ZipFileMember class>>newFrom:named: (in category 'instance creation') -----<br>
> - ----- Method: ZipFileMember class>>newFrom:named: (in category 'as yet unclassified') -----<br>
> newFrom: stream named: fileName<br>
> ^(self new) stream: stream externalFileName: fileName!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipFileMember>>readLocalDirectoryFileHeaderFrom: (in category 'private-reading') -----<br>
> readLocalDirectoryFileHeaderFrom: aStream <br>
> "Positions stream as necessary. Will return stream to its original position"<br>
> <br>
> | fileNameLength extraFieldLength xcrc32 xcompressedSize xuncompressedSize sig oldPos |<br>
> <br>
> oldPos := aStream position.<br>
> <br>
> aStream position: localHeaderRelativeOffset.<br>
> <br>
> sig := aStream next: 4.<br>
> sig = LocalFileHeaderSignature asByteArray<br>
> ifFalse: [ aStream position: oldPos.<br>
> + ^self error: ('bad LH signature at {1}' translated format: {localHeaderRelativeOffset printStringHex}) ].<br>
> - ^self error: 'bad LH signature at ', localHeaderRelativeOffset printStringHex ].<br>
> <br>
> versionNeededToExtract := aStream nextLittleEndianNumber: 2.<br>
> bitFlag := aStream nextLittleEndianNumber: 2.<br>
> compressionMethod := aStream nextLittleEndianNumber: 2.<br>
> <br>
> lastModFileDateTime := aStream nextLittleEndianNumber: 4.<br>
> xcrc32 := aStream nextLittleEndianNumber: 4.<br>
> xcompressedSize := aStream nextLittleEndianNumber: 4.<br>
> xuncompressedSize := aStream nextLittleEndianNumber: 4.<br>
> <br>
> fileNameLength := aStream nextLittleEndianNumber: 2.<br>
> extraFieldLength := aStream nextLittleEndianNumber: 2.<br>
> <br>
> fileName := (aStream next: fileNameLength) asString asSqueakPathName.<br>
> localExtraField := (aStream next: extraFieldLength) asByteArray.<br>
> <br>
> dataOffset := aStream position.<br>
> <br>
> "Don't trash these fields if we already got them from the central directory"<br>
> self hasDataDescriptor ifFalse: [<br>
> crc32 := xcrc32.<br>
> compressedSize := xcompressedSize.<br>
> uncompressedSize := xuncompressedSize.<br>
> ].<br>
> <br>
> aStream position: oldPos.!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipFileMember>>rewindData (in category 'private-reading') -----<br>
> rewindData<br>
> super rewindData.<br>
> (stream isNil or: [ stream closed ])<br>
> + ifTrue: [ self error: 'stream missing or closed' translated ].<br>
> - ifTrue: [ self error: 'stream missing or closed' ].<br>
> stream position: (localHeaderRelativeOffset + 4).<br>
> self skipLocalDirectoryFileHeaderFrom: stream.!<br>
> <br>
> Item was changed:<br>
> + ----- Method: ZipStringMember class>>newFrom:named: (in category 'instance creation') -----<br>
> - ----- Method: ZipStringMember class>>newFrom:named: (in category 'as yet unclassified') -----<br>
> newFrom: aString named: aFileName<br>
> ^(self new) contents: aString; localFileName: aFileName; yourself!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipWriteStream>>encodeMatch:distance: (in category 'encoding') -----<br>
> encodeMatch: length distance: dist<br>
> "Encode the given match of length length starting at dist bytes ahead"<br>
> | literal distance |<br>
> dist > 0 <br>
> + ifFalse: [^self error: 'Distance must be positive' translated].<br>
> - ifFalse:[^self error:'Distance must be positive'].<br>
> length < MinMatch <br>
> + ifTrue: [^self error: ('Match length must be at least {1}' translated format: {MinMatch})].<br>
> - ifTrue:[^self error:'Match length must be at least ', MinMatch printString].<br>
> litCount := litCount + 1.<br>
> matchCount := matchCount + 1.<br>
> literals at: litCount put: length - MinMatch.<br>
> distances at: litCount put: dist.<br>
> literal := (MatchLengthCodes at: length - MinMatch + 1).<br>
> literalFreq at: literal+1 put: (literalFreq at: literal+1) + 1.<br>
> dist < 257<br>
> ifTrue:[distance := DistanceCodes at: dist]<br>
> ifFalse:[distance := DistanceCodes at: 257 + (dist - 1 bitShift: -7)].<br>
> distanceFreq at: distance+1 put: (distanceFreq at: distance+1) + 1.<br>
> ^self shouldFlush!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipWriteStream>>flushBlock: (in category 'encoding') -----<br>
> flushBlock: lastBlock<br>
> "Send the current block"<br>
> | lastFlag bitsRequired method bitsSent<br>
> storedLength fixedLength dynamicLength <br>
> blTree lTree dTree blBits blFreq |<br>
> <br>
> lastFlag := lastBlock ifTrue:[1] ifFalse:[0].<br>
> <br>
> "Compute the literal/length and distance tree"<br>
> lTree := ZipEncoderTree buildTreeFrom: literalFreq maxDepth: MaxBits.<br>
> dTree := ZipEncoderTree buildTreeFrom: distanceFreq maxDepth: MaxBits.<br>
> <br>
> "Compute the bit length tree"<br>
> blBits := lTree bitLengths, dTree bitLengths.<br>
> blFreq := WordArray new: MaxBitLengthCodes.<br>
> self scanBitLengths: blBits into: blFreq.<br>
> blTree := ZipEncoderTree buildTreeFrom: blFreq maxDepth: MaxBitLengthBits.<br>
> <br>
> "Compute the bit length for the current block.<br>
> Note: Most of this could be computed on the fly but it's getting<br>
> really ugly in this case so we do it afterwards."<br>
> storedLength := self storedBlockSize.<br>
> fixedLength := self fixedBlockSizeFor: lTree and: dTree.<br>
> dynamicLength := self dynamicBlockSizeFor: lTree and: dTree <br>
> using: blTree and: blFreq.<br>
> VerboseLevel > 1 ifTrue:[<br>
> Transcript cr; show:'Block sizes (S/F/D):';<br>
> space; print: storedLength // 8; <br>
> nextPut:$/; print: fixedLength // 8; <br>
> nextPut:$/; print: dynamicLength // 8; space; endEntry].<br>
> <br>
> "Check which method to use"<br>
> method := self forcedMethod.<br>
> method ifNil:[<br>
> method := (storedLength < fixedLength and:[storedLength < dynamicLength]) <br>
> ifTrue:[#stored]<br>
> ifFalse:[fixedLength < dynamicLength ifTrue:[#fixed] ifFalse:[#dynamic]]].<br>
> (method == #stored and:[blockStart < 0]) ifTrue:[<br>
> "Cannot use #stored if the block is not available"<br>
> method := fixedLength < dynamicLength ifTrue:[#fixed] ifFalse:[#dynamic]].<br>
> <br>
> bitsSent := encoder bitPosition. "# of bits sent before this block"<br>
> bitsRequired := nil.<br>
> <br>
> (method == #stored) ifTrue:[<br>
> VerboseLevel > 0 ifTrue:[Transcript show:'S'].<br>
> bitsRequired := storedLength.<br>
> encoder nextBits: 3 put: StoredBlock << 1 + lastFlag.<br>
> self sendStoredBlock].<br>
> <br>
> (method == #fixed) ifTrue:[<br>
> VerboseLevel > 0 ifTrue:[Transcript show:'F'].<br>
> bitsRequired := fixedLength.<br>
> encoder nextBits: 3 put: FixedBlock << 1 + lastFlag.<br>
> self sendFixedBlock].<br>
> <br>
> (method == #dynamic) ifTrue:[<br>
> VerboseLevel > 0 ifTrue:[Transcript show:'D'].<br>
> bitsRequired := dynamicLength.<br>
> encoder nextBits: 3 put: DynamicBlock << 1 + lastFlag.<br>
> self sendDynamicBlock: blTree <br>
> literalTree: lTree <br>
> distanceTree: dTree <br>
> bitLengths: blBits].<br>
> <br>
> bitsRequired = (encoder bitPosition - bitsSent)<br>
> + ifFalse:[self error: 'Bits size mismatch' translated].<br>
> - ifFalse:[self error:'Bits size mismatch'].<br>
> <br>
> lastBlock <br>
> ifTrue:[self release]<br>
> ifFalse:[self initializeNewBlock].!<br>
> <br>
> Item was changed:<br>
> ----- Method: ZipWriteStream>>sendCompressedBlock:with: (in category 'dynamic blocks') -----<br>
> sendCompressedBlock: litTree with: distTree<br>
> "Send the current block using the encodings from the given literal/length and distance tree"<br>
> | sum |<br>
> sum := encoder<br>
> sendBlock: (ReadStream on: literals from: 1 to: litCount)<br>
> with: (ReadStream on: distances from: 1 to: litCount)<br>
> with: litTree<br>
> with: distTree.<br>
> + sum = (blockPosition - blockStart) ifFalse:[self error: 'Wrong number of bytes' translated].!<br>
> - sum = (blockPosition - blockStart) ifFalse:[self error:'Wrong number of bytes'].!<br>
> <br>
>