stream !== null && $this->pack === $pack && $offset >= $this->pos && $offset - $this->pos <= self::SKIP_MAX) { try { $this->skipTo($offset); return $this->readExactly($length); } catch (\Exception) { // The open stream broke (e.g. a timeout); open the pack again below. } } $this->close(); $fh = $this->backend->get('packs/' . substr($pack, 0, 2) . '/' . $pack); $this->stream = $fh; $this->pack = $pack; $this->pos = 0; try { if ($offset > 0 && (stream_get_meta_data($fh)['seekable'] ?? false) && fseek($fh, $offset) === 0) { $this->pos = $offset; } $this->skipTo($offset); // a stream that cannot seek is read up to the offset return $this->readExactly($length); } catch (\Exception $e) { $this->close(); throw $e; } } public function close(): void { $fh = $this->stream; $this->stream = null; if ($fh !== null) { @fclose($fh); } } public function __destruct() { $this->close(); } private function skipTo(int $offset): void { while ($this->pos < $offset) { $this->readExactly(min($offset - $this->pos, self::CHUNK)); } } /** Read $length bytes from the open stream; a short read closes it. */ private function readExactly(int $length): string { $fh = $this->stream ?? throw new RepositoryException('No open pack stream'); $data = ''; while (strlen($data) < $length) { $chunk = fread($fh, min($length - strlen($data), self::CHUNK)); if ($chunk === false || $chunk === '') { $this->close(); throw new RepositoryException('Short read in pack ' . $this->pack); } $data .= $chunk; } $this->pos += $length; return $data; } }