3 namespace Org\Mxchange\CoreFramework\Index\File;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
7 use Org\Mxchange\CoreFramework\Filesystem\File\BaseBinaryFile;
8 use Org\Mxchange\CoreFramework\Index\BaseIndex;
9 use Org\Mxchange\CoreFramework\Index\Indexable;
10 use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
13 use \OutOfBoundsException;
15 use \UnexpectedValueException;
18 * A general file-based index class
20 * @author Roland Haeder <webmaster@ship-simu.org>
22 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
23 * @license GNU GPL 3.0 or any newer version
24 * @link http://www.ship-simu.org
26 * This program is free software: you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation, either version 3 of the License, or
29 * (at your option) any later version.
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <http://www.gnu.org/licenses/>.
39 abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
41 * Protected constructor
45 protected function __construct () {
46 // Call parent constructor
47 parent::__construct(__CLASS__);
51 * Reads the file header
54 * @throws UnexpectedValueException If header length or count of elements is invalid
56 public function readIndexHeader () {
57 // First rewind to beginning as the header sits at the beginning ...
58 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
59 $this->getIteratorInstance()->rewind();
61 // Then read it (see constructor for calculation)
62 $data = $this->getIteratorInstance()->read($this->getIteratorInstance()->getHeaderSize());
64 // Have all requested bytes been read?
65 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getHeaderSize()));
66 if (strlen($data) != $this->getIteratorInstance()->getHeaderSize()) {
67 // Invalid header length
68 throw new UnexpectedValueException(sprintf('data(%d)=%s is not expected length %d',
71 $this->getIteratorInstance()->getHeaderSize()
73 } elseif (empty(trim($data, chr(0)))) {
75 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
77 } elseif (substr($data, -1, 1) != chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)) {
79 throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
81 chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
85 // Okay, then remove it
86 $data = substr($data, 0, -1);
88 // And update seek position
89 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->updateSeekPosition() ...');
90 $this->getIteratorInstance()->updateSeekPosition();
98 $header = explode(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA), $data);
100 // Check if the array has only 3 elements
101 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header()=%d', count($header)));
102 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header(%d)=%s', count($header), print_r($header, true)));
103 if (count($header) != 2) {
105 throw new UnexpectedValueException(sprintf('header()=%d is not expected value 2', count($header)));
106 } elseif ($header[0] !== Indexable::INDEX_MAGIC) {
107 // Magic must be in first element
108 throw new UnexpectedValueException(sprintf('header[0]=%s is not the expected magic (%s)', $header[0], Indexable::INDEX_MAGIC));
109 } elseif (strlen($header[1]) != BaseBinaryFile::LENGTH_COUNT) {
110 // Length of total entries not matching
111 throw new UnexpectedValueException(sprintf('header[1](%d)=%s does not have expected length %d', strlen($header[1]), $header[1], BaseBinaryFile::LENGTH_COUNT));
115 $header[1] = hex2bin($header[1]);
118 $this->getIteratorInstance()->setHeader($header);
121 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
125 * Flushes the file header
129 public function flushFileHeader () {
130 // Put all informations together
131 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
132 $header = sprintf('%s%s%s%s',
134 Indexable::INDEX_MAGIC,
136 // Separator header data
137 chr(BaseBinaryFile::SEPARATOR_HEADER_DATA),
140 str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getCounter()), BaseBinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
142 // Separator header<->entries
143 chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
146 // Write it to disk (header is always at seek position 0)
147 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->writeAtPosition(0, header=%s) ...', $header));
148 $this->getIteratorInstance()->writeAtPosition(0, $header);
151 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
155 * Initializes this index
157 * @param $fileInfoInstance An instance of a SplFileInfo class
159 * @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.
161 protected function initIndex (SplFileInfo $fileInfoInstance) {
162 // Get a file i/o pointer instance for index file
163 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
164 $fileInstance = ObjectFactory::createObjectByConfiguredName('index_file_class', array($fileInfoInstance, $this));
166 // Get iterator instance
167 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
170 $this->setIteratorInstance($iteratorInstance);
172 // Calculate header size
174 strlen(Indexable::INDEX_MAGIC) +
175 strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
176 BaseBinaryFile::LENGTH_COUNT +
177 strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES))
181 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
182 $this->getIteratorInstance()->setHeaderSize($headerSize);
184 // Init counters and gaps array
185 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->initCountersGapsArray() ...');
186 $this->getIteratorInstance()->initCountersGapsArray();
188 // Default is not created
191 // Is the file's header initialized?
192 if (!$this->getIteratorInstance()->isFileHeaderInitialized()) {
193 // First pre-allocate a bit
194 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->preAllocateFile(index) ...');
195 $this->getIteratorInstance()->preAllocateFile('index');
197 // Then write file header
198 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->createFileHeader() ...');
199 $this->getIteratorInstance()->createFileHeader();
201 // Mark as freshly created
205 // Load the file header
206 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->readIndexHeader() ...');
207 $this->readIndexHeader();
210 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
212 // Analyze file structure
213 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->analyzeFileStructure() ...');
214 $this->getIteratorInstance()->analyzeFileStructure();
218 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
222 * Calculates minimum length for one entry/block
224 * @return $length Minimum length for one entry/block
226 public function calculateMinimumBlockLength () {
228 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
229 if (self::$minimumBlockLength == 0) {
231 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
232 self::$minimumBlockLength = (
234 BaseBinaryFile::LENGTH_TYPE + strlen(chr(BaseBinaryFile::SEPARATOR_TYPE_POSITION)) +
236 BaseBinaryFile::LENGTH_POSITION + strlen(chr(BaseBinaryFile::SEPARATOR_ENTRIES))
241 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
242 return self::$minimumBlockLength;
246 * "Getter" for file size
248 * @return $fileSize Size of currently loaded file
250 public function getFileSize () {
251 // Call iterator's method
252 return $this->getIteratorInstance()->getFileSize();
256 * Searches for next suitable gap the given length of data can fit in
257 * including padding bytes.
259 * @param $length Length of raw data
260 * @return $seekPosition Found next gap's seek position
261 * @throws InvalidArgumentException If the parameter is not valid
262 * @todo Unfinished work
264 public function searchNextGap (int $length) {
265 // Validate parameter
266 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: length=%d - CALLED!', $length));
269 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
273 $this->partialStub('length=' . $length);
277 * Writes at given position by seeking to it.
279 * @param $seekPosition Seek position in file
280 * @param $dataStream Data to be written
281 * @return mixed Number of writes bytes or false on error
282 * @throws OutOfBoundsException If the position is not seekable
283 * @throws InvalidArgumentException If a parameter is not valid
285 public function writeAtPosition (int $seekPosition, string $dataStream) {
286 // Validate parameter
287 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
288 if ($seekPosition < 0) {
289 // Invalid seek position
290 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
291 } elseif (empty($dataStream)) {
293 throw new InvalidArgumentException('Parameter "dataStream" is empty');
296 // Call iterated object's method
297 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
298 $status = $this->getIteratorInstance()->writeAtPosition($seekPosition, $dataStream);
301 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
306 * Checks if this index has been fully and properly loaded.
308 * @return $isLoaded Whether this index has been loaded
310 public function isIndexLoaded () {
312 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
313 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));