]> 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\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;
15
16 // Import SPL stuff
17 use \OutOfBoundsException;
18 use \SplFileInfo;
19 use \UnexpectedValueException;
20
21 /**
22  * A general file-based index class
23  *
24  * @author              Roland Haeder <webmaster@ship-simu.org>
25  * @version             0.0.0
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
29  *
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.
34  *
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.
39  *
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/>.
42  */
43 abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
44         /**
45          * Minimum block length
46          */
47         private static $minimumBlockLength = 0;
48
49         /**
50          * Protected constructor
51          *
52          * @param       $className      Name of class
53          * @return      void
54          */
55         protected function __construct (string $className) {
56                 // Call parent constructor
57                 parent::__construct($className);
58         }
59
60         /**
61          * Reads the file header
62          *
63          * @return      void
64          * @throws      UnexpectedValueException        If header length or count of elements is invalid
65          */
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();
70
71                 // Get header size
72                 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
73
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);
77
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',
83                                 strlen($data),
84                                 $data,
85                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
86                         ));
87                 } elseif (empty(trim($data, chr(0)))) {
88                         // Empty file header
89                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
90                         return;
91                 } elseif (substr($data, -1, 1) != chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)) {
92                         // Bad last character
93                         throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
94                                 $data,
95                                 chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
96                         ));
97                 }
98
99                 // Okay, then remove it
100                 $data = substr($data, 0, -1);
101
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();
105
106                 /*
107                  * Now split it:
108                  *
109                  * 0 => magic
110                  * 1 => total entries
111                  */
112                 $header = explode(chr(BinaryFile::SEPARATOR_HEADER_DATA), $data);
113
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,
120                 ]);
121
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) {
125                         // Bad header
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));
133                 }
134
135                 // Decode count
136                 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES] = hex2bin($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]);
137
138                 // Set it here
139                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
140
141                 // Trace message
142                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
143         }
144
145         /**
146          * Flushes the file header
147          *
148          * @return      void
149          */
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',
154                         // Magic
155                         Indexable::INDEX_MAGIC,
156
157                         // Separator header data
158                         chr(BinaryFile::SEPARATOR_HEADER_DATA),
159
160                         // Total entries
161                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
162
163                         // Separator header<->entries
164                         chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
165                 );
166
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);
170
171                 // Trace message
172                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
173         }
174
175         /**
176          * Initializes this file-based index
177          *
178          * @param       $fileInfoInstance       An instance of a SplFileInfo class
179          * @return      void
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.
181          */
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));
186
187                 // Get iterator instance
188                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
189
190                 // Set iterator here
191                 $this->setIteratorInstance($iteratorInstance);
192
193                 // Calculate header size
194                 $headerSize = (
195                         strlen(Indexable::INDEX_MAGIC) +
196                         strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
197                         BinaryFile::LENGTH_COUNT +
198                         strlen(chr(BinaryFile::SEPARATOR_HEADER_ENTRIES))
199                 );
200
201                 // Set it
202                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
203                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
204
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();
208
209                 // Default is not created
210                 $created = false;
211
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');
217
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();
221
222                         // Mark as freshly created
223                         $created = true;
224                 }
225
226                 // Load the file header
227                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Invoking this->readIndexHeader() ...');
228                 $this->readIndexHeader();
229
230                 // Freshly created?
231                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
232                 if (!$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();
236                 }
237
238                 // Trace message
239                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
240         }
241
242         /**
243          * Calculates minimum length for one entry/block
244          *
245          * @return      $length         Minimum length for one entry/block
246          */
247         public function calculateMinimumBlockLength () {
248                 // Is it "cached"?
249                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
250                 if (self::$minimumBlockLength == 0) {
251                         // Calulcate it
252                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
253                         self::$minimumBlockLength = (
254                                 // Type
255                                 BinaryFile::LENGTH_TYPE + strlen(chr(BinaryFile::SEPARATOR_TYPE_POSITION)) +
256                                 // Position
257                                 BinaryFile::LENGTH_POSITION + strlen(chr(BinaryFile::SEPARATOR_ENTRIES))
258                         );
259                 }
260
261                 // Return it
262                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
263                 return self::$minimumBlockLength;
264         }
265
266         /**
267          * "Getter" for file size
268          *
269          * @return      $fileSize       Size of currently loaded file
270          */
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();
275
276                 // Return it
277                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileSize=%d - EXIT!', $fileSize));
278                 return $fileSize;
279         }
280
281         /**
282          * Searches for next suitable gap the given length of data can fit in
283          * including padding bytes.
284          *
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
289          */
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));
293                 if ($length <= 0) {
294                         // Throw IAE
295                         throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
296                 }
297
298                 // Debug message
299                 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: length=%d,this=%s', __METHOD__, __LINE__, $length, print_r($this, true)));
300         }
301
302         /**
303          * Writes at given position by seeking to it.
304          *
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
310          */
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)) {
318                         // Empty dataStream
319                         throw new InvalidArgumentException('Parameter "dataStream" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
320                 }
321
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);
325
326                 // Return status
327                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
328                 return $status;
329         }
330
331         /**
332          * Checks if this index has been fully and properly loaded.
333          *
334          * @return      $isLoaded       Whether this index has been loaded
335          */
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!');
342                         return TRUE;
343                 }
344
345                 // Debug message
346                 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));
347         }
348
349 }