19bd7d62fSAndreas Gohr<?php 29bd7d62fSAndreas Gohr 39bd7d62fSAndreas Gohrnamespace dokuwiki\Search\Index; 49bd7d62fSAndreas Gohr 5*7fcedc39SAndreas Gohruse dokuwiki\Search\Exception\IndexLockException; 6*7fcedc39SAndreas 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 */ 29*7fcedc39SAndreas Gohr public function __construct($idx, $suffix = '', $isWritable = false) 309bd7d62fSAndreas Gohr { 31*7fcedc39SAndreas 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 } 409bd7d62fSAndreas Gohr 41*7fcedc39SAndreas Gohr /** 42*7fcedc39SAndreas Gohr * Warn developer when they forgot to save their changes 43*7fcedc39SAndreas Gohr */ 44*7fcedc39SAndreas Gohr public function __destruct() 45*7fcedc39SAndreas Gohr { 46*7fcedc39SAndreas Gohr if ($this->isDirty()) { 47*7fcedc39SAndreas Gohr throw new IndexUsageException('MemoryIndex destroyed in dirty state - forgot to call save()?'); 48*7fcedc39SAndreas Gohr } 49*7fcedc39SAndreas Gohr } 50*7fcedc39SAndreas Gohr 51*7fcedc39SAndreas Gohr /** 52*7fcedc39SAndreas Gohr * @inheritdoc 53*7fcedc39SAndreas Gohr * @throws IndexLockException 54*7fcedc39SAndreas Gohr */ 559bd7d62fSAndreas Gohr public function changeRow($rid, $value) 569bd7d62fSAndreas Gohr { 57*7fcedc39SAndreas Gohr if (!$this->isWritable) throw new IndexLockException(); 58*7fcedc39SAndreas Gohr 599bd7d62fSAndreas Gohr if ($rid > count($this->data)) { 609bd7d62fSAndreas Gohr $this->data = array_pad($this->data, $rid, ''); 619bd7d62fSAndreas Gohr } 629bd7d62fSAndreas Gohr $this->data[$rid] = $value; 63b3cb0bc3SAndreas Gohr $this->dirty = true; 649bd7d62fSAndreas Gohr } 659bd7d62fSAndreas Gohr 66*7fcedc39SAndreas Gohr /** 67*7fcedc39SAndreas Gohr * @inheritdoc 68*7fcedc39SAndreas Gohr * @throws IndexLockException 69*7fcedc39SAndreas Gohr */ 709bd7d62fSAndreas Gohr public function retrieveRow($rid) 719bd7d62fSAndreas Gohr { 72b3cb0bc3SAndreas Gohr if (isset($this->data[$rid])) { 73b3cb0bc3SAndreas Gohr return $this->data[$rid]; 74b3cb0bc3SAndreas Gohr } 75*7fcedc39SAndreas Gohr if ($this->isWritable) { 76dec26820SAndreas Gohr $this->changeRow($rid, ''); // add to index 77*7fcedc39SAndreas Gohr } 789bd7d62fSAndreas Gohr return ''; 799bd7d62fSAndreas Gohr } 809bd7d62fSAndreas Gohr 81d6396b6dSAndreas Gohr /** @inheritdoc */ 829f63f003SAndreas Gohr public function retrieveRows($rids) 839f63f003SAndreas Gohr { 849f63f003SAndreas Gohr $result = []; 859f63f003SAndreas Gohr foreach ($rids as $rid) { 869f63f003SAndreas Gohr if (isset($this->data[$rid])) $result[$rid] = $this->data[$rid]; 879f63f003SAndreas Gohr } 889f63f003SAndreas Gohr 899f63f003SAndreas Gohr return $result; 909f63f003SAndreas Gohr } 919f63f003SAndreas Gohr 929f63f003SAndreas Gohr /** @inheritdoc */ 938ed35011SAndreas Gohr public function getRowIDs($values) 94d6396b6dSAndreas Gohr { 95d6396b6dSAndreas Gohr $values = array_map('trim', $values); 96d6396b6dSAndreas Gohr $values = array_fill_keys($values, 1); // easier access as associative array 97d6396b6dSAndreas Gohr 98d6396b6dSAndreas Gohr $result = []; 99d6396b6dSAndreas Gohr $count = count($this->data); 100d6396b6dSAndreas Gohr for ($ln = 0; $ln < $count; $ln++) { 101d6396b6dSAndreas Gohr $line = $this->data[$ln]; 102d6396b6dSAndreas Gohr if (isset($values[$line])) { 103d6396b6dSAndreas Gohr $result[$line] = $ln; 104d6396b6dSAndreas Gohr unset($values[$line]); 105d6396b6dSAndreas Gohr } 106d6396b6dSAndreas Gohr } 107d6396b6dSAndreas Gohr 108*7fcedc39SAndreas Gohr if (!$this->isWritable) return $result; 109*7fcedc39SAndreas Gohr 110d6396b6dSAndreas Gohr // if there are still values, they have not been found and will be appended 111d6396b6dSAndreas Gohr foreach (array_keys($values) as $value) { 112d6396b6dSAndreas Gohr $this->data[] = $value; 113d6396b6dSAndreas Gohr $result[$value] = $ln++; 114b3cb0bc3SAndreas Gohr $this->dirty = true; 115d6396b6dSAndreas Gohr } 116d6396b6dSAndreas Gohr 117d6396b6dSAndreas Gohr return $result; 118d6396b6dSAndreas Gohr } 119d6396b6dSAndreas Gohr 12003a35633SAndreas Gohr /** @inheritdoc */ 12103a35633SAndreas Gohr public function search($re) 12203a35633SAndreas Gohr { 12303a35633SAndreas Gohr return preg_grep($re, $this->data); 12403a35633SAndreas Gohr } 12503a35633SAndreas Gohr 1269bd7d62fSAndreas Gohr /** 1279bd7d62fSAndreas Gohr * Save the changed index back to its file 1289bd7d62fSAndreas Gohr * 129b3cb0bc3SAndreas Gohr * The method will check the internal dirty state and will only write when the index has actually been changed 130b3cb0bc3SAndreas Gohr * 1319bd7d62fSAndreas Gohr * @throws IndexWriteException 132*7fcedc39SAndreas Gohr * @throws IndexLockException 1339bd7d62fSAndreas Gohr */ 1349bd7d62fSAndreas Gohr public function save() 1359bd7d62fSAndreas Gohr { 1369bd7d62fSAndreas Gohr global $conf; 1379bd7d62fSAndreas Gohr 138b3cb0bc3SAndreas Gohr if (!$this->isDirty()) { 139b3cb0bc3SAndreas Gohr return; 140b3cb0bc3SAndreas Gohr } 141b3cb0bc3SAndreas Gohr 142*7fcedc39SAndreas Gohr if (!$this->isWritable) throw new IndexLockException(); 143*7fcedc39SAndreas Gohr 1449bd7d62fSAndreas Gohr $tempname = $this->filename . '.tmp'; 1459bd7d62fSAndreas Gohr 1469bd7d62fSAndreas Gohr $fh = @fopen($tempname, 'w'); 1479bd7d62fSAndreas Gohr if (!$fh) { 1489bd7d62fSAndreas Gohr throw new IndexWriteException("Failed to write $tempname"); 1499bd7d62fSAndreas Gohr } 1509bd7d62fSAndreas Gohr fwrite($fh, implode("\n", $this->data)); 151dec26820SAndreas Gohr if (count($this->data)) { 1529bd7d62fSAndreas Gohr fwrite($fh, "\n"); 1539bd7d62fSAndreas Gohr } 1549bd7d62fSAndreas Gohr fclose($fh); 1559bd7d62fSAndreas Gohr 1569bd7d62fSAndreas Gohr if ($conf['fperm']) { 1579bd7d62fSAndreas Gohr chmod($tempname, $conf['fperm']); 1589bd7d62fSAndreas Gohr } 1599bd7d62fSAndreas Gohr 1609bd7d62fSAndreas Gohr if (!io_rename($tempname, $this->filename)) { 1619bd7d62fSAndreas Gohr throw new IndexWriteException("Failed to write {$this->filename}"); 1629bd7d62fSAndreas Gohr } 163b3cb0bc3SAndreas Gohr 164b3cb0bc3SAndreas Gohr $this->dirty = false; 1659bd7d62fSAndreas Gohr } 1669bd7d62fSAndreas Gohr 167b3cb0bc3SAndreas Gohr /** 168b3cb0bc3SAndreas Gohr * Check if the index has been modified and needs to be saved 169b3cb0bc3SAndreas Gohr * @return bool 170b3cb0bc3SAndreas Gohr */ 171b3cb0bc3SAndreas Gohr public function isDirty() 172b3cb0bc3SAndreas Gohr { 173b3cb0bc3SAndreas Gohr return $this->dirty; 174b3cb0bc3SAndreas Gohr } 1759bd7d62fSAndreas Gohr} 176