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