]> git.mxchange.org Git - core.git/blob - framework/main/classes/index/file/class_BaseFileIndex.php
Continued:
[core.git] / framework / main / classes / index / file / class_BaseFileIndex.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Index\File;
4
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\Index\BaseIndex;
11 use Org\Mxchange\CoreFramework\Index\Indexable;
12 use Org\Mxchange\CoreFramework\Utils\Arrays\ArrayUtils;
13 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
14
15 // Import SPL stuff
16 use \OutOfBoundsException;
17 use \SplFileInfo;
18 use \UnexpectedValueException;
19
20 /**
21  * A general file-based index class
22  *
23  * @author              Roland Haeder <webmaster@ship-simu.org>
24  * @version             0.0.0
25  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
26  * @license             GNU GPL 3.0 or any newer version
27  * @link                http://www.ship-simu.org
28  *
29  * This program is free software: you can redistribute it and/or modify
30  * it under the terms of the GNU General Public License as published by
31  * the Free Software Foundation, either version 3 of the License, or
32  * (at your option) any later version.
33  *
34  * This program is distributed in the hope that it will be useful,
35  * but WITHOUT ANY WARRANTY; without even the implied warranty of
36  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37  * GNU General Public License for more details.
38  *
39  * You should have received a copy of the GNU General Public License
40  * along with this program. If not, see <http://www.gnu.org/licenses/>.
41  */
42 abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
43         /**
44          * Minimum block length
45          */
46         private static $minimumBlockLength = 0;
47
48         /**
49          * Protected constructor
50          *
51          * @param       $className      Name of class
52          * @return      void
53          */
54         protected function __construct (string $className) {
55                 // Call parent constructor
56                 parent::__construct($className);
57         }
58
59         /**
60          * Reads the file header
61          *
62          * @return      void
63          * @throws      UnexpectedValueException        If header length or count of elements is invalid
64          */
65         public function readIndexHeader () {
66                 // First rewind to beginning as the header sits at the beginning ...
67                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
68                 $this->getIteratorInstance()->rewind();
69
70                 // Get header size
71                 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
72
73                 // Then read it (see constructor for calculation)
74                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: headerSize=%d', $headerSize));
75                 $data = $this->getIteratorInstance()->getBinaryFileInstance()->read($headerSize);
76
77                 // Have all requested bytes been read?
78                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()));
79                 if (strlen($data) != $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
80                         // Invalid header length
81                         throw new UnexpectedValueException(sprintf('data(%d)=%s is not expected length %d',
82                                 strlen($data),
83                                 $data,
84                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
85                         ));
86                 } elseif (empty(trim($data, chr(0)))) {
87                         // Empty file header
88                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
89                         return;
90                 } elseif (substr($data, -1, 1) != chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)) {
91                         // Bad last character
92                         throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
93                                 $data,
94                                 chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
95                         ));
96                 }
97
98                 // Okay, then remove it
99                 $data = substr($data, 0, -1);
100
101                 // And update seek position
102                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->updateSeekPosition() ...');
103                 $this->getIteratorInstance()->getBinaryFileInstance()->updateSeekPosition();
104
105                 /*
106                  * Now split it:
107                  *
108                  * 0 => magic
109                  * 1 => total entries
110                  */
111                 $header = explode(chr(BinaryFile::SEPARATOR_HEADER_DATA), $data);
112
113                 // Map numeric entries to associative (alpha-numeric) elements
114                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling ArrayUtils::mapNumericKeysToAssociative(%d) ...', count($header)));
115                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: header(%d)=%s', count($header), print_r($header, true)));
116                 $header = ArrayUtils::mapNumericKeysToAssociative($header, [
117                         BinaryFile::HEADER_NAME_MAGIC,
118                         BinaryFile::HEADER_NAME_TOTAL_ENTRIES,
119                 ]);
120
121                 // Check if the array has only 2 elements
122                 /* 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)));
123                 if (count($header) != BinaryFile::HEADER_INDEX_ELEMENT_COUNT) {
124                         // Bad header
125                         throw new UnexpectedValueException(sprintf('header()=%d is not expected value %d', count($header), BinaryFile::HEADER_INDEX_ELEMENT_COUNT));
126                 } elseif ($header[BinaryFile::HEADER_NAME_MAGIC] !== Indexable::INDEX_MAGIC) {
127                         // Magic must be in first element
128                         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));
129                 } elseif (strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]) != BinaryFile::LENGTH_COUNT) {
130                         // Length of total entries not matching
131                         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));
132                 }
133
134                 // Decode count
135                 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES] = hex2bin($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]);
136
137                 // Set it here
138                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
139
140                 // Trace message
141                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
142         }
143
144         /**
145          * Flushes the file header
146          *
147          * @return      void
148          */
149         public function flushFileHeader () {
150                 // Put all informations together
151                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
152                 $header = sprintf('%s%s%s%s',
153                         // Magic
154                         Indexable::INDEX_MAGIC,
155
156                         // Separator header data
157                         chr(BinaryFile::SEPARATOR_HEADER_DATA),
158
159                         // Total entries
160                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
161
162                         // Separator header<->entries
163                         chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
164                 );
165
166                 // Write it to disk (header is always at seek position 0)
167                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->writeAtPosition(0, header=%s) ...', $header));
168                 $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition(0, $header);
169
170                 // Trace message
171                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
172         }
173
174         /**
175          * Initializes this file-based index
176          *
177          * @param       $fileInfoInstance       An instance of a SplFileInfo class
178          * @return      void
179          * @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.
180          */
181         protected function initFileIndex (SplFileInfo $fileInfoInstance) {
182                 // Get a file i/o pointer instance for index file
183                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
184                 $fileInstance = ObjectFactory::createObjectByConfiguredName('index_file_class', array($fileInfoInstance, $this));
185
186                 // Get iterator instance
187                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
188
189                 // Set iterator here
190                 $this->setIteratorInstance($iteratorInstance);
191
192                 // Calculate header size
193                 $headerSize = (
194                         strlen(Indexable::INDEX_MAGIC) +
195                         strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
196                         BinaryFile::LENGTH_COUNT +
197                         strlen(chr(BinaryFile::SEPARATOR_HEADER_ENTRIES))
198                 );
199
200                 // Set it
201                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
202                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
203
204                 // Init counters and gaps array
205                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->initCountersGapsArray() ...');
206                 $this->getIteratorInstance()->getBinaryFileInstance()->initCountersGapsArray();
207
208                 // Default is not created
209                 $created = false;
210
211                 // Is the file's header initialized?
212                 if (!$this->getIteratorInstance()->getBinaryFileInstance()->isFileHeaderInitialized()) {
213                         // First pre-allocate a bit
214                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->preAllocateFile(index) ...');
215                         $this->getIteratorInstance()->getBinaryFileInstance()->preAllocateFile('index');
216
217                         // Then write file header
218                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->createFileHeader() ...');
219                         $this->getIteratorInstance()->getBinaryFileInstance()->createFileHeader();
220
221                         // Mark as freshly created
222                         $created = true;
223                 }
224
225                 // Load the file header
226                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->readIndexHeader() ...');
227                 $this->readIndexHeader();
228
229                 // Freshly created?
230                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
231                 if (!$created) {
232                         // Analyze file structure
233                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->analyzeFileStructure() ...');
234                         $this->getIteratorInstance()->getBinaryFileInstance()->analyzeFileStructure();
235                 }
236
237                 // Trace message
238                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
239         }
240
241         /**
242          * Calculates minimum length for one entry/block
243          *
244          * @return      $length         Minimum length for one entry/block
245          */
246         public function calculateMinimumBlockLength () {
247                 // Is it "cached"?
248                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
249                 if (self::$minimumBlockLength == 0) {
250                         // Calulcate it
251                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
252                         self::$minimumBlockLength = (
253                                 // Type
254                                 BinaryFile::LENGTH_TYPE + strlen(chr(BinaryFile::SEPARATOR_TYPE_POSITION)) +
255                                 // Position
256                                 BinaryFile::LENGTH_POSITION + strlen(chr(BinaryFile::SEPARATOR_ENTRIES))
257                         );
258                 }
259
260                 // Return it
261                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
262                 return self::$minimumBlockLength;
263         }
264
265         /**
266          * "Getter" for file size
267          *
268          * @return      $fileSize       Size of currently loaded file
269          */
270         public function getFileSize () {
271                 // Call iterator's method
272                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
273                 $fileSize = $this->getIteratorInstance()->getBinaryFileInstance()->getFileSize();
274
275                 // Return it
276                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileSize=%d - EXIT!', $fileSize));
277                 return $fileSize;
278         }
279
280         /**
281          * Searches for next suitable gap the given length of data can fit in
282          * including padding bytes.
283          *
284          * @param       $length                 Length of raw data
285          * @return      $seekPosition   Found next gap's seek position
286          * @throws      InvalidArgumentException        If the parameter is not valid
287          * @todo        Unfinished work
288          */
289         public function searchNextGap (int $length) {
290                 // Validate parameter
291                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: length=%d - CALLED!', $length));
292                 if ($length <= 0) {
293                         // Throw IAE
294                         throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
295                 }
296
297                 // Debug message
298                 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: length=%d,this=%s', __METHOD__, __LINE__, $length, print_r($this, true)));
299         }
300
301         /**
302          * Writes at given position by seeking to it.
303          *
304          * @param       $seekPosition   Seek position in file
305          * @param       $dataStream             Data to be written
306          * @return      mixed                   Number of writes bytes or false on error
307          * @throws      OutOfBoundsException    If the position is not seekable
308          * @throws      InvalidArgumentException        If a parameter is not valid
309          */
310         public function writeAtPosition (int $seekPosition, string $dataStream) {
311                 // Validate parameter
312                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
313                 if ($seekPosition < 0) {
314                         // Invalid seek position
315                         throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
316                 } elseif (empty($dataStream)) {
317                         // Empty dataStream
318                         throw new InvalidArgumentException('Parameter "dataStream" is empty');
319                 }
320
321                 // Call iterated object's method
322                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Calling this->iteratorInstance->binaryFileInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
323                 $status = $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition($seekPosition, $dataStream);
324
325                 // Return status
326                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
327                 return $status;
328         }
329
330         /**
331          * Checks if this index has been fully and properly loaded.
332          *
333          * @return      $isLoaded       Whether this index has been loaded
334          */
335         public function isIndexLoaded () {
336                 // Is the file gaps-only?
337                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
338                 if ($this->getIteratorInstance()->getBinaryFileInstance()->isFileGapsOnly()) {
339                         // Then skip below code as this implies the file has been fully analyzed and "loaded"
340                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Underlaying file is gaps-only: Returning TRUE ... - EXIT!');
341                         return TRUE;
342                 }
343
344                 // Debug message
345                 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));
346         }
347
348 }