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