1<?php 2 3namespace dokuwiki\Search\Index; 4 5use dokuwiki\Search\Exception\IndexLockException; 6use dokuwiki\Search\Exception\IndexUsageException; 7use dokuwiki\Search\Exception\IndexWriteException; 8 9/** 10 * Access to a single index file 11 * 12 * Access using this class always happens by loading the full index into memory. 13 * All modifications need to be explicitly made permanent using the save() method. 14 * Should be used for small indexes that receive many changes at once. 15 */ 16class MemoryIndex extends AbstractIndex 17{ 18 /** @var string the raw data lines of the index, no newlines */ 19 protected $data; 20 21 /** @var bool has the index been modified? */ 22 protected $dirty = false; 23 24 /** 25 * Loads the full contents of the index into memory 26 * 27 * @inheritdoc 28 */ 29 public function __construct($idx, $suffix = '', $isWritable = false) 30 { 31 parent::__construct($idx, $suffix, $isWritable); 32 33 $this->data = []; 34 if (!file_exists($this->filename)) { 35 return; 36 } 37 $this->data = file($this->filename, FILE_IGNORE_NEW_LINES); 38 } 39 40 /** 41 * Warn about dirty unlocks 42 * 43 * @inheritdoc 44 */ 45 public function unlock() 46 { 47 if ($this->isDirty()) { 48 throw new IndexUsageException('MemoryIndex unlocked in dirty state - forgot to call save()?'); 49 } 50 parent::unlock(); 51 } 52 53 /** 54 * @inheritdoc 55 * @throws IndexLockException 56 */ 57 public function changeRow($rid, $value) 58 { 59 if (!$this->isWritable) throw new IndexLockException(); 60 61 if ($rid > count($this->data)) { 62 $this->data = array_pad($this->data, $rid, ''); 63 } 64 $this->data[$rid] = $value; 65 $this->dirty = true; 66 } 67 68 /** 69 * @inheritdoc 70 * @throws IndexLockException 71 */ 72 public function retrieveRow($rid) 73 { 74 if (isset($this->data[$rid])) { 75 return $this->data[$rid]; 76 } 77 if ($this->isWritable) { 78 $this->changeRow($rid, ''); // add to index 79 } 80 return ''; 81 } 82 83 /** @inheritdoc */ 84 public function retrieveRows($rids) 85 { 86 $result = []; 87 foreach ($rids as $rid) { 88 if (isset($this->data[$rid])) $result[$rid] = $this->data[$rid]; 89 } 90 91 return $result; 92 } 93 94 /** @inheritdoc */ 95 public function getRowIDs($values) 96 { 97 $values = array_map('trim', $values); 98 $values = array_fill_keys($values, 1); // easier access as associative array 99 100 $result = []; 101 $count = count($this->data); 102 for ($ln = 0; $ln < $count; $ln++) { 103 $line = $this->data[$ln]; 104 if (isset($values[$line])) { 105 $result[$line] = $ln; 106 unset($values[$line]); 107 } 108 } 109 110 if (!$this->isWritable) return $result; 111 112 // if there are still values, they have not been found and will be appended 113 foreach (array_keys($values) as $value) { 114 $this->data[] = $value; 115 $result[$value] = $ln++; 116 $this->dirty = true; 117 } 118 119 return $result; 120 } 121 122 /** @inheritdoc */ 123 public function search($re) 124 { 125 return preg_grep($re, $this->data); 126 } 127 128 /** 129 * Save the changed index back to its file 130 * 131 * The method will check the internal dirty state and will only write when the index has actually been changed 132 * 133 * @throws IndexWriteException 134 * @throws IndexLockException 135 */ 136 public function save() 137 { 138 global $conf; 139 140 if (!$this->isDirty()) { 141 return; 142 } 143 144 if (!$this->isWritable) throw new IndexLockException(); 145 146 $tempname = $this->filename . '.tmp'; 147 148 $fh = @fopen($tempname, 'w'); 149 if (!$fh) { 150 throw new IndexWriteException("Failed to write $tempname"); 151 } 152 fwrite($fh, implode("\n", $this->data)); 153 if (count($this->data)) { 154 fwrite($fh, "\n"); 155 } 156 fclose($fh); 157 158 if ($conf['fperm']) { 159 chmod($tempname, $conf['fperm']); 160 } 161 162 if (!io_rename($tempname, $this->filename)) { 163 throw new IndexWriteException("Failed to write {$this->filename}"); 164 } 165 166 $this->dirty = false; 167 } 168 169 /** 170 * Check if the index has been modified and needs to be saved 171 * @return bool 172 */ 173 public function isDirty() 174 { 175 return $this->dirty; 176 } 177} 178