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