3 namespace dokuwiki\ChangeLog
;
5 use dokuwiki\Utf8\PhpString
;
8 * Provides methods for handling of changelog
13 * Adds an entry to the changelog file
15 * @return array added log line as revision info
17 abstract public function addLogEntry(array $info, $timestamp = null);
20 * Parses a changelog line into its components
22 * @param string $line changelog line
23 * @return array|bool parsed line or false
24 * @author Ben Coburn <btcoburn@silicodon.net>
27 public static function parseLogLine($line)
29 $info = sexplode("\t", rtrim($line, "\n"), 8);
30 if ($info[3]) { // we need at least the page id to consider it a valid line
32 'date' => (int)$info[0], // unix timestamp
33 'ip' => $info[1], // IP address (127.0.0.1)
34 'type' => $info[2], // log line type
35 'id' => $info[3], // page id
36 'user' => $info[4], // user name
37 'sum' => $info[5], // edit summary (or action reason)
38 'extra' => $info[6], // extra data (varies by line type)
39 'sizechange' => ($info[7] != '') ?
(int)$info[7] : null, // size difference in bytes
47 * Build a changelog line from its components
49 * @param array $info Revision info structure
50 * @param int $timestamp log line date (optional)
51 * @return string changelog line
53 public static function buildLogLine(array &$info, $timestamp = null)
55 $strip = ["\t", "\n"];
57 'date' => $timestamp ??
$info['date'],
59 'type' => str_replace($strip, '', $info['type']),
61 'user' => $info['user'],
62 'sum' => PhpString
::substr(str_replace($strip, '', $info['sum'] ??
''), 0, 255),
63 'extra' => str_replace($strip, '', $info['extra']),
64 'sizechange' => $info['sizechange']
67 return implode("\t", $entry) . "\n";
71 * Returns path to changelog
73 * @return string path to file
75 abstract protected function getChangelogFilename();
78 * Checks if the ID has old revisions
81 public function hasRevisions()
83 $logfile = $this->getChangelogFilename();
84 return file_exists($logfile);
89 protected $chunk_size;
92 * Set chunk size for file reading
93 * Chunk size zero let read whole file at once
95 * @param int $chunk_size maximum block size read from file
97 public function setChunkSize($chunk_size)
99 if (!is_numeric($chunk_size)) $chunk_size = 0;
101 $this->chunk_size
= max($chunk_size, 0);
105 * Returns lines from changelog.
106 * If file larger than $chunk_size, only chunk is read that could contain $rev.
108 * When reference timestamp $rev is outside time range of changelog, readloglines() will return
109 * lines in first or last chunk, but they obviously does not contain $rev.
111 * @param int $rev revision timestamp
112 * @return array|false
113 * if success returns array(fp, array(changeloglines), $head, $tail, $eof)
114 * where fp only defined for chuck reading, needs closing.
117 protected function readloglines($rev)
119 $file = $this->getChangelogFilename();
121 if (!file_exists($file)) {
130 if (filesize($file) < $this->chunk_size ||
$this->chunk_size
== 0) {
132 $lines = file($file);
133 if ($lines === false) {
138 $fp = fopen($file, 'rb'); // "file pointer"
142 fseek($fp, 0, SEEK_END
);
147 while ($tail - $head > $this->chunk_size
) {
148 $finger = $head +
(int)(($tail - $head) / 2);
149 $finger = $this->getNewlinepointer($fp, $finger);
151 if ($finger == $head ||
$finger == $tail) {
154 $info = $this->parseLogLine($tmp);
155 $finger_rev = $info['date'];
157 if ($finger_rev > $rev) {
164 if ($tail - $head < 1) {
165 // could not find chunk, assume requested rev is missing
170 $lines = $this->readChunk($fp, $head, $tail);
172 return [$fp, $lines, $head, $tail, $eof];
176 * Read chunk and return array with lines of given chunk.
177 * Has no check if $head and $tail are really at a new line
179 * @param resource $fp resource file pointer
180 * @param int $head start point chunk
181 * @param int $tail end point chunk
182 * @return array lines read from chunk
184 protected function readChunk($fp, $head, $tail)
187 $chunk_size = max($tail - $head, 0); // found chunk size
190 while ($got < $chunk_size && !feof($fp)) {
191 $tmp = @fread
($fp, max(min($this->chunk_size
, $chunk_size - $got), 0));
192 if ($tmp === false) { //error state
195 $got +
= strlen($tmp);
198 $lines = explode("\n", $chunk);
199 array_pop($lines); // remove trailing newline
204 * Set pointer to first new line after $finger and return its position
206 * @param resource $fp file pointer
207 * @param int $finger a pointer
208 * @return int pointer
210 protected function getNewlinepointer($fp, $finger)
215 fgets($fp); // slip the finger forward to a new line
222 * Returns the next lines of the changelog of the chunk before head or after tail
224 * @param resource $fp file pointer
225 * @param int $head position head of last chunk
226 * @param int $tail position tail of last chunk
227 * @param int $direction positive forward, negative backward
228 * @return array with entries:
229 * - $lines: changelog lines of read chunk
230 * - $head: head of chunk
231 * - $tail: tail of chunk
233 protected function readAdjacentChunk($fp, $head, $tail, $direction)
235 if (!$fp) return [[], $head, $tail];
237 if ($direction > 0) {
240 $tail = $head +
(int)($this->chunk_size
* (2 / 3));
241 $tail = $this->getNewlinepointer($fp, $tail);
245 $head = max($tail - $this->chunk_size
, 0);
247 $nl = $this->getNewlinepointer($fp, $head);
248 // was the chunk big enough? if not, take another bite
249 if ($nl > 0 && $tail <= $nl) {
250 $head = max($head - $this->chunk_size
, 0);
259 $lines = $this->readChunk($fp, $head, $tail);
260 return [$lines, $head, $tail];