| | |
| | private DeltaDecoder $decoder; |
| | - |
| | - public function __construct( DeltaDecoder $decoder ) { |
| | - $this->decoder = $decoder; |
| | - } |
| | - |
| | - public function getSize( |
| | - PackStreamManager $manager, |
| | - string $packFile, |
| | - int $offset |
| | - ): int { |
| | - $result = $manager->computeInt( |
| | - $packFile, |
| | - function( StreamReader $stream ) use ( $offset ): int { |
| | - $stream->seek( $offset ); |
| | - |
| | - $header = $this->readVarInt( $stream ); |
| | - $size = $header['value']; |
| | - $type = $header['byte'] >> 4 & 7; |
| | - |
| | - if( $type === 6 || $type === 7 ) { |
| | - $size = $this->decoder->readDeltaTargetSize( $stream, $type ); |
| | - } |
| | - |
| | - return $size; |
| | - }, |
| | - 0 |
| | - ); |
| | - |
| | - return $result; |
| | - } |
| | - |
| | - public function read( |
| | - PackStreamManager $manager, |
| | - string $packFile, |
| | - int $offset, |
| | - int $cap, |
| | - callable $readShaBaseFn |
| | - ): string { |
| | - $result = $manager->computeStringDedicated( |
| | - $packFile, |
| | - function( StreamReader $stream ) use ( |
| | - $offset, |
| | - $cap, |
| | - $readShaBaseFn |
| | - ): string { |
| | - $result = $this->readWithStream( |
| | - $stream, |
| | - $offset, |
| | - $cap, |
| | - $readShaBaseFn |
| | - ); |
| | - |
| | - return $result; |
| | - }, |
| | - '' |
| | - ); |
| | - |
| | - return $result; |
| | - } |
| | - |
| | - private function readWithStream( |
| | - StreamReader $stream, |
| | - int $offset, |
| | - int $cap, |
| | - callable $readShaBaseFn |
| | - ): string { |
| | - $stream->seek( $offset ); |
| | - |
| | - $header = $this->readVarInt( $stream ); |
| | - $type = $header['byte'] >> 4 & 7; |
| | - $result = ''; |
| | - |
| | - if( $type === 6 ) { |
| | - $neg = $this->readOffsetDelta( $stream ); |
| | - $cur = $stream->tell(); |
| | - $base = $offset - $neg; |
| | - $bData = $this->readWithStream( |
| | - $stream, |
| | - $base, |
| | - $cap, |
| | - $readShaBaseFn |
| | - ); |
| | - |
| | - $stream->seek( $cur ); |
| | - |
| | - $delta = $this->inflate( $stream ); |
| | - $result = $this->decoder->apply( $bData, $delta, $cap ); |
| | - } elseif( $type === 7 ) { |
| | - $sha = bin2hex( $stream->read( 20 ) ); |
| | - $bas = $readShaBaseFn( $sha, $cap ); |
| | - $del = $this->inflate( $stream ); |
| | - $result = $this->decoder->apply( $bas, $del, $cap ); |
| | - } else { |
| | - $result = $this->inflate( $stream, $cap ); |
| | - } |
| | - |
| | - return $result; |
| | - } |
| | - |
| | - public function streamRawCompressed( |
| | - PackStreamManager $manager, |
| | - string $packFile, |
| | - int $offset |
| | - ): Generator { |
| | - yield from $manager->streamGenerator( |
| | - $packFile, |
| | - function( StreamReader $stream ) use ( $offset ): Generator { |
| | - $stream->seek( $offset ); |
| | - |
| | - $header = $this->readVarInt( $stream ); |
| | - $type = $header['byte'] >> 4 & 7; |
| | - |
| | - if( $type !== 6 && $type !== 7 ) { |
| | - $extractor = CompressionStream::createExtractor(); |
| | - |
| | - yield from $extractor->stream( $stream ); |
| | - } |
| | - } |
| | - ); |
| | - } |
| | - |
| | - public function streamEntryGenerator( |
| | - PackStreamManager $manager, |
| | - string $packFile, |
| | - int $offset, |
| | - int $depth, |
| | - callable $getSizeShaFn, |
| | - callable $streamShaFn |
| | - ): Generator { |
| | - yield from $manager->streamGeneratorDedicated( |
| | - $packFile, |
| | - function( StreamReader $stream ) use ( |
| | - $manager, |
| | - $packFile, |
| | - $offset, |
| | - $depth, |
| | - $getSizeShaFn, |
| | - $streamShaFn |
| | - ): Generator { |
| | - $stream->seek( $offset ); |
| | - |
| | - $header = $this->readVarInt( $stream ); |
| | - $type = $header['byte'] >> 4 & 7; |
| | - |
| | - if( $type === 6 || $type === 7 ) { |
| | - yield from $this->streamDeltaObjectGenerator( |
| | - $stream, |
| | - $manager, |
| | - $packFile, |
| | - $offset, |
| | - $type, |
| | - $depth, |
| | - $getSizeShaFn, |
| | - $streamShaFn |
| | - ); |
| | - } else { |
| | - $inflater = CompressionStream::createInflater(); |
| | - |
| | - yield from $inflater->stream( $stream ); |
| | - } |
| | - } |
| | - ); |
| | - } |
| | - |
| | - private function streamDeltaObjectGenerator( |
| | - StreamReader $stream, |
| | - PackStreamManager $manager, |
| | - string $packFile, |
| | - int $offset, |
| | - int $type, |
| | - int $depth, |
| | - callable $getSizeShaFn, |
| | - callable $streamShaFn |
| | - ): Generator { |
| | - if( $depth < self::MAX_DEPTH ) { |
| | - if( $type === 6 ) { |
| | - $neg = $this->readOffsetDelta( $stream ); |
| | - $baseSize = $this->getSize( |
| | - $manager, |
| | - $packFile, |
| | - $offset - $neg |
| | - ); |
| | - |
| | - if( $baseSize > self::MAX_BASE_RAM ) { |
| | - $tmpStream = $this->resolveBaseToTempFile( |
| | - $manager, |
| | - $packFile, |
| | - $offset - $neg, |
| | - $depth, |
| | - $getSizeShaFn, |
| | - $streamShaFn |
| | - ); |
| | - |
| | - yield from $this->decoder->applyStreamGenerator( |
| | - $stream, |
| | - $tmpStream |
| | - ); |
| | - } else { |
| | - $readShaBaseFn = function( |
| | - string $sha, |
| | - int $cap |
| | - ) use ( |
| | - $streamShaFn, |
| | - $depth |
| | - ): string { |
| | - $chunks = []; |
| | - |
| | - foreach( $streamShaFn( $sha, $depth + 1 ) as $chunk ) { |
| | - $chunks[] = $chunk; |
| | - } |
| | - |
| | - $result = implode( '', $chunks ); |
| | - |
| | - if( $cap > 0 && strlen( $result ) > $cap ) { |
| | - $result = substr( $result, 0, $cap ); |
| | - } |
| | - |
| | - return $result; |
| | - }; |
| | - |
| | - $base = $this->read( |
| | - $manager, |
| | - $packFile, |
| | - $offset - $neg, |
| | - 0, |
| | - $readShaBaseFn |
| | - ); |
| | - |
| | - yield from $this->decoder->applyStreamGenerator( |
| | - $stream, |
| | - $base |
| | - ); |
| | - } |
| | - } else { |
| | - $baseSha = bin2hex( $stream->read( 20 ) ); |
| | - $baseSize = $getSizeShaFn( $baseSha ); |
| | - |
| | - if( $baseSize > self::MAX_BASE_RAM ) { |
| | - $tmpStream = BufferedReader::createTemp(); |
| | - $written = false; |
| | - |
| | - foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { |
| | - $tmpStream->write( $chunk ); |
| | - |
| | - $written = true; |
| | - } |
| | - |
| | - if( $written ) { |
| | - $tmpStream->rewind(); |
| | - |
| | - yield from $this->decoder->applyStreamGenerator( |
| | - $stream, |
| | - $tmpStream |
| | - ); |
| | - } |
| | - } else { |
| | - $chunks = []; |
| | - $written = false; |
| | - |
| | - foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { |
| | - $chunks[] = $chunk; |
| | - $written = true; |
| | - } |
| | - |
| | - if( $written ) { |
| | - $base = implode( '', $chunks ); |
| | - |
| | - yield from $this->decoder->applyStreamGenerator( |
| | - $stream, |
| | - $base |
| | - ); |
| | - } |
| | - } |
| | - } |
| | - } |
| | - } |
| | - |
| | - private function resolveBaseToTempFile( |
| | - PackStreamManager $manager, |
| | - string $packFile, |
| | - int $baseOffset, |
| | - int $depth, |
| | - callable $getSizeShaFn, |
| | - callable $streamShaFn |
| | - ): StreamReader { |
| | - $result = BufferedReader::createTemp(); |
| | - |
| | - foreach( $this->streamEntryGenerator( |
| | - $manager, |
| | - $packFile, |
| | - $baseOffset, |
| | - $depth + 1, |
| | - $getSizeShaFn, |
| | - $streamShaFn |
| | - ) as $chunk ) { |
| | - $result->write( $chunk ); |
| | - } |
| | - |
| | - $result->rewind(); |
| | - |
| | - return $result; |
| | - } |
| | - |
| | - private function readVarInt( StreamReader $stream ): array { |
| | - $byte = ord( $stream->read( 1 ) ); |
| | - $val = $byte & 15; |
| | - $shft = 4; |
| | - $fst = $byte; |
| | - |
| | - while( $byte & 128 ) { |
| | - $byte = ord( $stream->read( 1 ) ); |
| | - $val |= ($byte & 127) << $shft; |
| | - $shft += 7; |
| | - } |
| | - |
| | - return [ 'value' => $val, 'byte' => $fst ]; |
| | - } |
| | - |
| | - private function readOffsetDelta( StreamReader $stream ): int { |
| | - $byte = ord( $stream->read( 1 ) ); |
| | - $result = $byte & 127; |
| | - |
| | - while( $byte & 128 ) { |
| | - $byte = ord( $stream->read( 1 ) ); |
| | - $result = ($result + 1) << 7 | $byte & 127; |
| | - } |
| | + private array $cache; |
| | + |
| | + public function __construct( DeltaDecoder $decoder ) { |
| | + $this->decoder = $decoder; |
| | + $this->cache = []; |
| | + } |
| | + |
| | + public function getSize( |
| | + PackStreamManager $manager, |
| | + string $packFile, |
| | + int $offset |
| | + ): int { |
| | + $result = $manager->computeInt( |
| | + $packFile, |
| | + function( StreamReader $stream ) use ( $offset ): int { |
| | + $stream->seek( $offset ); |
| | + |
| | + $header = $this->readVarInt( $stream ); |
| | + $size = $header['value']; |
| | + $type = $header['byte'] >> 4 & 7; |
| | + |
| | + if( $type === 6 || $type === 7 ) { |
| | + $size = $this->decoder->readDeltaTargetSize( $stream, $type ); |
| | + } |
| | + |
| | + return $size; |
| | + }, |
| | + 0 |
| | + ); |
| | + |
| | + return $result; |
| | + } |
| | + |
| | + public function read( |
| | + PackStreamManager $manager, |
| | + string $packFile, |
| | + int $offset, |
| | + int $cap, |
| | + callable $readShaBaseFn |
| | + ): string { |
| | + $result = $manager->computeStringDedicated( |
| | + $packFile, |
| | + function( StreamReader $stream ) use ( |
| | + $offset, |
| | + $cap, |
| | + $readShaBaseFn |
| | + ): string { |
| | + $result = $this->readWithStream( |
| | + $stream, |
| | + $offset, |
| | + $cap, |
| | + $readShaBaseFn |
| | + ); |
| | + |
| | + return $result; |
| | + }, |
| | + '' |
| | + ); |
| | + |
| | + return $result; |
| | + } |
| | + |
| | + private function readWithStream( |
| | + StreamReader $stream, |
| | + int $offset, |
| | + int $cap, |
| | + callable $readShaBaseFn |
| | + ): string { |
| | + $result = ''; |
| | + |
| | + if( isset( $this->cache[$offset] ) ) { |
| | + $result = $this->cache[$offset]; |
| | + } else { |
| | + $stream->seek( $offset ); |
| | + |
| | + $header = $this->readVarInt( $stream ); |
| | + $type = $header['byte'] >> 4 & 7; |
| | + |
| | + if( $type === 6 ) { |
| | + $neg = $this->readOffsetDelta( $stream ); |
| | + $cur = $stream->tell(); |
| | + $base = $offset - $neg; |
| | + $bData = $this->readWithStream( |
| | + $stream, |
| | + $base, |
| | + $cap, |
| | + $readShaBaseFn |
| | + ); |
| | + |
| | + $stream->seek( $cur ); |
| | + |
| | + $delta = $this->inflate( $stream ); |
| | + $result = $this->decoder->apply( $bData, $delta, $cap ); |
| | + } elseif( $type === 7 ) { |
| | + $sha = bin2hex( $stream->read( 20 ) ); |
| | + $bas = $readShaBaseFn( $sha, $cap ); |
| | + $del = $this->inflate( $stream ); |
| | + $result = $this->decoder->apply( $bas, $del, $cap ); |
| | + } else { |
| | + $result = $this->inflate( $stream, $cap ); |
| | + } |
| | + |
| | + $this->cache[$offset] = $result; |
| | + |
| | + count( $this->cache ) > 50 ? array_shift( $this->cache ) : null; |
| | + } |
| | + |
| | + return $result; |
| | + } |
| | + |
| | + public function streamRawCompressed( |
| | + PackStreamManager $manager, |
| | + string $packFile, |
| | + int $offset |
| | + ): Generator { |
| | + yield from $manager->streamGenerator( |
| | + $packFile, |
| | + function( StreamReader $stream ) use ( $offset ): Generator { |
| | + $stream->seek( $offset ); |
| | + |
| | + $header = $this->readVarInt( $stream ); |
| | + $type = $header['byte'] >> 4 & 7; |
| | + |
| | + if( $type !== 6 && $type !== 7 ) { |
| | + $extractor = CompressionStream::createExtractor(); |
| | + |
| | + yield from $extractor->stream( $stream ); |
| | + } |
| | + } |
| | + ); |
| | + } |
| | + |
| | + public function streamEntryGenerator( |
| | + PackStreamManager $manager, |
| | + string $packFile, |
| | + int $offset, |
| | + int $depth, |
| | + callable $getSizeShaFn, |
| | + callable $streamShaFn |
| | + ): Generator { |
| | + yield from $manager->streamGeneratorDedicated( |
| | + $packFile, |
| | + function( StreamReader $stream ) use ( |
| | + $manager, |
| | + $packFile, |
| | + $offset, |
| | + $depth, |
| | + $getSizeShaFn, |
| | + $streamShaFn |
| | + ): Generator { |
| | + $stream->seek( $offset ); |
| | + |
| | + $header = $this->readVarInt( $stream ); |
| | + $type = $header['byte'] >> 4 & 7; |
| | + |
| | + if( $type === 6 || $type === 7 ) { |
| | + yield from $this->streamDeltaObjectGenerator( |
| | + $stream, |
| | + $manager, |
| | + $packFile, |
| | + $offset, |
| | + $type, |
| | + $depth, |
| | + $getSizeShaFn, |
| | + $streamShaFn |
| | + ); |
| | + } else { |
| | + $inflater = CompressionStream::createInflater(); |
| | + |
| | + yield from $inflater->stream( $stream ); |
| | + } |
| | + } |
| | + ); |
| | + } |
| | + |
| | + private function streamDeltaObjectGenerator( |
| | + StreamReader $stream, |
| | + PackStreamManager $manager, |
| | + string $packFile, |
| | + int $offset, |
| | + int $type, |
| | + int $depth, |
| | + callable $getSizeShaFn, |
| | + callable $streamShaFn |
| | + ): Generator { |
| | + if( $depth < self::MAX_DEPTH ) { |
| | + if( $type === 6 ) { |
| | + $neg = $this->readOffsetDelta( $stream ); |
| | + $baseSize = $this->getSize( |
| | + $manager, |
| | + $packFile, |
| | + $offset - $neg |
| | + ); |
| | + |
| | + if( $baseSize > self::MAX_BASE_RAM ) { |
| | + $tmpStream = $this->resolveBaseToTempFile( |
| | + $manager, |
| | + $packFile, |
| | + $offset - $neg, |
| | + $depth, |
| | + $getSizeShaFn, |
| | + $streamShaFn |
| | + ); |
| | + |
| | + yield from $this->decoder->applyStreamGenerator( |
| | + $stream, |
| | + $tmpStream |
| | + ); |
| | + } else { |
| | + $readShaBaseFn = function( |
| | + string $sha, |
| | + int $cap |
| | + ) use ( |
| | + $streamShaFn, |
| | + $depth |
| | + ): string { |
| | + $chunks = []; |
| | + |
| | + foreach( $streamShaFn( $sha, $depth + 1 ) as $chunk ) { |
| | + $chunks[] = $chunk; |
| | + } |
| | + |
| | + $result = implode( '', $chunks ); |
| | + |
| | + if( $cap > 0 && strlen( $result ) > $cap ) { |
| | + $result = substr( $result, 0, $cap ); |
| | + } |
| | + |
| | + return $result; |
| | + }; |
| | + |
| | + $base = $this->read( |
| | + $manager, |
| | + $packFile, |
| | + $offset - $neg, |
| | + 0, |
| | + $readShaBaseFn |
| | + ); |
| | + |
| | + yield from $this->decoder->applyStreamGenerator( |
| | + $stream, |
| | + $base |
| | + ); |
| | + } |
| | + } else { |
| | + $baseSha = bin2hex( $stream->read( 20 ) ); |
| | + $baseSize = $getSizeShaFn( $baseSha ); |
| | + |
| | + if( $baseSize > self::MAX_BASE_RAM ) { |
| | + $tmpStream = BufferedReader::createTemp(); |
| | + $written = false; |
| | + |
| | + foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { |
| | + $tmpStream->write( $chunk ); |
| | + |
| | + $written = true; |
| | + } |
| | + |
| | + if( $written ) { |
| | + $tmpStream->rewind(); |
| | + |
| | + yield from $this->decoder->applyStreamGenerator( |
| | + $stream, |
| | + $tmpStream |
| | + ); |
| | + } |
| | + } else { |
| | + $chunks = []; |
| | + $written = false; |
| | + |
| | + foreach( $streamShaFn( $baseSha, $depth + 1 ) as $chunk ) { |
| | + $chunks[] = $chunk; |
| | + $written = true; |
| | + } |
| | + |
| | + if( $written ) { |
| | + $base = implode( '', $chunks ); |
| | + |
| | + yield from $this->decoder->applyStreamGenerator( |
| | + $stream, |
| | + $base |
| | + ); |
| | + } |
| | + } |
| | + } |
| | + } |
| | + } |
| | + |
| | + private function resolveBaseToTempFile( |
| | + PackStreamManager $manager, |
| | + string $packFile, |
| | + int $baseOffset, |
| | + int $depth, |
| | + callable $getSizeShaFn, |
| | + callable $streamShaFn |
| | + ): StreamReader { |
| | + $result = BufferedReader::createTemp(); |
| | + |
| | + foreach( $this->streamEntryGenerator( |
| | + $manager, |
| | + $packFile, |
| | + $baseOffset, |
| | + $depth + 1, |
| | + $getSizeShaFn, |
| | + $streamShaFn |
| | + ) as $chunk ) { |
| | + $result->write( $chunk ); |
| | + } |
| | + |
| | + $result->rewind(); |
| | + |
| | + return $result; |
| | + } |
| | + |
| | + private function readVarInt( StreamReader $stream ): array { |
| | + $data = $stream->read( 12 ); |
| | + $byte = isset( $data[0] ) ? ord( $data[0] ) : 0; |
| | + $val = $byte & 15; |
| | + $shft = 4; |
| | + $fst = $byte; |
| | + $pos = 1; |
| | + |
| | + while( $byte & 128 ) { |
| | + $byte = isset( $data[$pos] ) ? ord( $data[$pos++] ) : 0; |
| | + $val |= ($byte & 127) << $shft; |
| | + $shft += 7; |
| | + } |
| | + |
| | + $rem = strlen( $data ) - $pos; |
| | + |
| | + $rem > 0 ? $stream->seek( -$rem, SEEK_CUR ) : null; |
| | + |
| | + return [ 'value' => $val, 'byte' => $fst ]; |
| | + } |
| | + |
| | + private function readOffsetDelta( StreamReader $stream ): int { |
| | + $data = $stream->read( 12 ); |
| | + $byte = isset( $data[0] ) ? ord( $data[0] ) : 0; |
| | + $result = $byte & 127; |
| | + $pos = 1; |
| | + |
| | + while( $byte & 128 ) { |
| | + $byte = isset( $data[$pos] ) ? ord( $data[$pos++] ) : 0; |
| | + $result = ($result + 1) << 7 | $byte & 127; |
| | + } |
| | + |
| | + $rem = strlen( $data ) - $pos; |
| | + |
| | + $rem > 0 ? $stream->seek( -$rem, SEEK_CUR ) : null; |
| | |
| | return $result; |