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