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