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