. * */ namespace Friendica\Util; /** * An iterator which returns lines from file in reversed order * * original code https://stackoverflow.com/a/10494801 */ class ReversedFileReader implements \Iterator { const BUFFER_SIZE = 4096; const SEPARATOR = "\n"; /** @var resource */ private $fh = null; /** @var int */ private $filesize = -1; /** @var int */ private $pos = -1; /** @var array */ private $buffer = null; /** @var int */ private $key = -1; /** @var string */ private $value = null; /** * Open $filename for read and reset iterator * * @param string $filename File to open * @return $this */ public function open(string $filename): ReversedFileReader { $this->fh = fopen($filename, 'r'); if (!$this->fh) { // this should use a custom exception. throw new \Exception("Unable to open $filename"); } $this->filesize = filesize($filename); $this->pos = -1; $this->buffer = null; $this->key = -1; $this->value = null; return $this; } /** * Read $size bytes behind last position * * @param int $size * @return string */ private function _read(int $size): string { $this->pos -= $size; fseek($this->fh, $this->pos); return fread($this->fh, $size); } /** * Read next line from end of file * Return null if no lines are left to read * * @return string|null Depending on data being buffered */ private function _readline(): ?string { $buffer = & $this->buffer; while (true) { if ($this->pos == 0) { return array_pop($buffer); } if (is_null($buffer)) { return null; } if (count($buffer) > 1) { return array_pop($buffer); } $buffer = explode(self::SEPARATOR, $this->_read(self::BUFFER_SIZE) . $buffer[0]); } } /** * Fetch next line from end and set it as current iterator value. * * @see Iterator::next() * @return void */ #[\ReturnTypeWillChange] public function next() { ++$this->key; $this->value = $this->_readline(); } /** * Rewind iterator to the first line at the end of file * * @see Iterator::rewind() * @return void */ #[\ReturnTypeWillChange] public function rewind() { if ($this->filesize > 0) { $this->pos = $this->filesize; $this->value = null; $this->key = -1; $this->buffer = explode(self::SEPARATOR, $this->_read($this->filesize % self::BUFFER_SIZE ?: self::BUFFER_SIZE)); $this->next(); } } /** * Return current line number, starting from zero at the end of file * * @see Iterator::key() * @return int */ public function key(): int { return $this->key; } /** * Return current line * * @see Iterator::current() * @return string */ public function current(): string { return $this->value; } /** * Checks if current iterator value is valid, that is, we read all lines in files * * @see Iterator::valid() * @return bool */ public function valid(): bool { return ! is_null($this->value); } }