xref: /dokuwiki/inc/Search/Index/FileIndex.php (revision 9f63f003f1342f30f2226786546a29e25d1b79ee)
1<?php
2
3namespace dokuwiki\Search\Index;
4
5use dokuwiki\Search\Exception\IndexAccessException;
6use dokuwiki\Search\Exception\IndexWriteException;
7
8/**
9 * Access to a single index file
10 *
11 * Access using this class always happens on a line-by-line basis. It is usually not read in full.
12 * All modifications are implicitly saved
13 * Should be used for large indexes that receive only few changes at once.
14 */
15class FileIndex extends AbstractIndex
16{
17    /** @var array RID cache for faster access */
18    protected static $ridCache = [];
19
20    /**
21     * @inheritdoc
22     * @throws IndexWriteException
23     * @author Tom N Harris <tnharris@whoopdedo.org>
24     */
25    public function changeRow($rid, $value)
26    {
27        global $conf;
28
29        if (substr($value, -1) !== "\n") {
30            $value .= "\n";
31        }
32
33        $tempname = $this->filename . '.tmp';
34        $fh = @fopen($tempname, 'w');
35        if (!$fh) {
36            throw new IndexWriteException("Failed to write {$tempname}");
37        }
38        $ih = @fopen($this->filename, 'r');
39
40        $ln = -1; // line counter
41        // copy previous index lines line-by-line, replacing the wanted line
42        if ($ih) {
43            while (($curline = fgets($ih)) !== false) {
44                fwrite($fh, (++$ln == $rid) ? $value : $curline);
45            }
46            fclose($ih);
47        }
48        // if wanted line is beyond the current line count, insert empty lines inbetween
49        if ($rid > $ln) {
50            while ($rid > ++$ln) {
51                fwrite($fh, "\n");
52            }
53            fwrite($fh, $value);
54        }
55        fclose($fh);
56
57        if ($conf['fperm']) {
58            chmod($tempname, $conf['fperm']);
59        }
60        io_rename($tempname, $this->filename);
61    }
62
63    /**
64     * @inheritdoc
65     * @author Tom N Harris <tnharris@whoopdedo.org>
66     */
67    public function retrieveRow($rid)
68    {
69        if (!file_exists($this->filename)) {
70            return '';
71        }
72        $fh = @fopen($this->filename, 'r');
73        if (!$fh) {
74            return '';
75        }
76        $ln = -1;
77        while (($line = fgets($fh)) !== false) {
78            if (++$ln == $rid) {
79                fclose($fh);
80                return rtrim((string)$line);
81            }
82        }
83        fclose($fh);
84
85        // still here? pad the index for the given ID
86        // we do not simply call changeRow() here because appending is faster than line-by-line copying
87        if (!file_put_contents($this->filename, join("\n", array_fill(0, $rid - $ln + 1, '')), FILE_APPEND)) {
88            throw new IndexWriteException("Failed to write {$this->filename}");
89        }
90
91        return '';
92    }
93
94    /** @inheritdoc */
95    public function retrieveRows($rids)
96    {
97        $result = [];
98        sort($rids);
99        $next = array_shift($rids);
100
101        if (!file_exists($this->filename)) {
102            return $result;
103        }
104        $fh = @fopen($this->filename, 'r');
105        if (!$fh) {
106            return $result;
107        }
108        $ln = -1;
109        while (($line = fgets($fh)) !== false) {
110            if (++$ln === $next) {
111                $result[$ln] = rtrim((string)$line);
112                $next = array_shift($rids);
113                if ($next === false) break;
114            }
115        }
116        fclose($fh);
117        return $result;
118    }
119
120
121    /**
122     * @inheritdoc
123     * @throws IndexAccessException
124     */
125    public function getRowIDs($values)
126    {
127        $values = array_map('trim', $values);
128        $values = array_fill_keys($values, 1); // easier access as associative array
129
130        // search for the values
131        $result = [];
132        $ln = 0;
133        if (file_exists($this->filename)) {
134            $fh = @fopen($this->filename, 'r');
135            if (!$fh) {
136                throw new IndexAccessException("Failed to read {$this->filename}");
137            }
138            while (($line = fgets($fh)) !== false && $values) {
139                $line = trim($line);
140                if (isset($values[$line])) {
141                    $result[$line] = $ln;
142                    unset($values[$line]);
143                }
144                $ln++;
145            }
146            fclose($fh);
147        }
148
149        // if there are still values, they have not been found and will be appended
150        foreach (array_keys($values) as $value) {
151            file_put_contents($this->filename, "$value\n", FILE_APPEND);
152            $result[$value] = $ln++;
153        }
154
155        return $result;
156    }
157
158    /** @inheritdoc */
159    public function search($re)
160    {
161        $result = [];
162        $ln = 0;
163        if (file_exists($this->filename)) {
164            $fh = @fopen($this->filename, 'r');
165            if (!$fh) {
166                throw new IndexAccessException("Failed to read {$this->filename}");
167            }
168            while (($line = fgets($fh)) !== false) {
169                $line = trim($line);
170                if (preg_match($re, $line)) {
171                    $result[$ln] = $line;
172                }
173                $ln++;
174            }
175            fclose($fh);
176        }
177        return $result;
178    }
179
180    /**
181     * Cached version of accessCachedValue()
182     *
183     * @param string $value
184     * @return int the RID of the entry
185     * @throws IndexAccessException
186     * @throws IndexWriteException
187     */
188    public function accessCachedValue($value)
189    {
190        if (isset(static::$ridCache['value'])) {
191            return static::$ridCache['value'];
192        }
193
194        // limit cache to 10 entries by discarding the oldest element
195        // as in DokuWiki usually only the most recently
196        // added item will be requested again
197        if (count(static::$ridCache) > 10) {
198            array_shift(static::$ridCache);
199        }
200        static::$ridCache[$value] = $this->getRowID($value);
201        return static::$ridCache[$value];
202    }
203}
204