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