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 * @inheritdoc 80 * @throws IndexAccessException 81 */ 82 public function getRowIDs($values) 83 { 84 $values = array_map('trim', $values); 85 $values = array_fill_keys($values, 1); // easier access as associative array 86 87 // search for the values 88 $result = []; 89 $ln = 0; 90 if (file_exists($this->filename)) { 91 $fh = @fopen($this->filename, 'r'); 92 if (!$fh) throw new IndexAccessException("Failed to read {$this->filename}"); 93 while (($line = fgets($fh)) !== false && $values) { 94 $line = trim($line); 95 if (isset($values[$line])) { 96 $result[$line] = $ln; 97 unset($values[$line]); 98 } 99 $ln++; 100 } 101 fclose($fh); 102 } 103 104 // if there are still values, they have not been found and will be appended 105 foreach (array_keys($values) as $value) { 106 file_put_contents($this->filename, "$value\n", FILE_APPEND); 107 $result[$value] = $ln++; 108 } 109 110 return $result; 111 } 112 113 /** 114 * Cached version of accessCachedValue() 115 * 116 * @param string $value 117 * @return int the RID of the entry 118 * @throws IndexAccessException 119 * @throws IndexWriteException 120 */ 121 public function accessCachedValue($value) 122 { 123 if (isset(static::$ridCache['value'])) return static::$ridCache['value']; 124 125 // limit cache to 10 entries by discarding the oldest element 126 // as in DokuWiki usually only the most recently 127 // added item will be requested again 128 if (count(static::$ridCache) > 10) array_shift(static::$ridCache); 129 static::$ridCache[$value] = $this->getRowID($value); 130 return static::$ridCache[$value]; 131 } 132} 133