xref: /dokuwiki/inc/Search/Index/MemoryIndex.php (revision 21fbd01b3c3eea88b767376b7b158f31f0f63127)
19bd7d62fSAndreas Gohr<?php
29bd7d62fSAndreas Gohr
39bd7d62fSAndreas Gohrnamespace dokuwiki\Search\Index;
49bd7d62fSAndreas Gohr
57fcedc39SAndreas Gohruse dokuwiki\Search\Exception\IndexLockException;
67fcedc39SAndreas Gohruse dokuwiki\Search\Exception\IndexUsageException;
79bd7d62fSAndreas Gohruse dokuwiki\Search\Exception\IndexWriteException;
89bd7d62fSAndreas Gohr
99bd7d62fSAndreas Gohr/**
109bd7d62fSAndreas Gohr * Access to a single index file
119bd7d62fSAndreas Gohr *
129bd7d62fSAndreas Gohr * Access using this class always happens by loading the full index into memory.
139bd7d62fSAndreas Gohr * All modifications need to be explicitly made permanent using the save() method.
149bd7d62fSAndreas Gohr * Should be used for small indexes that receive many changes at once.
159bd7d62fSAndreas Gohr */
169bd7d62fSAndreas Gohrclass MemoryIndex extends AbstractIndex
179bd7d62fSAndreas Gohr{
189bd7d62fSAndreas Gohr    /** @var string the raw data lines of the index, no newlines */
199bd7d62fSAndreas Gohr    protected $data;
209bd7d62fSAndreas Gohr
21b3cb0bc3SAndreas Gohr    /** @var bool has the index been modified? */
22b3cb0bc3SAndreas Gohr    protected $dirty = false;
23b3cb0bc3SAndreas Gohr
249bd7d62fSAndreas Gohr    /**
259bd7d62fSAndreas Gohr     * Loads the full contents of the index into memory
269bd7d62fSAndreas Gohr     *
279bd7d62fSAndreas Gohr     * @inheritdoc
289bd7d62fSAndreas Gohr     */
297fcedc39SAndreas Gohr    public function __construct($idx, $suffix = '', $isWritable = false)
309bd7d62fSAndreas Gohr    {
317fcedc39SAndreas Gohr        parent::__construct($idx, $suffix, $isWritable);
329bd7d62fSAndreas Gohr
339bd7d62fSAndreas Gohr        $this->data = [];
34b3cb0bc3SAndreas Gohr        if (!file_exists($this->filename)) {
35b3cb0bc3SAndreas Gohr            return;
36b3cb0bc3SAndreas Gohr        }
379bd7d62fSAndreas Gohr        $this->data = file($this->filename, FILE_IGNORE_NEW_LINES);
389bd7d62fSAndreas Gohr    }
399bd7d62fSAndreas Gohr
407fcedc39SAndreas Gohr    /**
41c66b5ec6SAndreas Gohr     * Warn about dirty unlocks
42c66b5ec6SAndreas Gohr     *
43c66b5ec6SAndreas Gohr     * @inheritdoc
447fcedc39SAndreas Gohr     */
45c66b5ec6SAndreas Gohr    public function unlock()
467fcedc39SAndreas Gohr    {
477fcedc39SAndreas Gohr        if ($this->isDirty()) {
48c66b5ec6SAndreas Gohr            throw new IndexUsageException('MemoryIndex unlocked in dirty state - forgot to call save()?');
497fcedc39SAndreas Gohr        }
50c66b5ec6SAndreas Gohr        parent::unlock();
517fcedc39SAndreas Gohr    }
527fcedc39SAndreas Gohr
537fcedc39SAndreas Gohr    /**
547fcedc39SAndreas Gohr     * @inheritdoc
557fcedc39SAndreas Gohr     * @throws IndexLockException
567fcedc39SAndreas Gohr     */
579bd7d62fSAndreas Gohr    public function changeRow($rid, $value)
589bd7d62fSAndreas Gohr    {
597fcedc39SAndreas Gohr        if (!$this->isWritable) throw new IndexLockException();
607fcedc39SAndreas Gohr
619bd7d62fSAndreas Gohr        if ($rid > count($this->data)) {
629bd7d62fSAndreas Gohr            $this->data = array_pad($this->data, $rid, '');
639bd7d62fSAndreas Gohr        }
649bd7d62fSAndreas Gohr        $this->data[$rid] = $value;
65b3cb0bc3SAndreas Gohr        $this->dirty = true;
669bd7d62fSAndreas Gohr    }
679bd7d62fSAndreas Gohr
687fcedc39SAndreas Gohr    /**
697fcedc39SAndreas Gohr     * @inheritdoc
707fcedc39SAndreas Gohr     * @throws IndexLockException
717fcedc39SAndreas Gohr     */
729bd7d62fSAndreas Gohr    public function retrieveRow($rid)
739bd7d62fSAndreas Gohr    {
74b3cb0bc3SAndreas Gohr        if (isset($this->data[$rid])) {
75b3cb0bc3SAndreas Gohr            return $this->data[$rid];
76b3cb0bc3SAndreas Gohr        }
777fcedc39SAndreas Gohr        if ($this->isWritable) {
78dec26820SAndreas Gohr            $this->changeRow($rid, ''); // add to index
797fcedc39SAndreas Gohr        }
809bd7d62fSAndreas Gohr        return '';
819bd7d62fSAndreas Gohr    }
829bd7d62fSAndreas Gohr
83d6396b6dSAndreas Gohr    /** @inheritdoc */
849f63f003SAndreas Gohr    public function retrieveRows($rids)
859f63f003SAndreas Gohr    {
869f63f003SAndreas Gohr        $result = [];
879f63f003SAndreas Gohr        foreach ($rids as $rid) {
889f63f003SAndreas Gohr            if (isset($this->data[$rid])) $result[$rid] = $this->data[$rid];
899f63f003SAndreas Gohr        }
909f63f003SAndreas Gohr
919f63f003SAndreas Gohr        return $result;
929f63f003SAndreas Gohr    }
939f63f003SAndreas Gohr
949f63f003SAndreas Gohr    /** @inheritdoc */
958ed35011SAndreas Gohr    public function getRowIDs($values)
96d6396b6dSAndreas Gohr    {
97d6396b6dSAndreas Gohr        $values = array_map('trim', $values);
98d6396b6dSAndreas Gohr        $values = array_fill_keys($values, 1); // easier access as associative array
99d6396b6dSAndreas Gohr
100d6396b6dSAndreas Gohr        $result = [];
101d6396b6dSAndreas Gohr        $count = count($this->data);
102d6396b6dSAndreas Gohr        for ($ln = 0; $ln < $count; $ln++) {
103d6396b6dSAndreas Gohr            $line = $this->data[$ln];
104d6396b6dSAndreas Gohr            if (isset($values[$line])) {
105d6396b6dSAndreas Gohr                $result[$line] = $ln;
106d6396b6dSAndreas Gohr                unset($values[$line]);
107d6396b6dSAndreas Gohr            }
108d6396b6dSAndreas Gohr        }
109d6396b6dSAndreas Gohr
1107fcedc39SAndreas Gohr        if (!$this->isWritable) return $result;
1117fcedc39SAndreas Gohr
112d6396b6dSAndreas Gohr        // if there are still values, they have not been found and will be appended
113d6396b6dSAndreas Gohr        foreach (array_keys($values) as $value) {
114d6396b6dSAndreas Gohr            $this->data[] = $value;
115d6396b6dSAndreas Gohr            $result[$value] = $ln++;
116b3cb0bc3SAndreas Gohr            $this->dirty = true;
117d6396b6dSAndreas Gohr        }
118d6396b6dSAndreas Gohr
119d6396b6dSAndreas Gohr        return $result;
120d6396b6dSAndreas Gohr    }
121d6396b6dSAndreas Gohr
12203a35633SAndreas Gohr    /** @inheritdoc */
12303a35633SAndreas Gohr    public function search($re)
12403a35633SAndreas Gohr    {
12503a35633SAndreas Gohr        return preg_grep($re, $this->data);
12603a35633SAndreas Gohr    }
12703a35633SAndreas Gohr
1289bd7d62fSAndreas Gohr    /**
1299bd7d62fSAndreas Gohr     * Save the changed index back to its file
1309bd7d62fSAndreas Gohr     *
131b3cb0bc3SAndreas Gohr     * The method will check the internal dirty state and will only write when the index has actually been changed
132b3cb0bc3SAndreas Gohr     *
1339bd7d62fSAndreas Gohr     * @throws IndexWriteException
1347fcedc39SAndreas Gohr     * @throws IndexLockException
1359bd7d62fSAndreas Gohr     */
1369bd7d62fSAndreas Gohr    public function save()
1379bd7d62fSAndreas Gohr    {
1389bd7d62fSAndreas Gohr        global $conf;
1399bd7d62fSAndreas Gohr
140b3cb0bc3SAndreas Gohr        if (!$this->isDirty()) {
141b3cb0bc3SAndreas Gohr            return;
142b3cb0bc3SAndreas Gohr        }
143b3cb0bc3SAndreas Gohr
1447fcedc39SAndreas Gohr        if (!$this->isWritable) throw new IndexLockException();
1457fcedc39SAndreas Gohr
1469bd7d62fSAndreas Gohr        $tempname = $this->filename . '.tmp';
1479bd7d62fSAndreas Gohr
1489bd7d62fSAndreas Gohr        $fh = @fopen($tempname, 'w');
1499bd7d62fSAndreas Gohr        if (!$fh) {
1509bd7d62fSAndreas Gohr            throw new IndexWriteException("Failed to write $tempname");
1519bd7d62fSAndreas Gohr        }
1529bd7d62fSAndreas Gohr        fwrite($fh, implode("\n", $this->data));
153dec26820SAndreas Gohr        if (count($this->data)) {
1549bd7d62fSAndreas Gohr            fwrite($fh, "\n");
1559bd7d62fSAndreas Gohr        }
1569bd7d62fSAndreas Gohr        fclose($fh);
1579bd7d62fSAndreas Gohr
1589bd7d62fSAndreas Gohr        if ($conf['fperm']) {
1599bd7d62fSAndreas Gohr            chmod($tempname, $conf['fperm']);
1609bd7d62fSAndreas Gohr        }
1619bd7d62fSAndreas Gohr
1629bd7d62fSAndreas Gohr        if (!io_rename($tempname, $this->filename)) {
1639bd7d62fSAndreas Gohr            throw new IndexWriteException("Failed to write {$this->filename}");
1649bd7d62fSAndreas Gohr        }
165b3cb0bc3SAndreas Gohr
166b3cb0bc3SAndreas Gohr        $this->dirty = false;
1679bd7d62fSAndreas Gohr    }
1689bd7d62fSAndreas Gohr
169b3cb0bc3SAndreas Gohr    /**
170b3cb0bc3SAndreas Gohr     * Check if the index has been modified and needs to be saved
171b3cb0bc3SAndreas Gohr     * @return bool
172b3cb0bc3SAndreas Gohr     */
173b3cb0bc3SAndreas Gohr    public function isDirty()
174b3cb0bc3SAndreas Gohr    {
175b3cb0bc3SAndreas Gohr        return $this->dirty;
176b3cb0bc3SAndreas Gohr    }
17783b3acccSAndreas Gohr
17883b3acccSAndreas Gohr    /** @inheritdoc */
179*21fbd01bSAndreas Gohr    public function count(): int
180*21fbd01bSAndreas Gohr    {
181*21fbd01bSAndreas Gohr        return count($this->data);
182*21fbd01bSAndreas Gohr    }
183*21fbd01bSAndreas Gohr
184*21fbd01bSAndreas Gohr    /** @inheritdoc */
18583b3acccSAndreas Gohr    public function getIterator(): \ArrayIterator
18683b3acccSAndreas Gohr    {
18783b3acccSAndreas Gohr        return new \ArrayIterator($this->data);
18883b3acccSAndreas Gohr    }
1899bd7d62fSAndreas Gohr}
190