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) throw new IndexWriteException("Failed to write {$tempname}"); 36 $ih = @fopen($this->filename, 'r'); 37 38 $ln = -1; // line counter 39 // copy previous index lines line-by-line, replacing the wanted line 40 if ($ih) { 41 while (($curline = fgets($ih)) !== false) { 42 fwrite($fh, (++$ln == $rid) ? $value : $curline); 43 } 44 fclose($ih); 45 } 46 // if wanted line is beyond the current line count, insert empty lines inbetween 47 if ($rid > $ln) { 48 while ($rid > ++$ln) { 49 fwrite($fh, "\n"); 50 } 51 fwrite($fh, $value); 52 } 53 fclose($fh); 54 55 if ($conf['fperm']) { 56 chmod($tempname, $conf['fperm']); 57 } 58 io_rename($tempname, $this->filename); 59 } 60 61 /** 62 * @inheritdoc 63 * @author Tom N Harris <tnharris@whoopdedo.org> 64 */ 65 public function retrieveRow($rid) 66 { 67 if (!file_exists($this->filename)) return ''; 68 $fh = @fopen($this->filename, 'r'); 69 if (!$fh) return ''; 70 $ln = -1; 71 while (($line = fgets($fh)) !== false) { 72 if (++$ln == $rid) break; 73 } 74 fclose($fh); 75 return rtrim((string)$line); 76 } 77 78 /** 79 * Searches the Index for a given value and adds it if not found 80 * 81 * Entries previously marked as deleted will be restored. 82 * 83 * Note the existance of an entry in the index does not say anything about the exististance 84 * of the real world object (eg. a page) 85 * 86 * You should preferable use accessCachedValue() instead. 87 * 88 * @param string $value 89 * @return int the RID of the entry 90 * @throws IndexAccessException 91 * @throws IndexWriteException 92 */ 93 public function accessValue($value) 94 { 95 $result = $this->accessValues([$value]); 96 return $result[$value]; 97 } 98 99 /** 100 * Searches the Index for all given values and adds them if not found 101 * 102 * @param string[] $values 103 * @return array the RIDs of the entries 104 * @throws IndexAccessException 105 */ 106 public function accessValues($values) 107 { 108 $values = array_map('trim', $values); 109 $values = array_fill_keys($values, 1); // easier access as associative array 110 111 // search for the values 112 $result = []; 113 $ln = 0; 114 if (file_exists($this->filename)) { 115 $fh = @fopen($this->filename, 'r'); 116 if (!$fh) throw new IndexAccessException("Failed to read {$this->filename}"); 117 while (($line = fgets($fh)) !== false && $values) { 118 $line = trim($line); 119 if (isset($values[$line])) { 120 $result[$line] = $ln; 121 unset($values[$line]); 122 } 123 $ln++; 124 } 125 fclose($fh); 126 } 127 128 // if there are still values, they have not been found and will be appended 129 foreach (array_keys($values) as $value) { 130 file_put_contents($this->filename, "$value\n", FILE_APPEND); 131 $result[$value] = $ln++; 132 } 133 134 return $result; 135 } 136 137 /** 138 * Cached version of accessCachedValue() 139 * 140 * @param string $value 141 * @return int the RID of the entry 142 * @throws IndexAccessException 143 * @throws IndexWriteException 144 */ 145 public function accessCachedValue($value) 146 { 147 if (isset(static::$ridCache['value'])) return static::$ridCache['value']; 148 149 // limit cache to 10 entries by discarding the oldest element 150 // as in DokuWiki usually only the most recently 151 // added item will be requested again 152 if (count(static::$ridCache) > 10) array_shift(static::$ridCache); 153 static::$ridCache[$value] = $this->accessValue($value); 154 return static::$ridCache[$value]; 155 } 156} 157