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\Index\BaseIndex;
10 use Org\Mxchange\CoreFramework\Index\Indexable;
11 use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
14 use \OutOfBoundsException;
16 use \UnexpectedValueException;
19 * A general file-based index class
21 * @author Roland Haeder <webmaster@ship-simu.org>
23 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
24 * @license GNU GPL 3.0 or any newer version
25 * @link http://www.ship-simu.org
27 * This program is free software: you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation, either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program. If not, see <http://www.gnu.org/licenses/>.
40 abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
42 * Minimum block length
44 private static $minimumBlockLength = 0;
47 * Protected constructor
49 * @param $className Name of class
52 protected function __construct (string $className) {
53 // Call parent constructor
54 parent::__construct($className);
58 * Reads the file header
61 * @throws UnexpectedValueException If header length or count of elements is invalid
63 public function readIndexHeader () {
64 // First rewind to beginning as the header sits at the beginning ...
65 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
66 $this->getIteratorInstance()->rewind();
69 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
71 // Then read it (see constructor for calculation)
72 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: headerSize=%d', $headerSize));
73 $data = $this->getIteratorInstance()->getBinaryFileInstance()->read($headerSize);
75 // Have all requested bytes been read?
76 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()));
77 if (strlen($data) != $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
78 // Invalid header length
79 throw new UnexpectedValueException(sprintf('data(%d)=%s is not expected length %d',
82 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
84 } elseif (empty(trim($data, chr(0)))) {
86 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
88 } elseif (substr($data, -1, 1) != chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)) {
90 throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
92 chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
96 // Okay, then remove it
97 $data = substr($data, 0, -1);
99 // And update seek position
100 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->updateSeekPosition() ...');
101 $this->getIteratorInstance()->getBinaryFileInstance()->updateSeekPosition();
109 $header = explode(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA), $data);
111 // Check if the array has only 3 elements
112 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header()=%d', count($header)));
113 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header(%d)=%s', count($header), print_r($header, true)));
114 if (count($header) != 2) {
116 throw new UnexpectedValueException(sprintf('header()=%d is not expected value 2', count($header)));
117 } elseif ($header[0] !== Indexable::INDEX_MAGIC) {
118 // Magic must be in first element
119 throw new UnexpectedValueException(sprintf('header[0]=%s is not the expected magic (%s)', $header[0], Indexable::INDEX_MAGIC));
120 } elseif (strlen($header[1]) != BaseBinaryFile::LENGTH_COUNT) {
121 // Length of total entries not matching
122 throw new UnexpectedValueException(sprintf('header[1](%d)=%s does not have expected length %d', strlen($header[1]), $header[1], BaseBinaryFile::LENGTH_COUNT));
126 $header[1] = hex2bin($header[1]);
129 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
132 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
136 * Flushes the file header
140 public function flushFileHeader () {
141 // Put all informations together
142 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
143 $header = sprintf('%s%s%s%s',
145 Indexable::INDEX_MAGIC,
147 // Separator header data
148 chr(BaseBinaryFile::SEPARATOR_HEADER_DATA),
151 str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BaseBinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
153 // Separator header<->entries
154 chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
157 // Write it to disk (header is always at seek position 0)
158 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->writeAtPosition(0, header=%s) ...', $header));
159 $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition(0, $header);
162 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
166 * Initializes this file-based index
168 * @param $fileInfoInstance An instance of a SplFileInfo class
170 * @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.
172 protected function initFileIndex (SplFileInfo $fileInfoInstance) {
173 // Get a file i/o pointer instance for index file
174 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
175 $fileInstance = ObjectFactory::createObjectByConfiguredName('index_file_class', array($fileInfoInstance, $this));
177 // Get iterator instance
178 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
181 $this->setIteratorInstance($iteratorInstance);
183 // Calculate header size
185 strlen(Indexable::INDEX_MAGIC) +
186 strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
187 BaseBinaryFile::LENGTH_COUNT +
188 strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES))
192 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
193 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
195 // Init counters and gaps array
196 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->initCountersGapsArray() ...');
197 $this->getIteratorInstance()->getBinaryFileInstance()->initCountersGapsArray();
199 // Default is not created
202 // Is the file's header initialized?
203 if (!$this->getIteratorInstance()->getBinaryFileInstance()->isFileHeaderInitialized()) {
204 // First pre-allocate a bit
205 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->preAllocateFile(index) ...');
206 $this->getIteratorInstance()->getBinaryFileInstance()->preAllocateFile('index');
208 // Then write file header
209 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->createFileHeader() ...');
210 $this->getIteratorInstance()->getBinaryFileInstance()->createFileHeader();
212 // Mark as freshly created
216 // Load the file header
217 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->readIndexHeader() ...');
218 $this->readIndexHeader();
221 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
223 // Analyze file structure
224 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->analyzeFileStructure() ...');
225 $this->getIteratorInstance()->getBinaryFileInstance()->analyzeFileStructure();
229 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
233 * Calculates minimum length for one entry/block
235 * @return $length Minimum length for one entry/block
237 public function calculateMinimumBlockLength () {
239 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
240 if (self::$minimumBlockLength == 0) {
242 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
243 self::$minimumBlockLength = (
245 BaseBinaryFile::LENGTH_TYPE + strlen(chr(BaseBinaryFile::SEPARATOR_TYPE_POSITION)) +
247 BaseBinaryFile::LENGTH_POSITION + strlen(chr(BaseBinaryFile::SEPARATOR_ENTRIES))
252 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
253 return self::$minimumBlockLength;
257 * "Getter" for file size
259 * @return $fileSize Size of currently loaded file
261 public function getFileSize () {
262 // Call iterator's method
263 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
264 $fileSize = $this->getIteratorInstance()->getBinaryFileInstance()->getFileSize();
267 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileSize=%d - EXIT!', $fileSize));
272 * Searches for next suitable gap the given length of data can fit in
273 * including padding bytes.
275 * @param $length Length of raw data
276 * @return $seekPosition Found next gap's seek position
277 * @throws InvalidArgumentException If the parameter is not valid
278 * @todo Unfinished work
280 public function searchNextGap (int $length) {
281 // Validate parameter
282 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: length=%d - CALLED!', $length));
285 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
289 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: length=%d,this=%s', __METHOD__, __LINE__, $length, print_r($this, true)));
293 * Writes at given position by seeking to it.
295 * @param $seekPosition Seek position in file
296 * @param $dataStream Data to be written
297 * @return mixed Number of writes bytes or false on error
298 * @throws OutOfBoundsException If the position is not seekable
299 * @throws InvalidArgumentException If a parameter is not valid
301 public function writeAtPosition (int $seekPosition, string $dataStream) {
302 // Validate parameter
303 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
304 if ($seekPosition < 0) {
305 // Invalid seek position
306 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
307 } elseif (empty($dataStream)) {
309 throw new InvalidArgumentException('Parameter "dataStream" is empty');
312 // Call iterated object's method
313 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
314 $status = $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition($seekPosition, $dataStream);
317 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
322 * Checks if this index has been fully and properly loaded.
324 * @return $isLoaded Whether this index has been loaded
326 public function isIndexLoaded () {
327 // Is the file gaps-only?
328 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
329 if ($this->getIteratorInstance()->getBinaryFileInstance()->isFileGapsOnly()) {
330 // Then skip below code as this implies the file has been fully analyzed and "loaded"
331 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Underlaying file is gaps-only: Returning TRUE ... - EXIT!');
336 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));