]> git.mxchange.org Git - core.git/blob - framework/main/classes/file_directories/binary/class_BaseBinaryFile.php
Continued:
[core.git] / framework / main / classes / file_directories / binary / class_BaseBinaryFile.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Filesystem\File;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\Block;
9 use Org\Mxchange\CoreFramework\Filesystem\Block\CalculatableBlock;
10 use Org\Mxchange\CoreFramework\Filesystem\File\BaseAbstractFile;
11
12 // Import SPL stuff
13 use \SplFileInfo;
14
15 /**
16  * A general binary file class
17  *
18  * @author              Roland Haeder <webmaster@ship-simu.org>
19  * @version             0.0.0
20  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
21  * @license             GNU GPL 3.0 or any newer version
22  * @link                http://www.ship-simu.org
23  *
24  * This program is free software: you can redistribute it and/or modify
25  * it under the terms of the GNU General Public License as published by
26  * the Free Software Foundation, either version 3 of the License, or
27  * (at your option) any later version.
28  *
29  * This program is distributed in the hope that it will be useful,
30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32  * GNU General Public License for more details.
33  *
34  * You should have received a copy of the GNU General Public License
35  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
36  */
37 abstract class BaseBinaryFile extends BaseAbstractFile {
38         /**
39          * Separator for header data
40          */
41         const SEPARATOR_HEADER_DATA = 0x01;
42
43         /**
44          * Separator header->entries
45          */
46         const SEPARATOR_HEADER_ENTRIES = 0x02;
47
48         /**
49          * Separator group->hash
50          */
51         const SEPARATOR_GROUP_HASH = 0x03;
52
53         /**
54          * Separator hash->value
55          */
56         const SEPARATOR_HASH_VALUE = 0x04;
57
58         /**
59          * Separator entry->entry
60          */
61         const SEPARATOR_ENTRIES = 0x05;
62
63         /**
64          * Separator type->position
65          */
66         const SEPARATOR_TYPE_POSITION = 0x06;
67
68         /**
69          * Length of count
70          */
71         const LENGTH_COUNT = 20;
72
73         /**
74          * Length of position
75          */
76         const LENGTH_POSITION = 20;
77
78         /**
79          * Length of group
80          */
81         const LENGTH_GROUP = 10;
82
83         /**
84          * Maximum length of entry type
85          */
86         const LENGTH_TYPE = 20;
87
88         //***** Array elements for 'gaps' array *****
89
90         /**
91          * Start of gap
92          */
93         const GAPS_INDEX_START = 'start';
94
95         /**
96          * End of gap
97          */
98         const GAPS_INDEX_END = 'end';
99
100         /**
101          * Current seek position
102          */
103         private $seekPosition = 0;
104
105         /**
106          * Size of header
107          */
108         private $headerSize = 0;
109
110         /**
111          * File header
112          */
113         private $header = [];
114
115         /**
116          * Seek positions for gaps ("fragmentation")
117          */
118         private $gaps = [];
119
120         /**
121          * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
122          */
123         private $damagedEntries = [];
124
125         /**
126          * Back-buffer
127          */
128         private $backBuffer = '';
129
130         /**
131          * Currently loaded block (will be returned by current())
132          */
133         private $currentBlock = '';
134
135         /**
136          * An instance of a Block class
137          */
138         private $blockInstance = NULL;
139
140         /**
141          * Protected constructor
142          *
143          * @param       $className      Name of the class
144          * @return      void
145          */
146         protected function __construct (string $className) {
147                 // Call parent constructor
148                 parent::__construct($className);
149
150                 // Init counters and gaps array
151                 $this->initCountersGapsArray();
152         }
153
154         /**
155          * Setter for Block instance
156          *
157          * @param       $blockInstance  An instance of an Block class
158          * @return      void
159          */
160         protected final function setBlockInstance (Block $blockInstance) {
161                 $this->blockInstance = $blockInstance;
162         }
163
164         /**
165          * Getter for Block instance
166          *
167          * @return      $blockInstance  An instance of an Block class
168          */
169         public final function getBlockInstance () {
170                 return $this->blockInstance;
171         }
172
173         /**
174          * Checks whether the abstracted file only contains gaps by counting all
175          * gaps' bytes together and compare it to total length.
176          *
177          * @return      $isGapsOnly             Whether the abstracted file only contains gaps
178          */
179         private function isFileOnlyGaps () {
180                 // First/last gap found?
181                 /* Only for debugging
182                 if (isset($this->gaps[0])) {
183                         // Output first and last gap
184                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] this->gaps[0]=%s,this->gaps[%s]=%s', __METHOD__, __LINE__, print_r($this->gaps[0], true), (count($this->gaps) - 1), print_r($this->gaps[count($this->gaps) - 1], true)));
185                 } // END - if
186                 */
187
188                 // Now count every gap
189                 $gapsSize = 0;
190                 foreach ($this->gaps as $gap) {
191                         // Calculate size of found gap: end-start including both
192                         $gapsSize += ($gap[self::GAPS_INDEX_END] - $gap[self::GAPS_INDEX_START]);
193                 } // END - if
194
195                 // Debug output
196                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] gapsSize=%s,this->headerSize=%s', __METHOD__, __LINE__, $gapsSize, $this->getHeaderSize()));
197
198                 // Total gap size + header size must be same as file size
199                 $isGapsOnly = (($this->getHeaderSize() + $gapsSize) == $this->getFileSize());
200
201                 // Return status
202                 return $isGapsOnly;
203         }
204
205         /**
206          * Initializes counter for valid entries, arrays for damaged entries and
207          * an array for gap seek positions. If you call this method on your own,
208          * please re-analyze the file structure. So you are better to call
209          * analyzeFile() instead of this method.
210          *
211          * @return      void
212          */
213         public function initCountersGapsArray () {
214                 // Init counter and seek position
215                 $this->setCounter(0);
216                 $this->setSeekPosition(0);
217
218                 // Init arrays
219                 $this->gaps = [];
220                 $this->damagedEntries = [];
221         }
222
223         /**
224          * Getter for header size
225          *
226          * @return      $totalEntries   Size of file header
227          */
228         public final function getHeaderSize () {
229                 // Get it
230                 return $this->headerSize;
231         }
232
233         /**
234          * Setter for header size
235          *
236          * @param       $headerSize             Size of file header
237          * @return      void
238          */
239         public final function setHeaderSize ($headerSize) {
240                 // Set it
241                 $this->headerSize = $headerSize;
242         }
243
244         /**
245          * Getter for header array
246          *
247          * @return      $totalEntries   Size of file header
248          */
249         public final function getHeader () {
250                 // Get it
251                 return $this->header;
252         }
253
254         /**
255          * Setter for header
256          *
257          * @param       $header         Array for a file header
258          * @return      void
259          */
260         public final function setHeader (array $header) {
261                 // Set it
262                 $this->header = $header;
263         }
264
265         /**
266          * Getter for seek position
267          *
268          * @return      $seekPosition   Current seek position (stored here in object)
269          */
270         public final function getSeekPosition () {
271                 // Get it
272                 return $this->seekPosition;
273         }
274
275         /**
276          * Setter for seek position
277          *
278          * @param       $seekPosition   Current seek position (stored here in object)
279          * @return      void
280          */
281         protected final function setSeekPosition ($seekPosition) {
282                 // And set it
283                 $this->seekPosition = $seekPosition;
284         }
285
286         /**
287          * Updates seekPosition attribute from file to avoid to much access on file.
288          *
289          * @return      void
290          */
291         public function updateSeekPosition () {
292                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
293
294                 // Get key (= seek position)
295                 $seekPosition = $this->key();
296                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Setting seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
297
298                 // And set it here
299                 $this->setSeekPosition($seekPosition);
300
301                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
302         }
303
304         /**
305          * Seeks to beginning of file, updates seek position in this object and
306          * flushes the header.
307          *
308          * @return      void
309          */
310         protected function rewindUpdateSeekPosition () {
311                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
312
313                 // flushFileHeader must be callable
314                 assert(is_callable(array($this, 'flushFileHeader')));
315
316                 // Seek to beginning of file
317                 $this->rewind();
318
319                 // And update seek position ...
320                 $this->updateSeekPosition();
321
322                 // ... to write it back into the file
323                 $this->flushFileHeader();
324
325                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
326         }
327
328         /**
329          * Seeks to old position
330          *
331          * @return      void
332          */
333         protected function seekToOldPosition () {
334                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
335
336                 // Seek to currently ("old") saved position
337                 $this->seek($this->getSeekPosition());
338
339                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
340         }
341
342         /**
343          * Checks whether the block separator has been found
344          *
345          * @param       $str            String to look in
346          * @return      $isFound        Whether the block separator has been found
347          */
348         public static function isBlockSeparatorFound ($str) {
349                 // Determine it
350                 $isFound = (strpos($str, chr(self::SEPARATOR_ENTRIES)) !== false);
351
352                 // Return result
353                 return $isFound;
354         }
355
356         /**
357          * Initializes the back-buffer by setting it to an empty string.
358          *
359          * @return      void
360          */
361         private function initBackBuffer () {
362                 // Simply call the setter
363                 $this->setBackBuffer('');
364         }
365
366         /**
367          * Setter for backBuffer field
368          *
369          * @param       $backBuffer             Characters to "store" in back-buffer
370          * @return      void
371          */
372         private function setBackBuffer ($backBuffer) {
373                 // Cast to string (so no arrays or objects)
374                 $backBuffer = (string) $backBuffer;
375
376                 // ... and set it
377                 $this->backBuffer = $backBuffer;
378         }
379
380         /**
381          * Getter for backBuffer field
382          *
383          * @return      $backBuffer             Characters "stored" in back-buffer
384          */
385         private function getBackBuffer () {
386                 return $this->backBuffer;
387         }
388
389         /**
390          * Setter for currentBlock field
391          *
392          * @param       $currentBlock           Characters to set a currently loaded block
393          * @return      void
394          */
395         private function setCurrentBlock ($currentBlock) {
396                 // Cast to string (so no arrays or objects)
397                 $currentBlock = (string) $currentBlock;
398
399                 // ... and set it
400                 $this->currentBlock = $currentBlock;
401         }
402
403         /**
404          * Gets currently read data
405          *
406          * @return      $current        Currently read data
407          */
408         public function getCurrentBlock () {
409                 // Return it
410                 return $this->currentBlock;
411         }
412
413         /**
414          * Initializes this file class
415          *
416          * @param       $infoInstance   An instance of a SplFileInfo class
417          * @return      void
418          */
419         protected function initFile (SplFileInfo $infoInstance) {
420                 // Get a file i/o pointer instance
421                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($infoInstance));
422
423                 // ... and set it here
424                 $this->setPointerInstance($pointerInstance);
425         }
426
427         /**
428          * Writes data at given position
429          *
430          * @param       $seekPosition   Seek position
431          * @param       $data                   Data to be written
432          * @param       $flushHeader    Whether to flush the header (default: flush)
433          * @return      void
434          */
435         public function writeData ($seekPosition, $data, $flushHeader = true) {
436                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s,data()=%d - CALLED!', __METHOD__, __LINE__, $seekPosition, strlen($data)));
437
438                 // Write data at given position
439                 $this->getPointerInstance()->writeAtPosition($seekPosition, $data);
440
441                 // Increment counter
442                 $this->incrementCounter();
443
444                 // Update seek position
445                 $this->updateSeekPosition();
446
447                 // Flush the header?
448                 if ($flushHeader === true) {
449                         // Flush header
450                         $this->flushFileHeader();
451
452                         // Seek to old position
453                         $this->seekToOldPosition();
454                 } // END - if
455
456                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
457         }
458
459         /**
460          * Marks the currently loaded block as empty (with length of the block)
461          *
462          * @param       $length         Length of the block
463          * @return      void
464          */
465         protected function markCurrentBlockAsEmpty ($length) {
466                 // Get current seek position
467                 $currentPosition = $this->key();
468
469                 // Now add it as gap entry
470                 array_push($this->gaps, array(
471                         self::GAPS_INDEX_START  => ($currentPosition - $length),
472                         self::GAPS_INDEX_END    => $currentPosition,
473                 ));
474         }
475
476         /**
477          * Checks whether the file header is initialized
478          *
479          * @return      $isInitialized  Whether the file header is initialized
480          */
481         public function isFileHeaderInitialized () {
482                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
483
484                 // Default is not initialized
485                 $isInitialized = false;
486
487                 // Is the file initialized?
488                 if ($this->isFileInitialized()) {
489                         // Some bytes has been written, so rewind to start of it.
490                         $rewindStatus = $this->rewind();
491                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
492
493                         // Is the rewind() call successfull?
494                         if ($rewindStatus != 1) {
495                                 // Something bad happened
496                                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Could not rewind().', __METHOD__, __LINE__));
497                         } // END - if
498
499                         // Read file header
500                         $this->readFileHeader();
501
502                         // The above method does already check the header
503                         $isInitialized = true;
504                 } // END - if
505
506                 // Return result
507                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
508                 return $isInitialized;
509         }
510
511         /**
512          * Checks whether the assigned file has been initialized
513          *
514          * @return      $isInitialized          Whether the file's size is zero
515          */
516         public function isFileInitialized () {
517                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
518
519                 // Get it from iterator which holds the pointer instance. If false is returned
520                 $fileSize = $this->size();
521                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] fileSize=%s', __METHOD__, __LINE__, $fileSize));
522
523                 /*
524                  * The returned file size should not be false or NULL as this means
525                  * that the pointer class does not work correctly.
526                  */
527                 assert(is_int($fileSize));
528
529                 // Is more than 0 returned?
530                 $isInitialized = ($fileSize > 0);
531
532                 // Return result
533                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
534                 return $isInitialized;
535         }
536
537         /**
538          * Creates the assigned file
539          *
540          * @return      void
541          */
542         public function createFileHeader () {
543                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
544
545                 // The file's header should not be initialized here
546                 assert(!$this->isFileHeaderInitialized());
547
548                 // Simple flush file header which will create it.
549                 $this->flushFileHeader();
550
551                 // Rewind seek position (to beginning of file) and update/flush file header
552                 $this->rewindUpdateSeekPosition();
553
554                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
555         }
556
557         /**
558          * Pre-allocates file (if enabled) with some space for later faster write access.
559          *
560          * @param       $type   Type of the file
561          * @return      void
562          */
563         public function preAllocateFile ($type) {
564                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
565
566                 // Is it enabled?
567                 if (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
568                         // Not enabled
569                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating file.', __METHOD__, __LINE__));
570
571                         // Don't continue here.
572                         return;
573                 } // END - if
574
575                 // Message to user
576                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Pre-allocating file ...', __METHOD__, __LINE__));
577
578                 // Calculate minimum length for one entry
579                 $minLengthEntry = $this->getBlockInstance()->calculateMinimumBlockLength();
580                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] minLengthEntry=%s', __METHOD__, __LINE__, $minLengthEntry));
581
582                 // Calulcate seek position
583                 $seekPosition = $minLengthEntry * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count');
584                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
585
586                 // Now simply write a NUL there. This will pre-allocate the file.
587                 $this->writeData($seekPosition, chr(0));
588
589                 // Rewind seek position (to beginning of file) and update/flush file header
590                 $this->rewindUpdateSeekPosition();
591
592                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
593         }
594
595         /**
596          * Determines seek position
597          *
598          * @return      $seekPosition   Current seek position
599          */
600         public function determineSeekPosition () {
601                 // Call pointer instance
602                 return $this->getPointerInstance()->determineSeekPosition();
603         }
604
605         /**
606          * Seek to given offset (default) or other possibilities as fseek() gives.
607          *
608          * @param       $offset         Offset to seek to (or used as "base" for other seeks)
609          * @param       $whence         Added to offset (default: only use offset to seek to)
610          * @return      $status         Status of file seek: 0 = success, -1 = failed
611          */
612         public function seek ($offset, $whence = SEEK_SET) {
613                 // Call pointer instance
614                 return $this->getPointerInstance()->seek($offset, $whence);
615         }
616
617         /**
618          * Reads given amount of bytes from file.
619          *
620          * @param       $bytes  Amount of bytes to read
621          * @return      $data   Data read from file
622          */
623         public function read ($bytes = NULL) {
624                 // $bytes shall be integer
625                 assert(is_int($bytes));
626
627                 // Call pointer instance
628                 return $this->getPointerInstance()->read($bytes);
629         }
630
631         /**
632          * Rewinds to the beginning of the file
633          *
634          * @return      $status         Status of this operation
635          */
636         public function rewind () {
637                 // Call pointer instance
638                 return $this->getPointerInstance()->rewind();
639         }
640
641         /**
642          * Analyzes entries in index file. This will count all found (and valid)
643          * entries, mark invalid as damaged and count gaps ("fragmentation"). If
644          * only gaps are found, the file is considered as "virgin" (no entries).
645          *
646          * @return      void
647          */
648         public function analyzeFile () {
649                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
650
651                 // Make sure the file is initialized
652                 assert($this->isFileInitialized());
653
654                 // Init counters and gaps array
655                 $this->initCountersGapsArray();
656
657                 // Output message (as this may take some time)
658                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Analyzing file structure ... (this may take some time)', __METHOD__, __LINE__));
659
660                 // First rewind to the begining
661                 $this->rewind();
662
663                 // Then try to load all entries
664                 while ($this->valid()) {
665                         // Go to next entry
666                         $this->next();
667
668                         // Get current entry
669                         $current = $this->getCurrentBlock();
670
671                         /*
672                          * If the block is empty, maybe the whole file is? This could mean
673                          * that the file has been pre-allocated.
674                          */
675                         if (empty($current)) {
676                                 // Then skip this part
677                                 continue;
678                         } // END - if
679
680                         // Debug message
681                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] current()=%d', __METHOD__, __LINE__, strlen($current)));
682                 } // END - while
683
684                 // If the last read block is empty, check gaps
685                 if (empty($current)) {
686                         // Output message
687                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] Found a total of %s gaps.', __METHOD__, __LINE__, count($this->gaps)));
688
689                         // Check gaps, if the whole file is empty.
690                         if ($this->isFileOnlyGaps()) {
691                                 // Only gaps, so don't continue here.
692                                 return;
693                         } // END - if
694
695                         /*
696                          * The above call has calculated a total size of all gaps. If the
697                          * percentage of gaps passes a "soft" limit and last
698                          * defragmentation is to far in the past, or if a "hard" limit has
699                          * reached, run defragmentation.
700                          */
701                         if ($this->isDefragmentationNeeded()) {
702                                 // Run "defragmentation"
703                                 $this->doRunDefragmentation();
704                         } // END - if
705                 } // END - if
706                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
707         }
708
709         /**
710          * Advances to next "block" of bytes
711          *
712          * @return      void
713          */
714         public function next () {
715                 // Is there nothing to read?
716                 if (!$this->valid()) {
717                         // Nothing to read
718                         return;
719                 } // END - if
720
721                 // Debug message
722                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d] key()=%d', __FUNCTION__, __LINE__, $this->key()));
723
724                 // Make sure the block instance is set
725                 assert($this->getBlockInstance() instanceof CalculatableBlock);
726
727                 // First calculate minimum block length
728                 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
729                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d] length=%s', __FUNCTION__, __LINE__, $length));
730
731                 // Short be more than zero!
732                 assert($length > 0);
733
734                 // Read possibly back-buffered bytes from previous call of next().
735                 $data = $this->getBackBuffer();
736
737                 /*
738                  * Read until a entry/block separator has been found. The next read
739                  * "block" may not fit, so this loop will continue until the EOB or EOF
740                  * has been reached whatever comes first.
741                  */
742                 while ((!$this->isEndOfFileReached()) && (!self::isBlockSeparatorFound($data))) {
743                         // Then read the next possible block
744                         $block = $this->read($length);
745
746                         // Debug message
747                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d] block()=%d,length=%s', __FUNCTION__, __LINE__, strlen($block), $length));
748
749                         // Is it all empty?
750                         if (strlen(trim($block)) == 0) {
751                                 // Mark this block as empty
752                                 $this->markCurrentBlockAsEmpty(strlen($block));
753
754                                 // Skip to next block
755                                 continue;
756                         } // END - if
757
758                         // At this block then
759                         $data .= $block;
760
761                         // A debug message
762                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('[%s:%d] data()=%d', __FUNCTION__, __LINE__, strlen($data)));
763                 } // END - while
764
765                 // EOF reached?
766                 if ($this->isEndOfFileReached()) {
767                         // Set whole data as current read block
768                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('Calling setCurrentBlock(' . strlen($data) . ') ...');
769                         $this->setCurrentBlock($data);
770
771                         // Then abort here silently
772                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('EOF reached.');
773                         return;
774                 } // END - if
775
776                 /*
777                  * Init back-buffer which is the data that has been found beyond the
778                  * separator.
779                  */
780                 $this->initBackBuffer();
781
782                 // Separate data
783                 $dataArray = explode(chr(self::SEPARATOR_ENTRIES), $data);
784
785                 // This array must contain two elements
786                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('dataArray=' . print_r($dataArray, true));
787                 assert(count($dataArray) == 2);
788
789                 // Left part is the actual block, right one the back-buffer data
790                 $this->setCurrentBlock($dataArray[0]);
791                 $this->setBackBuffer($dataArray[1]);
792         }
793
794         /**
795          * Checks wether the current entry is valid (not at the end of the file).
796          * This method will return true if an emptied (nulled) entry has been found.
797          *
798          * @return      $isValid        Whether the next entry is valid
799          */
800         public function valid () {
801                 // Make sure the block instance is set
802                 assert($this->getBlockInstance() instanceof Block);
803
804                 // First calculate minimum block length
805                 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
806
807                 // Short be more than zero!
808                 assert($length > 0);
809
810                 // Get current seek position
811                 $seekPosition = $this->key();
812
813                 // Then try to read it
814                 $data = $this->read($length);
815
816                 // If some bytes could be read, all is fine
817                 $isValid = ((is_string($data)) && (strlen($data) > 0));
818
819                 // Get header size
820                 $headerSize = $this->getHeaderSize();
821
822                 // Is the seek position at or beyond the header?
823                 if ($seekPosition >= $headerSize) {
824                         // Seek back to old position
825                         $this->seek($seekPosition);
826                 } else {
827                         // Seek directly behind the header
828                         $this->seek($headerSize);
829                 }
830
831                 // Return result
832                 return $isValid;
833         }
834
835         /**
836          * Gets current seek position ("key").
837          *
838          * @return      $key    Current key in iteration
839          */
840         public function key () {
841                 // Call pointer instance
842                 return $this->getPointerInstance()->determineSeekPosition();
843         }
844
845         /**
846          * Reads the file header
847          *
848          * @return      void
849          */
850         public function readFileHeader () {
851                 // Make sure the block instance is set
852                 assert($this->getBlockInstance() instanceof Block);
853
854                 // Call block instance
855                 $this->getBlockInstance()->readFileHeader();
856         }
857
858         /**
859          * Flushes the file header
860          *
861          * @return      void
862          */
863         public function flushFileHeader () {
864                 // Make sure the block instance is set
865                 assert($this->getBlockInstance() instanceof Block);
866
867                 // Call block instance
868                 $this->getBlockInstance()->flushFileHeader();
869         }
870
871         /**
872          * Searches for next suitable gap the given length of data can fit in
873          * including padding bytes.
874          *
875          * @param       $length                 Length of raw data
876          * @return      $seekPosition   Found next gap's seek position
877          */
878         public function searchNextGap ($length) {
879                 // If the file is only gaps, no need to seek
880                 if ($this->isFileOnlyGaps()) {
881                         // The first empty block is the first one right after the header
882                         return ($this->getHeaderSize() + 1);
883                 } // END - if
884
885                 // @TODO Unfinished
886                 $this->partialStub('length=' . $length);
887         }
888
889 }