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