xref: /dokuwiki/inc/Search/Index/FileIndex.php (revision 9bd7d62f47cb0e2a7651fefd7106f6ac10625281)
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) throw new IndexWriteException("Failed to write {$tempname}");
36        $ih = @fopen($this->filename, 'r');
37
38        $ln = -1; // line counter
39        // copy previous index lines line-by-line, replacing the wanted line
40        if ($ih) {
41            while (($curline = fgets($ih)) !== false) {
42                fwrite($fh, (++$ln == $rid) ? $value : $curline);
43            }
44            fclose($ih);
45        }
46        // if wanted line is beyond the current line count, insert empty lines inbetween
47        if ($rid > $ln) {
48            while ($rid > ++$ln) {
49                fwrite($fh, "\n");
50            }
51            fwrite($fh, $value);
52        }
53        fclose($fh);
54
55        if ($conf['fperm']) {
56            chmod($tempname, $conf['fperm']);
57        }
58        io_rename($tempname, $this->filename);
59    }
60
61    /**
62     * @inheritdoc
63     * @author Tom N Harris <tnharris@whoopdedo.org>
64     */
65    public function retrieveRow($rid)
66    {
67        if (!file_exists($this->filename)) return '';
68        $fh = @fopen($this->filename, 'r');
69        if (!$fh) return '';
70        $ln = -1;
71        while (($line = fgets($fh)) !== false) {
72            if (++$ln == $rid) break;
73        }
74        fclose($fh);
75        return rtrim((string)$line);
76    }
77
78    /**
79     * Searches the Index for a given value and adds it if not found
80     *
81     * Entries previously marked as deleted will be restored.
82     *
83     * Note the existance of an entry in the index does not say anything about the exististance
84     * of the real world object (eg. a page)
85     *
86     * You should preferable use accessCachedValue() instead.
87     *
88     * @param string $value
89     * @return int the RID of the entry
90     * @throws IndexAccessException
91     * @throws IndexWriteException
92     */
93    public function accessValue($value)
94    {
95        $result = $this->accessValues([$value]);
96        return $result[$value];
97    }
98
99    /**
100     * Searches the Index for all given values and adds them if not found
101     *
102     * @param string[] $values
103     * @return array the RIDs of the entries
104     * @throws IndexAccessException
105     */
106    public function accessValues($values)
107    {
108        $values = array_map('trim', $values);
109        $values = array_fill_keys($values, 1); // easier access as associative array
110
111        // search for the values
112        $result = [];
113        $ln = 0;
114        if (file_exists($this->filename)) {
115            $fh = @fopen($this->filename, 'r');
116            if (!$fh) throw new IndexAccessException("Failed to read {$this->filename}");
117            while (($line = fgets($fh)) !== false && $values) {
118                $line = trim($line);
119                if (isset($values[$line])) {
120                    $result[$line] = $ln;
121                    unset($values[$line]);
122                }
123                $ln++;
124            }
125            fclose($fh);
126        }
127
128        // if there are still values, they have not been found and will be appended
129        foreach (array_keys($values) as $value) {
130            file_put_contents($this->filename, "$value\n", FILE_APPEND);
131            $result[$value] = $ln++;
132        }
133
134        return $result;
135    }
136
137    /**
138     * Cached version of accessCachedValue()
139     *
140     * @param string $value
141     * @return int the RID of the entry
142     * @throws IndexAccessException
143     * @throws IndexWriteException
144     */
145    public function accessCachedValue($value)
146    {
147        if (isset(static::$ridCache['value'])) return static::$ridCache['value'];
148
149        // limit cache to 10 entries by discarding the oldest element
150        // as in DokuWiki usually only the most recently
151        // added item will be requested again
152        if (count(static::$ridCache) > 10) array_shift(static::$ridCache);
153        static::$ridCache[$value] = $this->accessValue($value);
154        return static::$ridCache[$value];
155    }
156}
157