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 /** 41*c66b5ec6SAndreas Gohr * Warn about dirty unlocks 42*c66b5ec6SAndreas Gohr * 43*c66b5ec6SAndreas Gohr * @inheritdoc 447fcedc39SAndreas Gohr */ 45*c66b5ec6SAndreas Gohr public function unlock() 467fcedc39SAndreas Gohr { 477fcedc39SAndreas Gohr if ($this->isDirty()) { 48*c66b5ec6SAndreas Gohr throw new IndexUsageException('MemoryIndex unlocked in dirty state - forgot to call save()?'); 497fcedc39SAndreas Gohr } 50*c66b5ec6SAndreas 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 } 1779bd7d62fSAndreas Gohr} 178