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 /** 42 * Warn developer when they forgot to save their changes 43 */ 44 public function __destruct() 45 { 46 if ($this->isDirty()) { 47 throw new IndexUsageException('MemoryIndex destroyed in dirty state - forgot to call save()?'); 48 } 49 } 50 51 /** 52 * @inheritdoc 53 * @throws IndexLockException 54 */ 55 public function changeRow($rid, $value) 56 { 57 if (!$this->isWritable) throw new IndexLockException(); 58 59 if ($rid > count($this->data)) { 60 $this->data = array_pad($this->data, $rid, ''); 61 } 62 $this->data[$rid] = $value; 63 $this->dirty = true; 64 } 65 66 /** 67 * @inheritdoc 68 * @throws IndexLockException 69 */ 70 public function retrieveRow($rid) 71 { 72 if (isset($this->data[$rid])) { 73 return $this->data[$rid]; 74 } 75 if ($this->isWritable) { 76 $this->changeRow($rid, ''); // add to index 77 } 78 return ''; 79 } 80 81 /** @inheritdoc */ 82 public function retrieveRows($rids) 83 { 84 $result = []; 85 foreach ($rids as $rid) { 86 if (isset($this->data[$rid])) $result[$rid] = $this->data[$rid]; 87 } 88 89 return $result; 90 } 91 92 /** @inheritdoc */ 93 public function getRowIDs($values) 94 { 95 $values = array_map('trim', $values); 96 $values = array_fill_keys($values, 1); // easier access as associative array 97 98 $result = []; 99 $count = count($this->data); 100 for ($ln = 0; $ln < $count; $ln++) { 101 $line = $this->data[$ln]; 102 if (isset($values[$line])) { 103 $result[$line] = $ln; 104 unset($values[$line]); 105 } 106 } 107 108 if (!$this->isWritable) return $result; 109 110 // if there are still values, they have not been found and will be appended 111 foreach (array_keys($values) as $value) { 112 $this->data[] = $value; 113 $result[$value] = $ln++; 114 $this->dirty = true; 115 } 116 117 return $result; 118 } 119 120 /** @inheritdoc */ 121 public function search($re) 122 { 123 return preg_grep($re, $this->data); 124 } 125 126 /** 127 * Save the changed index back to its file 128 * 129 * The method will check the internal dirty state and will only write when the index has actually been changed 130 * 131 * @throws IndexWriteException 132 * @throws IndexLockException 133 */ 134 public function save() 135 { 136 global $conf; 137 138 if (!$this->isDirty()) { 139 return; 140 } 141 142 if (!$this->isWritable) throw new IndexLockException(); 143 144 $tempname = $this->filename . '.tmp'; 145 146 $fh = @fopen($tempname, 'w'); 147 if (!$fh) { 148 throw new IndexWriteException("Failed to write $tempname"); 149 } 150 fwrite($fh, implode("\n", $this->data)); 151 if (count($this->data)) { 152 fwrite($fh, "\n"); 153 } 154 fclose($fh); 155 156 if ($conf['fperm']) { 157 chmod($tempname, $conf['fperm']); 158 } 159 160 if (!io_rename($tempname, $this->filename)) { 161 throw new IndexWriteException("Failed to write {$this->filename}"); 162 } 163 164 $this->dirty = false; 165 } 166 167 /** 168 * Check if the index has been modified and needs to be saved 169 * @return bool 170 */ 171 public function isDirty() 172 { 173 return $this->dirty; 174 } 175} 176