3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Util;
25 * An iterator which returns lines from file in reversed order
27 * original code https://stackoverflow.com/a/10494801
29 class ReversedFileReader implements \Iterator
31 const BUFFER_SIZE = 4096;
32 const SEPARATOR = "\n";
38 private $filesize = -1;
44 private $buffer = null;
50 private $value = null;
53 * Open $filename for read and reset iterator
55 * @param string $filename File to open
58 public function open(string $filename): ReversedFileReader
60 $this->fh = fopen($filename, 'r');
62 // this should use a custom exception.
63 throw new \Exception("Unable to open $filename");
65 $this->filesize = filesize($filename);
74 * Read $size bytes behind last position
79 private function _read(int $size): string
82 fseek($this->fh, $this->pos);
83 return fread($this->fh, $size);
87 * Read next line from end of file
88 * Return null if no lines are left to read
90 * @return string|null Depending on data being buffered
92 private function _readline(): ?string
94 $buffer = & $this->buffer;
96 if ($this->pos == 0) {
97 return array_pop($buffer);
99 if (is_null($buffer)) {
102 if (count($buffer) > 1) {
103 return array_pop($buffer);
105 $buffer = explode(self::SEPARATOR, $this->_read(self::BUFFER_SIZE) . $buffer[0]);
110 * Fetch next line from end and set it as current iterator value.
112 * @see Iterator::next()
115 #[\ReturnTypeWillChange]
116 public function next()
119 $this->value = $this->_readline();
123 * Rewind iterator to the first line at the end of file
125 * @see Iterator::rewind()
128 #[\ReturnTypeWillChange]
129 public function rewind()
131 if ($this->filesize > 0) {
132 $this->pos = $this->filesize;
135 $this->buffer = explode(self::SEPARATOR, $this->_read($this->filesize % self::BUFFER_SIZE ?: self::BUFFER_SIZE));
141 * Return current line number, starting from zero at the end of file
143 * @see Iterator::key()
146 public function key(): int
152 * Return current line
154 * @see Iterator::current()
157 public function current(): string
163 * Checks if current iterator value is valid, that is, we read all lines in files
165 * @see Iterator::valid()
168 public function valid(): bool
170 return ! is_null($this->value);