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