xref: /dokuwiki/inc/Search/Index/MemoryIndex.php (revision f5f947e5cc1263109ebb43e7bcef592e29716a8f)
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 getRowIDs($values)
61    {
62        $values = array_map('trim', $values);
63        $values = array_fill_keys($values, 1); // easier access as associative array
64
65        $result = [];
66        $count = count($this->data);
67        for ($ln = 0; $ln < $count; $ln++) {
68            $line = $this->data[$ln];
69            if (isset($values[$line])) {
70                $result[$line] = $ln;
71                unset($values[$line]);
72            }
73        }
74
75        // if there are still values, they have not been found and will be appended
76        foreach (array_keys($values) as $value) {
77            $this->data[] = $value;
78            $result[$value] = $ln++;
79            $this->dirty = true;
80        }
81
82        return $result;
83    }
84
85    /** @inheritdoc */
86    public function search($re)
87    {
88        return preg_grep($re, $this->data);
89    }
90
91    /**
92     * Save the changed index back to its file
93     *
94     * The method will check the internal dirty state and will only write when the index has actually been changed
95     *
96     * @throws IndexWriteException
97     */
98    public function save()
99    {
100        global $conf;
101
102        if (!$this->isDirty()) {
103            return;
104        }
105
106        $tempname = $this->filename . '.tmp';
107
108        $fh = @fopen($tempname, 'w');
109        if (!$fh) {
110            throw new IndexWriteException("Failed to write $tempname");
111        }
112        fwrite($fh, implode("\n", $this->data));
113        if (count($this->data)) {
114            fwrite($fh, "\n");
115        }
116        fclose($fh);
117
118        if ($conf['fperm']) {
119            chmod($tempname, $conf['fperm']);
120        }
121
122        if (!io_rename($tempname, $this->filename)) {
123            throw new IndexWriteException("Failed to write {$this->filename}");
124        }
125
126        $this->dirty = false;
127    }
128
129    /**
130     * Check if the index has been modified and needs to be saved
131     * @return bool
132     */
133    public function isDirty()
134    {
135        return $this->dirty;
136    }
137}
138