xref: /dokuwiki/inc/Search/Index/FileIndex.php (revision d6396b6d927e3ff373c0aed3927ae61eaca8d537)
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     * @inheritdoc
80     * @throws IndexAccessException
81     */
82    public function accessValues($values)
83    {
84        $values = array_map('trim', $values);
85        $values = array_fill_keys($values, 1); // easier access as associative array
86
87        // search for the values
88        $result = [];
89        $ln = 0;
90        if (file_exists($this->filename)) {
91            $fh = @fopen($this->filename, 'r');
92            if (!$fh) throw new IndexAccessException("Failed to read {$this->filename}");
93            while (($line = fgets($fh)) !== false && $values) {
94                $line = trim($line);
95                if (isset($values[$line])) {
96                    $result[$line] = $ln;
97                    unset($values[$line]);
98                }
99                $ln++;
100            }
101            fclose($fh);
102        }
103
104        // if there are still values, they have not been found and will be appended
105        foreach (array_keys($values) as $value) {
106            file_put_contents($this->filename, "$value\n", FILE_APPEND);
107            $result[$value] = $ln++;
108        }
109
110        return $result;
111    }
112
113    /**
114     * Cached version of accessCachedValue()
115     *
116     * @param string $value
117     * @return int the RID of the entry
118     * @throws IndexAccessException
119     * @throws IndexWriteException
120     */
121    public function accessCachedValue($value)
122    {
123        if (isset(static::$ridCache['value'])) return static::$ridCache['value'];
124
125        // limit cache to 10 entries by discarding the oldest element
126        // as in DokuWiki usually only the most recently
127        // added item will be requested again
128        if (count(static::$ridCache) > 10) array_shift(static::$ridCache);
129        static::$ridCache[$value] = $this->accessValue($value);
130        return static::$ridCache[$value];
131    }
132}
133