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 getRowIDs($values) 61 { 62 $values = array_map('trim', $values); 63 $values = array_fill_keys($values, 1); // easier access as associative array 64 65 $result = []; 66 $count = count($this->data); 67 for ($ln = 0; $ln < $count; $ln++) { 68 $line = $this->data[$ln]; 69 if (isset($values[$line])) { 70 $result[$line] = $ln; 71 unset($values[$line]); 72 } 73 } 74 75 // if there are still values, they have not been found and will be appended 76 foreach (array_keys($values) as $value) { 77 $this->data[] = $value; 78 $result[$value] = $ln++; 79 $this->dirty = true; 80 } 81 82 return $result; 83 } 84 85 /** @inheritdoc */ 86 public function search($re) 87 { 88 return preg_grep($re, $this->data); 89 } 90 91 /** 92 * Save the changed index back to its file 93 * 94 * The method will check the internal dirty state and will only write when the index has actually been changed 95 * 96 * @throws IndexWriteException 97 */ 98 public function save() 99 { 100 global $conf; 101 102 if (!$this->isDirty()) { 103 return; 104 } 105 106 $tempname = $this->filename . '.tmp'; 107 108 $fh = @fopen($tempname, 'w'); 109 if (!$fh) { 110 throw new IndexWriteException("Failed to write $tempname"); 111 } 112 fwrite($fh, implode("\n", $this->data)); 113 if (count($this->data)) { 114 fwrite($fh, "\n"); 115 } 116 fclose($fh); 117 118 if ($conf['fperm']) { 119 chmod($tempname, $conf['fperm']); 120 } 121 122 if (!io_rename($tempname, $this->filename)) { 123 throw new IndexWriteException("Failed to write {$this->filename}"); 124 } 125 126 $this->dirty = false; 127 } 128 129 /** 130 * Check if the index has been modified and needs to be saved 131 * @return bool 132 */ 133 public function isDirty() 134 { 135 return $this->dirty; 136 } 137} 138