xref: /dokuwiki/inc/Search/Index/MemoryIndex.php (revision 1ca67924afdba7cda93bb9fbf36899df6f3723ef)
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    /**
86     * Save the changed index back to its file
87     *
88     * The method will check the internal dirty state and will only write when the index has actually been changed
89     *
90     * @throws IndexWriteException
91     */
92    public function save()
93    {
94        global $conf;
95
96        if (!$this->isDirty()) {
97            return;
98        }
99
100        $tempname = $this->filename . '.tmp';
101
102        $fh = @fopen($tempname, 'w');
103        if (!$fh) {
104            throw new IndexWriteException("Failed to write $tempname");
105        }
106        fwrite($fh, implode("\n", $this->data));
107        if (count($this->data)) {
108            fwrite($fh, "\n");
109        }
110        fclose($fh);
111
112        if ($conf['fperm']) {
113            chmod($tempname, $conf['fperm']);
114        }
115
116        if (!io_rename($tempname, $this->filename)) {
117            throw new IndexWriteException("Failed to write {$this->filename}");
118        }
119
120        $this->dirty = false;
121    }
122
123    /**
124     * Check if the index has been modified and needs to be saved
125     * @return bool
126     */
127    public function isDirty()
128    {
129        return $this->dirty;
130    }
131}
132