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