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