xref: /dokuwiki/inc/Search/Index/MemoryIndex.php (revision 9f63f003f1342f30f2226786546a29e25d1b79ee)
1<?php
2
3namespace dokuwiki\Search\Index;
4
5use dokuwiki\Search\Exception\IndexWriteException;
6
7/**
8 * Access to a single index file
9 *
10 * Access using this class always happens by loading the full index into memory.
11 * All modifications need to be explicitly made permanent using the save() method.
12 * Should be used for small indexes that receive many changes at once.
13 */
14class MemoryIndex extends AbstractIndex
15{
16    /** @var string the raw data lines of the index, no newlines */
17    protected $data;
18
19    /** @var bool has the index been modified? */
20    protected $dirty = false;
21
22    /**
23     * Loads the full contents of the index into memory
24     *
25     * @inheritdoc
26     */
27    public function __construct($idx, $suffix = '')
28    {
29        parent::__construct($idx, $suffix);
30
31        $this->data = [];
32        if (!file_exists($this->filename)) {
33            return;
34        }
35        $this->data = file($this->filename, FILE_IGNORE_NEW_LINES);
36
37    }
38
39    /** @inheritdoc */
40    public function changeRow($rid, $value)
41    {
42        if ($rid > count($this->data)) {
43            $this->data = array_pad($this->data, $rid, '');
44        }
45        $this->data[$rid] = $value;
46        $this->dirty = true;
47    }
48
49    /** @inheritdoc */
50    public function retrieveRow($rid)
51    {
52        if (isset($this->data[$rid])) {
53            return $this->data[$rid];
54        }
55        $this->changeRow($rid, ''); // add to index
56        return '';
57    }
58
59    /** @inheritdoc */
60    public function retrieveRows($rids)
61    {
62        $result = [];
63        foreach ($rids as $rid) {
64            if (isset($this->data[$rid])) $result[$rid] = $this->data[$rid];
65        }
66
67        return $result;
68    }
69
70    /** @inheritdoc */
71    public function getRowIDs($values)
72    {
73        $values = array_map('trim', $values);
74        $values = array_fill_keys($values, 1); // easier access as associative array
75
76        $result = [];
77        $count = count($this->data);
78        for ($ln = 0; $ln < $count; $ln++) {
79            $line = $this->data[$ln];
80            if (isset($values[$line])) {
81                $result[$line] = $ln;
82                unset($values[$line]);
83            }
84        }
85
86        // if there are still values, they have not been found and will be appended
87        foreach (array_keys($values) as $value) {
88            $this->data[] = $value;
89            $result[$value] = $ln++;
90            $this->dirty = true;
91        }
92
93        return $result;
94    }
95
96    /** @inheritdoc */
97    public function search($re)
98    {
99        return preg_grep($re, $this->data);
100    }
101
102    /**
103     * Save the changed index back to its file
104     *
105     * The method will check the internal dirty state and will only write when the index has actually been changed
106     *
107     * @throws IndexWriteException
108     */
109    public function save()
110    {
111        global $conf;
112
113        if (!$this->isDirty()) {
114            return;
115        }
116
117        $tempname = $this->filename . '.tmp';
118
119        $fh = @fopen($tempname, 'w');
120        if (!$fh) {
121            throw new IndexWriteException("Failed to write $tempname");
122        }
123        fwrite($fh, implode("\n", $this->data));
124        if (count($this->data)) {
125            fwrite($fh, "\n");
126        }
127        fclose($fh);
128
129        if ($conf['fperm']) {
130            chmod($tempname, $conf['fperm']);
131        }
132
133        if (!io_rename($tempname, $this->filename)) {
134            throw new IndexWriteException("Failed to write {$this->filename}");
135        }
136
137        $this->dirty = false;
138    }
139
140    /**
141     * Check if the index has been modified and needs to be saved
142     * @return bool
143     */
144    public function isDirty()
145    {
146        return $this->dirty;
147    }
148}
149