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 /** 86 * Save the changed index back to its file 87 * 88 * The method will check the internal dirty state and will only write when the index has actually been changed 89 * 90 * @throws IndexWriteException 91 */ 92 public function save() 93 { 94 global $conf; 95 96 if (!$this->isDirty()) { 97 return; 98 } 99 100 $tempname = $this->filename . '.tmp'; 101 102 $fh = @fopen($tempname, 'w'); 103 if (!$fh) { 104 throw new IndexWriteException("Failed to write $tempname"); 105 } 106 fwrite($fh, implode("\n", $this->data)); 107 if (count($this->data)) { 108 fwrite($fh, "\n"); 109 } 110 fclose($fh); 111 112 if ($conf['fperm']) { 113 chmod($tempname, $conf['fperm']); 114 } 115 116 if (!io_rename($tempname, $this->filename)) { 117 throw new IndexWriteException("Failed to write {$this->filename}"); 118 } 119 120 $this->dirty = false; 121 } 122 123 /** 124 * Check if the index has been modified and needs to be saved 125 * @return bool 126 */ 127 public function isDirty() 128 { 129 return $this->dirty; 130 } 131} 132