<?php require_once __DIR__ . '/BufferedReader.php'; class LooseObjects { private string $objPath; public function __construct( string $objPath ) { $this->objPath = $objPath; } public function getSize( string $sha ): int { $path = $this->getPath( $sha ); $size = 0; if( \is_file( $path ) ) { foreach( $this->streamInflated( $path ) as $chunk ) { $parts = \explode( ' ', $chunk['head'] ); $size = isset( $parts[1] ) ? (int)$parts[1] : 0; break; } } return $size; } public function peek( string $sha, int $length = 255 ): string { $path = $this->getPath( $sha ); $buf = ''; if( \is_file( $path ) ) { foreach( $this->streamInflated( $path, 8192 ) as $chunk ) { $buf .= $chunk['body']; if( \strlen( $buf ) >= $length ) { break; } } } return \substr( $buf, 0, $length ); } public function stream( string $sha, callable $callback ): bool { $found = false; foreach( $this->streamChunks( $sha ) as $chunk ) { $found = true; $callback( $chunk ); } return $found; } public function streamChunks( string $sha ): Generator { $path = $this->getPath( $sha ); if( \is_file( $path ) ) { foreach( $this->streamInflated( $path ) as $chunk ) { if( $chunk['body'] !== '' ) { yield $chunk['body']; } } } } private function getPath( string $sha ): string { return "{$this->objPath}/" . \substr( $sha, 0, 2 ) . "/" . \substr( $sha, 2 ); } private function streamInflated( string $path, int $bufSz = 16384 ): Generator { $reader = new BufferedReader( $path ); $infl = $reader->isOpen() ? \inflate_init( \ZLIB_ENCODING_DEFLATE ) : false; if( $reader->isOpen() && $infl !== false ) { $found = false; $buffer = ''; while( !$reader->eof() ) { $chunk = $reader->read( $bufSz ); $inflated = \inflate_add( $infl, $chunk ); if( $inflated === false ) { break; } if( !$found ) { $buffer .= $inflated; $eos = \strpos( $buffer, "\0" ); if( $eos !== false ) { $found = true; yield [ 'head' => \substr( $buffer, 0, $eos ), 'body' => \substr( $buffer, $eos + 1 ) ]; } } elseif( $inflated !== '' ) { yield [ 'head' => '', 'body' => $inflated ]; } } } } }