1<?php 2 3namespace dokuwiki\Search\Index; 4 5use dokuwiki\Search\Exception\IndexLockException; 6 7/** 8 * Basic building block to access individual index files 9 * 10 * To be able to write to an index, a lock must be acquired. 11 * 12 * Indexes are iterable, yielding RID => value pairs. 13 */ 14abstract class AbstractIndex implements \IteratorAggregate, \Countable 15{ 16 /** @var string name of the index */ 17 protected string $idx; 18 19 /** @var string suffix of the index */ 20 protected string $suffix; 21 22 /** @var string full filename to the index */ 23 protected string $filename; 24 25 /** @var bool has this instance acquired a lock? */ 26 protected bool $isWritable = false; 27 28 /** 29 * Initialize the index 30 * 31 * The $suffix argument is for an index that is split into multiple parts. 32 * Different index files should use different base names. 33 * 34 * When $isWritable is true, a lock is acquired immediately 35 * 36 * @param string $idx name of the index 37 * @param string $suffix subpart identifier 38 * @param bool $isWritable acquire a lock immediately? 39 * @throws IndexLockException 40 */ 41 public function __construct(string $idx, string $suffix = '', bool $isWritable = false) 42 { 43 global $conf; 44 $this->filename = $conf['indexdir'] . '/' . $idx . $suffix . '.idx'; 45 $this->idx = $idx; 46 $this->suffix = $suffix; 47 if ($isWritable) $this->lock(); 48 } 49 50 /** 51 * Make this index writable by acquiring the lock 52 * 53 * @throws IndexLockException 54 */ 55 public function lock(): void 56 { 57 if ($this->isWritable) return; 58 Lock::acquire($this->idx); 59 $this->isWritable = true; 60 } 61 62 /** 63 * Make this index read-only by releasing the lock 64 * 65 * Decrements the reference count in the Lock registry. The filesystem 66 * lock is only removed when the count reaches zero. 67 */ 68 public function unlock(): void 69 { 70 if (!$this->isWritable) return; 71 Lock::release($this->idx); 72 $this->isWritable = false; 73 } 74 75 /** 76 * Whether this index instance is writable 77 * 78 * @return bool 79 */ 80 public function isWritable(): bool 81 { 82 return $this->isWritable; 83 } 84 85 /** 86 * Ensure lock is released when the index is destroyed 87 */ 88 public function __destruct() 89 { 90 $this->unlock(); 91 } 92 93 /** 94 * @return string the full path to the underlying file 95 */ 96 public function getFilename(): string 97 { 98 return $this->filename; 99 } 100 101 /** 102 * Does this index exist, yet? 103 * 104 * @return bool 105 */ 106 public function exists(): bool 107 { 108 return file_exists($this->getFilename()); 109 } 110 111 /** 112 * Return the largest numeric suffix for the current index 113 * 114 * This is only useful for indexes that use integer based suffixes (like the wordlength indexes) 115 * 116 * @return int 0 if no numeric suffix indexes are found 117 */ 118 public function max(): int 119 { 120 global $conf; 121 $result = 0; 122 $files = glob($conf['indexdir'] . '/' . $this->idx . '*.idx'); 123 $pattern = '/^' . preg_quote($this->idx, '/') . '(\d+)\.idx$/'; 124 foreach ($files as $file) { 125 if (preg_match($pattern, basename($file), $match)) { 126 $num = (int)$match[1]; 127 if ($num > $result) $result = $num; 128 } 129 } 130 131 return $result; 132 } 133 134 /** 135 * Change a line in the index 136 * 137 * If the line doesn't exist, it will be added, creating empty 138 * lines inbetween as necessary 139 * 140 * @param int $rid the line number, count starting at 0 141 * @param string $value line content to write 142 */ 143 abstract public function changeRow(int $rid, string $value); 144 145 /** 146 * Retrieve a line from the index 147 * 148 * Returns an empty string for non-existing lines 149 * 150 * @param int $rid the line number 151 * @return string a line with trailing whitespace removed 152 */ 153 abstract public function retrieveRow(int $rid): string; 154 155 /** 156 * Retrieve multiple lines from the index 157 * 158 * Ignores non-existing lines, eg the result array may be smaller than the input $rids 159 * 160 * @param int[] $rids 161 * @return array [rid => value] 162 */ 163 abstract public function retrieveRows(array $rids): array; 164 165 /** 166 * Searches the Index for a given value 167 * 168 * If the index is writable and the value is not found it will be added. Otherwise null is returned. 169 * 170 * Note the existence of an entry in the index does not say anything about the existence 171 * of the real world object (eg. a page) 172 * 173 * You should preferably use accessCachedValue() instead. 174 * 175 * @param string $value 176 * 177 * @return int|null the RID of the entry, null if not found and not added 178 */ 179 public function getRowID(string $value): ?int 180 { 181 $result = $this->getRowIDs([$value]); 182 return $result[$value] ?? null; 183 } 184 185 /** 186 * Searches the Index for all given values 187 * 188 * If the index is writable, not found values are added 189 * 190 * @param string[] $values 191 * @return array the RIDs of the entries (value => rid) 192 */ 193 abstract public function getRowIDs(array $values): array; 194 195 /** 196 * Find all RIDs matching a regular expression 197 * 198 * A full regular expression including delimiters and modifiers is expected. 199 * 200 * For searching across collections, prefer using CollectionSearch which scans each 201 * index only once for all terms instead of once per term. 202 * 203 * @param string $re the regular expression to match against 204 * @return array (rid => value) 205 */ 206 abstract public function search(string $re): array; 207 208 /** 209 * Clears the index by deleting its file 210 * 211 * @return void 212 */ 213 public function clear(): void 214 { 215 @unlink($this->filename); 216 } 217 218 /** 219 * Return the number of lines in the index 220 * 221 * @return int 222 */ 223 abstract public function count(): int; 224 225 /** 226 * Saves the index if needed 227 * 228 * The default implementation does nothing and is only for streamlining the API of 229 * the different index classes 230 * 231 * @return void 232 */ 233 public function save() 234 { 235 } 236} 237