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