+<?php
+// Own namespace
+namespace Org\Mxchange\CoreFramework\Index\File;
+
+// Import framework stuff
+use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
+use Org\Mxchange\CoreFramework\Filesystem\File\BaseBinaryFile;
+use Org\Mxchange\CoreFramework\Index\BaseIndex;
+use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
+
+// Import SPL stuff
+use \OutOfBoundsException;
+use \SplFileInfo;
+use \UnexpectedValueException;
+
+/**
+ * A general file-based index class
+ *
+ * @author Roland Haeder <webmaster@ship-simu.org>
+ * @version 0.0.0
+ * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
+ * @license GNU GPL 3.0 or any newer version
+ * @link http://www.ship-simu.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
+ /**
+ * Protected constructor
+ *
+ * @return void
+ */
+ protected function __construct () {
+ // Call parent constructor
+ parent::__construct(__CLASS__);
+ }
+
+ /**
+ * Reads the file header
+ *
+ * @return void
+ * @throws UnexpectedValueException If header length or count of elements is invalid
+ */
+ public function readIndexHeader () {
+ // First rewind to beginning as the header sits at the beginning ...
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
+ $this->getIteratorInstance()->rewind();
+
+ // Then read it (see constructor for calculation)
+ $data = $this->getIteratorInstance()->read($this->getIteratorInstance()->getHeaderSize());
+
+ // Have all requested bytes been read?
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getHeaderSize()));
+ if (strlen($data) != $this->getIteratorInstance()->getHeaderSize()) {
+ // Invalid header length
+ throw new UnexpectedValueException(sprintf('data(%d)=%s is not expected length %d',
+ strlen($data),
+ $data,
+ $this->getIteratorInstance()->getHeaderSize()
+ ));
+ } elseif (empty(trim($data, chr(0)))) {
+ // Empty file header
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
+ return;
+ } elseif (substr($data, -1, 1) != chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)) {
+ // Bad last character
+ throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
+ $data,
+ chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
+ ));
+ }
+
+ // Okay, then remove it
+ $data = substr($data, 0, -1);
+
+ // And update seek position
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->updateSeekPosition() ...');
+ $this->getIteratorInstance()->updateSeekPosition();
+
+ /*
+ * Now split it:
+ *
+ * 0 => magic
+ * 1 => total entries
+ */
+ $header = explode(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA), $data);
+
+ // Check if the array has only 3 elements
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header()=%d', count($header)));
+ //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header(%d)=%s', count($header), print_r($header, true)));
+ if (count($header) != 2) {
+ // Bad header
+ throw new UnexpectedValueException(sprintf('header()=%d is not expected value 2', count($header)));
+ } elseif ($header[0] !== Indexable::INDEX_MAGIC) {
+ // Magic must be in first element
+ throw new UnexpectedValueException(sprintf('header[0]=%s is not the expected magic (%s)', $header[0], Indexable::INDEX_MAGIC));
+ } elseif (strlen($header[1]) != BaseBinaryFile::LENGTH_COUNT) {
+ // Length of total entries not matching
+ throw new UnexpectedValueException(sprintf('header[1](%d)=%s does not have expected length %d', strlen($header[1]), $header[1], BaseBinaryFile::LENGTH_COUNT));
+ }
+
+ // Decode count
+ $header[1] = hex2bin($header[1]);
+
+ // Set it here
+ $this->getIteratorInstance()->setHeader($header);
+
+ // Trace message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
+ }
+
+ /**
+ * Flushes the file header
+ *
+ * @return void
+ */
+ public function flushFileHeader () {
+ // Put all informations together
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
+ $header = sprintf('%s%s%s%s',
+ // Magic
+ Indexable::INDEX_MAGIC,
+
+ // Separator header data
+ chr(BaseBinaryFile::SEPARATOR_HEADER_DATA),
+
+ // Total entries
+ str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getCounter()), BaseBinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
+
+ // Separator header<->entries
+ chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
+ );
+
+ // Write it to disk (header is always at seek position 0)
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->writeAtPosition(0, header=%s) ...', $header));
+ $this->getIteratorInstance()->writeAtPosition(0, $header);
+
+ // Trace message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
+ }
+
+ /**
+ * Initializes this index
+ *
+ * @param $fileInfoInstance An instance of a SplFileInfo class
+ * @return void
+ * @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.
+ */
+ protected function initIndex (SplFileInfo $fileInfoInstance) {
+ // Get a file i/o pointer instance for index file
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
+ $fileInstance = ObjectFactory::createObjectByConfiguredName('index_file_class', array($fileInfoInstance, $this));
+
+ // Get iterator instance
+ $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
+
+ // Set iterator here
+ $this->setIteratorInstance($iteratorInstance);
+
+ // Calculate header size
+ $headerSize = (
+ strlen(Indexable::INDEX_MAGIC) +
+ strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
+ BaseBinaryFile::LENGTH_COUNT +
+ strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES))
+ );
+
+ // Set it
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
+ $this->getIteratorInstance()->setHeaderSize($headerSize);
+
+ // Init counters and gaps array
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->initCountersGapsArray() ...');
+ $this->getIteratorInstance()->initCountersGapsArray();
+
+ // Default is not created
+ $created = false;
+
+ // Is the file's header initialized?
+ if (!$this->getIteratorInstance()->isFileHeaderInitialized()) {
+ // First pre-allocate a bit
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->preAllocateFile(index) ...');
+ $this->getIteratorInstance()->preAllocateFile('index');
+
+ // Then write file header
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->createFileHeader() ...');
+ $this->getIteratorInstance()->createFileHeader();
+
+ // Mark as freshly created
+ $created = true;
+ }
+
+ // Load the file header
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->readIndexHeader() ...');
+ $this->readIndexHeader();
+
+ // Freshly created?
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
+ if (!$created) {
+ // Analyze file structure
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->analyzeFileStructure() ...');
+ $this->getIteratorInstance()->analyzeFileStructure();
+ }
+
+ // Trace message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
+ }
+
+ /**
+ * Calculates minimum length for one entry/block
+ *
+ * @return $length Minimum length for one entry/block
+ */
+ public function calculateMinimumBlockLength () {
+ // Is it "cached"?
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
+ if (self::$minimumBlockLength == 0) {
+ // Calulcate it
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
+ self::$minimumBlockLength = (
+ // Type
+ BaseBinaryFile::LENGTH_TYPE + strlen(chr(BaseBinaryFile::SEPARATOR_TYPE_POSITION)) +
+ // Position
+ BaseBinaryFile::LENGTH_POSITION + strlen(chr(BaseBinaryFile::SEPARATOR_ENTRIES))
+ );
+ }
+
+ // Return it
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
+ return self::$minimumBlockLength;
+ }
+
+ /**
+ * "Getter" for file size
+ *
+ * @return $fileSize Size of currently loaded file
+ */
+ public function getFileSize () {
+ // Call iterator's method
+ return $this->getIteratorInstance()->getFileSize();
+ }
+
+ /**
+ * Searches for next suitable gap the given length of data can fit in
+ * including padding bytes.
+ *
+ * @param $length Length of raw data
+ * @return $seekPosition Found next gap's seek position
+ * @throws InvalidArgumentException If the parameter is not valid
+ * @todo Unfinished work
+ */
+ public function searchNextGap (int $length) {
+ // Validate parameter
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: length=%d - CALLED!', $length));
+ if ($length <= 0) {
+ // Throw IAE
+ throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
+ }
+
+ // Partial stub!
+ $this->partialStub('length=' . $length);
+ }
+
+ /**
+ * Writes at given position by seeking to it.
+ *
+ * @param $seekPosition Seek position in file
+ * @param $dataStream Data to be written
+ * @return mixed Number of writes bytes or false on error
+ * @throws OutOfBoundsException If the position is not seekable
+ * @throws InvalidArgumentException If a parameter is not valid
+ */
+ public function writeAtPosition (int $seekPosition, string $dataStream) {
+ // Validate parameter
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
+ if ($seekPosition < 0) {
+ // Invalid seek position
+ throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
+ } elseif (empty($dataStream)) {
+ // Empty dataStream
+ throw new InvalidArgumentException('Parameter "dataStream" is empty');
+ }
+
+ // Call iterated object's method
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
+ $status = $this->getIteratorInstance()->writeAtPosition($seekPosition, $dataStream);
+
+ // Return status
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
+ return $status;
+ }
+
+ /**
+ * Checks if this index has been fully and properly loaded.
+ *
+ * @return $isLoaded Whether this index has been loaded
+ */
+ public function isIndexLoaded () {
+ // Trace message
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
+ /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));
+ }
+
+}