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