]> 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\Index\BaseIndex;
10 use Org\Mxchange\CoreFramework\Index\Indexable;
11 use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
12
13 // Import SPL stuff
14 use \OutOfBoundsException;
15 use \SplFileInfo;
16 use \UnexpectedValueException;
17
18 /**
19  * A general file-based index class
20  *
21  * @author              Roland Haeder <webmaster@ship-simu.org>
22  * @version             0.0.0
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
26  *
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.
31  *
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.
36  *
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/>.
39  */
40 abstract class BaseFileIndex extends BaseIndex implements FileIndexer {
41         /**
42          * Minimum block length
43          */
44         private static $minimumBlockLength = 0;
45
46         /**
47          * Protected constructor
48          *
49          * @param       $className      Name of class
50          * @return      void
51          */
52         protected function __construct (string $className) {
53                 // Call parent constructor
54                 parent::__construct($className);
55         }
56
57         /**
58          * Reads the file header
59          *
60          * @return      void
61          * @throws      UnexpectedValueException        If header length or count of elements is invalid
62          */
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();
67
68                 // Get header size
69                 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
70
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);
74
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',
80                                 strlen($data),
81                                 $data,
82                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
83                         ));
84                 } elseif (empty(trim($data, chr(0)))) {
85                         // Empty file header
86                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: File header is empty - EXIT!');
87                         return;
88                 } elseif (substr($data, -1, 1) != chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)) {
89                         // Bad last character
90                         throw new UnexpectedValueException(sprintf('data=%s does not end with "%s"',
91                                 $data,
92                                 chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
93                         ));
94                 }
95
96                 // Okay, then remove it
97                 $data = substr($data, 0, -1);
98
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();
102
103                 /*
104                  * Now split it:
105                  *
106                  * 0 => magic
107                  * 1 => total entries
108                  */
109                 $header = explode(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA), $data);
110
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) {
115                         // Bad header
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));
123                 }
124
125                 // Decode count
126                 $header[1] = hex2bin($header[1]);
127
128                 // Set it here
129                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
130
131                 // Trace message
132                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
133         }
134
135         /**
136          * Flushes the file header
137          *
138          * @return      void
139          */
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',
144                         // Magic
145                         Indexable::INDEX_MAGIC,
146
147                         // Separator header data
148                         chr(BaseBinaryFile::SEPARATOR_HEADER_DATA),
149
150                         // Total entries
151                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BaseBinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
152
153                         // Separator header<->entries
154                         chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
155                 );
156
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);
160
161                 // Trace message
162                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
163         }
164
165         /**
166          * Initializes this file-based index
167          *
168          * @param       $fileInfoInstance       An instance of a SplFileInfo class
169          * @return      void
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.
171          */
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));
176
177                 // Get iterator instance
178                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', [$fileInstance]);
179
180                 // Set iterator here
181                 $this->setIteratorInstance($iteratorInstance);
182
183                 // Calculate header size
184                 $headerSize = (
185                         strlen(Indexable::INDEX_MAGIC) +
186                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
187                         BaseBinaryFile::LENGTH_COUNT +
188                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES))
189                 );
190
191                 // Set it
192                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: Setting headerSize=%d ...', $headerSize));
193                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
194
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();
198
199                 // Default is not created
200                 $created = false;
201
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');
207
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();
211
212                         // Mark as freshly created
213                         $created = true;
214                 }
215
216                 // Load the file header
217                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calling this->readIndexHeader() ...');
218                 $this->readIndexHeader();
219
220                 // Freshly created?
221                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: created=%d', intval($created)));
222                 if (!$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();
226                 }
227
228                 // Trace message
229                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: EXIT!');
230         }
231
232         /**
233          * Calculates minimum length for one entry/block
234          *
235          * @return      $length         Minimum length for one entry/block
236          */
237         public function calculateMinimumBlockLength () {
238                 // Is it "cached"?
239                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: CALLED!');
240                 if (self::$minimumBlockLength == 0) {
241                         // Calulcate it
242                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-INDEX: Calculating ...');
243                         self::$minimumBlockLength = (
244                                 // Type
245                                 BaseBinaryFile::LENGTH_TYPE + strlen(chr(BaseBinaryFile::SEPARATOR_TYPE_POSITION)) +
246                                 // Position
247                                 BaseBinaryFile::LENGTH_POSITION + strlen(chr(BaseBinaryFile::SEPARATOR_ENTRIES))
248                         );
249                 }
250
251                 // Return it
252                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
253                 return self::$minimumBlockLength;
254         }
255
256         /**
257          * "Getter" for file size
258          *
259          * @return      $fileSize       Size of currently loaded file
260          */
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();
265
266                 // Return it
267                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: fileSize=%d - EXIT!', $fileSize));
268                 return $fileSize;
269         }
270
271         /**
272          * Searches for next suitable gap the given length of data can fit in
273          * including padding bytes.
274          *
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
279          */
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));
283                 if ($length <= 0) {
284                         // Throw IAE
285                         throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
286                 }
287
288                 // Debug message
289                 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: length=%d,this=%s', __METHOD__, __LINE__, $length, print_r($this, true)));
290         }
291
292         /**
293          * Writes at given position by seeking to it.
294          *
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
300          */
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)) {
308                         // Empty dataStream
309                         throw new InvalidArgumentException('Parameter "dataStream" is empty');
310                 }
311
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);
315
316                 // Return status
317                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-INDEX: status[%s]=%d - EXIT!', gettype($status), $status));
318                 return $status;
319         }
320
321         /**
322          * Checks if this index has been fully and properly loaded.
323          *
324          * @return      $isLoaded       Whether this index has been loaded
325          */
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!');
332                         return TRUE;
333                 }
334
335                 // Debug message
336                 /* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, true)));
337         }
338
339 }