3 namespace Org\Mxchange\CoreFramework\Index\File;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\EntryPoint\ApplicationEntryPoint;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\File\BaseBinaryFile;
9 use Org\Mxchange\CoreFramework\Filesystem\File\BinaryFile;
10 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
11 use Org\Mxchange\CoreFramework\Index\BaseIndex;
12 use Org\Mxchange\CoreFramework\Index\Indexable;
13 use Org\Mxchange\CoreFramework\Utils\Arrays\ArrayUtils;
14 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
17 use \OutOfBoundsException;
19 use \UnexpectedValueException;
22 * A general file-based index class
24 * @author Roland Haeder <webmaster@ship-simu.org>
26 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
27 * @license GNU GPL 3.0 or any newer version
28 * @link http://www.ship-simu.org
30 * This program is free software: you can redistribute it and/or modify
31 * it under the terms of the GNU General Public License as published by
32 * the Free Software Foundation, either version 3 of the License, or
33 * (at your option) any later version.
35 * This program is distributed in the hope that it will be useful,
36 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 * GNU General Public License for more details.
40 * You should have received a copy of the GNU General Public License
41 * along with this program. If not, see <http://www.gnu.org/licenses/>.
43 abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
45 * Minimum block length
47 private static $minimumBlockLength = 0;
50 * Protected constructor
52 * @param $className Name of class
55 protected function __construct (string $className) {
56 // Call parent constructor
57 parent::__construct($className);
61 * Reads the file header
64 * @throws UnexpectedValueException If header length or count of elements is invalid
66 public function readIndexHeader () {
67 // First rewind to beginning as the header sits at the beginning ...
68 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
69 $this->getIteratorInstance()->rewind();
72 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
74 // Then read it (see constructor for calculation)
75 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: headerSize=%d', $headerSize));
76 $data = $this->getIteratorInstance()->getBinaryFileInstance()->read($headerSize);
78 // Have all requested bytes been read?
79 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()));
80 if (strlen($data) != $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
81 // Invalid header length
82 throw new UnexpectedValueException(sprintf('data(%d)=%s is not expected length %d',
85 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
87 } elseif (empty(trim($data, chr(0)))) {
89 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
91 } elseif (substr($data, -1, 1) != chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)) {
93 throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
95 chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
99 // Okay, then remove it
100 $data = substr($data, 0, -1);
102 // And update seek position
103 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->updateSeekPosition() ...');
104 $this->getIteratorInstance()->getBinaryFileInstance()->updateSeekPosition();
112 $header = explode(chr(BinaryFile::SEPARATOR_HEADER_DATA), $data);
114 // Map numeric entries to associative (alpha-numeric) elements
115 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Invoking ArrayUtils::mapNumericKeysToAssociative(%d) ...', count($header)));
116 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header(%d)=%s', count($header), print_r($header, true)));
117 $header = ArrayUtils::mapNumericKeysToAssociative($header, [
118 BinaryFile::HEADER_NAME_MAGIC,
119 BinaryFile::HEADER_NAME_TOTAL_ENTRIES,
122 // Check if the array has only 2 elements
123 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: HEADER_INDEX_ELEMENT_COUNT=%d,header()=%d', BinaryFile::HEADER_INDEX_ELEMENT_COUNT, count($header)));
124 if (count($header) != BinaryFile::HEADER_INDEX_ELEMENT_COUNT) {
126 throw new UnexpectedValueException(sprintf('header()=%d is not expected value %d', count($header), BinaryFile::HEADER_INDEX_ELEMENT_COUNT));
127 } elseif ($header[BinaryFile::HEADER_NAME_MAGIC] !== Indexable::INDEX_MAGIC) {
128 // Magic must be in first element
129 throw new UnexpectedValueException(sprintf('header[%s]=%s is not the expected magic (%s)', BinaryFile::HEADER_NAME_MAGIC, $header[BinaryFile::HEADER_NAME_MAGIC], Indexable::INDEX_MAGIC));
130 } elseif (strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]) != BinaryFile::LENGTH_COUNT) {
131 // Length of total entries not matching
132 throw new UnexpectedValueException(sprintf('header[%s](%d)=%s does not have expected length %d', BinaryFile::HEADER_NAME_TOTAL_ENTRIES, strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]), $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES], BinaryFile::LENGTH_COUNT));
136 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES] = hex2bin($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]);
139 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
142 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
146 * Flushes the file header
150 public function flushFileHeader () {
151 // Put all informations together
152 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
153 $header = sprintf('%s%s%s%s',
155 Indexable::INDEX_MAGIC,
157 // Separator header data
158 chr(BinaryFile::SEPARATOR_HEADER_DATA),
161 str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
163 // Separator header<->entries
164 chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
167 // Write it to disk (header is always at seek position 0)
168 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->writeAtPosition(0, header=%s) ...', $header));
169 $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition(0, $header);
172 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
176 * Initializes this file-based index
178 * @param $fileInfoInstance An instance of a SplFileInfo class
180 * @todo Currently the index file is not cached, please implement a memory-handling class and if enough RAM is found, cache the whole index file.
182 protected function initFileIndex (SplFileInfo $fileInfoInstance) {
183 // Get a file i/o pointer instance for index file
184 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
185 $fileInstance = ObjectFactory::createObjectByConfiguredName('index_file_class', array($fileInfoInstance, $this));
187 // Get iterator instance
188 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
191 $this->setIteratorInstance($iteratorInstance);
193 // Calculate header size
195 strlen(Indexable::INDEX_MAGIC) +
196 strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
197 BinaryFile::LENGTH_COUNT +
198 strlen(chr(BinaryFile::SEPARATOR_HEADER_ENTRIES))
202 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
203 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
205 // Init counters and gaps array
206 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->initCountersGapsArray() ...');
207 $this->getIteratorInstance()->getBinaryFileInstance()->initCountersGapsArray();
209 // Default is not created
212 // Is the file's header initialized?
213 if (!$this->getIteratorInstance()->getBinaryFileInstance()->isFileHeaderInitialized()) {
214 // First pre-allocate a bit
215 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->preAllocateFile(index) ...');
216 $this->getIteratorInstance()->getBinaryFileInstance()->preAllocateFile('index');
218 // Then write file header
219 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->createFileHeader() ...');
220 $this->getIteratorInstance()->getBinaryFileInstance()->createFileHeader();
222 // Mark as freshly created
226 // Load the file header
227 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->readIndexHeader() ...');
228 $this->readIndexHeader();
231 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
233 // Analyze file structure
234 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->analyzeFileStructure() ...');
235 $this->getIteratorInstance()->getBinaryFileInstance()->analyzeFileStructure();
239 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
243 * Calculates minimum length for one entry/block
245 * @return $length Minimum length for one entry/block
247 public function calculateMinimumBlockLength () {
249 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
250 if (self::$minimumBlockLength == 0) {
252 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
253 self::$minimumBlockLength = (
255 BinaryFile::LENGTH_TYPE + strlen(chr(BinaryFile::SEPARATOR_TYPE_POSITION)) +
257 BinaryFile::LENGTH_POSITION + strlen(chr(BinaryFile::SEPARATOR_ENTRIES))
262 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
263 return self::$minimumBlockLength;
267 * "Getter" for file size
269 * @return $fileSize Size of currently loaded file
271 public function getFileSize () {
272 // Call iterator's method
273 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
274 $fileSize = $this->getIteratorInstance()->getBinaryFileInstance()->getFileSize();
277 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileSize=%d - EXIT!', $fileSize));
282 * Searches for next suitable gap the given length of data can fit in
283 * including padding bytes.
285 * @param $length Length of raw data
286 * @return $seekPosition Found next gap's seek position
287 * @throws InvalidArgumentException If the parameter is not valid
288 * @todo Unfinished work
290 public function searchNextGap (int $length) {
291 // Validate parameter
292 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: length=%d - CALLED!', $length));
295 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
299 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: length=%d,this=%s', __METHOD__, __LINE__, $length, print_r($this, true)));
303 * Writes at given position by seeking to it.
305 * @param $seekPosition Seek position in file
306 * @param $dataStream Data to be written
307 * @return mixed Number of writes bytes or false on error
308 * @throws OutOfBoundsException If the position is not seekable
309 * @throws InvalidArgumentException If a parameter is not valid
311 public function writeAtPosition (int $seekPosition, string $dataStream) {
312 // Validate parameter
313 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
314 if ($seekPosition < 0) {
315 // Invalid seek position
316 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
317 } elseif (empty($dataStream)) {
319 throw new InvalidArgumentException('Parameter "dataStream" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
322 // Call iterated object's method
323 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Invoking this->iteratorInstance->binaryFileInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
324 $status = $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition($seekPosition, $dataStream);
327 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
332 * Checks if this index has been fully and properly loaded.
334 * @return $isLoaded Whether this index has been loaded
336 public function isIndexLoaded () {
337 // Is the file gaps-only?
338 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
339 if ($this->getIteratorInstance()->getBinaryFileInstance()->isFileGapsOnly()) {
340 // Then skip below code as this implies the file has been fully analyzed and "loaded"
341 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Underlaying file is gaps-only: Returning TRUE ... - EXIT!');
346 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));