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