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