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