1<?php 2 3namespace dokuwiki\Search\Index; 4 5use dokuwiki\Search\Exception\IndexAccessException; 6use dokuwiki\Search\Exception\IndexWriteException; 7 8/** 9 * Access to a single index file 10 * 11 * Access using this class always happens on a line-by-line basis. It is usually not read in full. 12 * All modifications are implicitly saved 13 * Should be used for large indexes that receive only few changes at once. 14 */ 15class FileIndex extends AbstractIndex 16{ 17 /** @var array RID cache for faster access */ 18 protected static $ridCache = []; 19 20 /** 21 * @inheritdoc 22 * @throws IndexWriteException 23 * @author Tom N Harris <tnharris@whoopdedo.org> 24 */ 25 public function changeRow($rid, $value) 26 { 27 global $conf; 28 29 if (substr($value, -1) !== "\n") { 30 $value .= "\n"; 31 } 32 33 $tempname = $this->filename . '.tmp'; 34 $fh = @fopen($tempname, 'w'); 35 if (!$fh) { 36 throw new IndexWriteException("Failed to write {$tempname}"); 37 } 38 $ih = @fopen($this->filename, 'r'); 39 40 $ln = -1; // line counter 41 // copy previous index lines line-by-line, replacing the wanted line 42 if ($ih) { 43 while (($curline = fgets($ih)) !== false) { 44 fwrite($fh, (++$ln == $rid) ? $value : $curline); 45 } 46 fclose($ih); 47 } 48 // if wanted line is beyond the current line count, insert empty lines inbetween 49 if ($rid > $ln) { 50 while ($rid > ++$ln) { 51 fwrite($fh, "\n"); 52 } 53 fwrite($fh, $value); 54 } 55 fclose($fh); 56 57 if ($conf['fperm']) { 58 chmod($tempname, $conf['fperm']); 59 } 60 io_rename($tempname, $this->filename); 61 } 62 63 /** 64 * @inheritdoc 65 * @author Tom N Harris <tnharris@whoopdedo.org> 66 */ 67 public function retrieveRow($rid) 68 { 69 if (!file_exists($this->filename)) { 70 return ''; 71 } 72 $fh = @fopen($this->filename, 'r'); 73 if (!$fh) { 74 return ''; 75 } 76 $ln = -1; 77 while (($line = fgets($fh)) !== false) { 78 if (++$ln == $rid) { 79 fclose($fh); 80 return rtrim((string) $line); 81 } 82 } 83 fclose($fh); 84 85 // still here? pad the index for the given ID 86 // we do not simply call changeRow() here because appending is faster than line-by-line copying 87 if (!file_put_contents($this->filename, join("\n", array_fill(0, $rid - $ln + 1, '')), FILE_APPEND)) { 88 throw new IndexWriteException("Failed to write {$this->filename}"); 89 } 90 91 return ''; 92 } 93 94 /** 95 * @inheritdoc 96 * @throws IndexAccessException 97 */ 98 public function getRowIDs($values) 99 { 100 $values = array_map('trim', $values); 101 $values = array_fill_keys($values, 1); // easier access as associative array 102 103 // search for the values 104 $result = []; 105 $ln = 0; 106 if (file_exists($this->filename)) { 107 $fh = @fopen($this->filename, 'r'); 108 if (!$fh) { 109 throw new IndexAccessException("Failed to read {$this->filename}"); 110 } 111 while (($line = fgets($fh)) !== false && $values) { 112 $line = trim($line); 113 if (isset($values[$line])) { 114 $result[$line] = $ln; 115 unset($values[$line]); 116 } 117 $ln++; 118 } 119 fclose($fh); 120 } 121 122 // if there are still values, they have not been found and will be appended 123 foreach (array_keys($values) as $value) { 124 file_put_contents($this->filename, "$value\n", FILE_APPEND); 125 $result[$value] = $ln++; 126 } 127 128 return $result; 129 } 130 131 /** @inheritdoc */ 132 public function search($re) 133 { 134 $result = []; 135 $ln = 0; 136 if (file_exists($this->filename)) { 137 $fh = @fopen($this->filename, 'r'); 138 if (!$fh) { 139 throw new IndexAccessException("Failed to read {$this->filename}"); 140 } 141 while (($line = fgets($fh)) !== false) { 142 $line = trim($line); 143 if (preg_match($re, $line)) { 144 $result[$ln] = $line; 145 } 146 $ln++; 147 } 148 fclose($fh); 149 } 150 return $result; 151 } 152 153 /** 154 * Cached version of accessCachedValue() 155 * 156 * @param string $value 157 * @return int the RID of the entry 158 * @throws IndexAccessException 159 * @throws IndexWriteException 160 */ 161 public function accessCachedValue($value) 162 { 163 if (isset(static::$ridCache['value'])) { 164 return static::$ridCache['value']; 165 } 166 167 // limit cache to 10 entries by discarding the oldest element 168 // as in DokuWiki usually only the most recently 169 // added item will be requested again 170 if (count(static::$ridCache) > 10) { 171 array_shift(static::$ridCache); 172 } 173 static::$ridCache[$value] = $this->getRowID($value); 174 return static::$ridCache[$value]; 175 } 176} 177