]> git.mxchange.org Git - core.git/blobdiff - inc/classes/main/file_directories/class_BaseFile.php
Added isFileOnlyGaps() and getFileSize(), both are "basicly finished".
[core.git] / inc / classes / main / file_directories / class_BaseFile.php
index 5fade9baeeaaded5a0f87f0af175e2fed130e20d..d7a73372baea2ccc1e949bf7687516f63cc9bfa4 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 class BaseFile extends BaseFrameworkSystem {
+       /**
+        * Separator for header data
+        */
+       const SEPARATOR_HEADER_DATA = 0x01;
+
+       /**
+        * Separator header->entries
+        */
+       const SEPARATOR_HEADER_ENTRIES = 0x02;
+
+       /**
+        * Separator hash->name
+        */
+       const SEPARATOR_HASH_NAME = 0x03;
+
+       /**
+        * Separator entry->entry
+        */
+       const SEPARATOR_ENTRIES = 0x04;
+
+       /**
+        * Separator type->position
+        */
+       const SEPARATOR_TYPE_POSITION = 0x05;
+
+       /**
+        * Length of count
+        */
+       const LENGTH_COUNT = 20;
+
+       /**
+        * Length of position
+        */
+       const LENGTH_POSITION = 20;
+
+       /**
+        * Length of name
+        */
+       const LENGTH_NAME = 10;
+
+       /**
+        * Maximum length of entry type
+        */
+       const LENGTH_TYPE = 20;
+
+       //***** Array elements for 'gaps' array *****
+
+       /**
+        * Start of gap
+        */
+       const GAPS_INDEX_START = 'start';
+
+       /**
+        * End of gap
+        */
+       const GAPS_INDEX_END = 'end';
+
+       /**
+        * Counter for total entries
+        */
+       private $totalEntries = 0;
+
+       /**
+        * Current seek position
+        */
+       private $seekPosition = 0;
+
+       /**
+        * Size of header
+        */
+       private $headerSize = 0;
+
+       /**
+        * File header
+        */
+       private $header = array();
+
+       /**
+        * Seek positions for gaps ("fragmentation")
+        */
+       private $gaps = array();
+
+       /**
+        * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
+        */
+       private $damagedEntries = array();
+
        /**
         * The current file we are working in
         */
@@ -64,6 +151,230 @@ y   * @return      void
                parent::__destruct();
        }
 
+       /**
+        * Checks whether the abstracted file only contains gaps by counting all
+        * gaps' bytes together and compare it to total length.
+        *
+        * @return      $isGapsOnly             Whether the abstracted file only contains gaps
+        */
+       private function isFileOnlyGaps () {
+               // First/last gap found?
+               /* Only for debugging
+               if (isset($this->gaps[0])) {
+                       // Output first and last gap
+                       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)));
+               } // END - if
+               */
+
+               // Now count every gap
+               $gapsSize = 0;
+               foreach ($this->gaps as $gap) {
+                       // Calculate size of found gap: end-start including both
+                       $gapsSize += ($gap[self::GAPS_INDEX_END] - $gap[self::GAPS_INDEX_START]);
+               } // END - if
+
+               // Debug output
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] gapsSize=%s,this->headerSize=%s', __METHOD__, __LINE__, $gapsSize, $this->getHeaderSize()));
+
+               // Total gap size + header size must be same as file size
+               $isGapsOnly = (($this->getHeaderSize() + $gapsSize) == $this->getFileSize());
+
+               // Return status
+               return $isGapsOnly;
+       }
+
+       /**
+        * Initializes counter for valid entries, arrays for damaged entries and
+        * an array for gap seek positions. If you call this method on your own,
+        * please re-analyze the file structure. So you are better to call
+        * analyzeFile() instead of this method.
+        *
+        * @return      void
+        */
+       public function initCountersGapsArray () {
+               // Init counter and seek position
+               $this->setCounter(0);
+               $this->setSeekPosition(0);
+
+               // Init arrays
+               $this->gaps = array();
+               $this->damagedEntries = array();
+       }
+
+       /**
+        * "Getter" for abstracted file size
+        *
+        * @return      $fileSize       Size of abstracted file
+        */
+       public function getFileSize () {
+               // Call block instanze
+               return $this->getBlockInstance()->getFileSize();
+       }
+
+       /**
+        * Getter for total entries
+        *
+        * @return      $totalEntries   Total entries in this file
+        */
+       public final function getCounter () {
+               // Get it
+               return $this->totalEntries;
+       }
+
+       /**
+        * Setter for total entries
+        *
+        * @param       $totalEntries   Total entries in this file
+        * @return      void
+        */
+       protected final function setCounter ($counter) {
+               // Set it
+               $this->totalEntries = $counter;
+       }
+
+       /**
+        * Increment counter
+        *
+        * @return      void
+        */
+       protected final function incrementCounter () {
+               // Get it
+               $this->totalEntries++;
+       }
+
+       /**
+        * Getter for header size
+        *
+        * @return      $totalEntries   Size of file header
+        */
+       public final function getHeaderSize () {
+               // Get it
+               return $this->headerSize;
+       }
+
+       /**
+        * Setter for header size
+        *
+        * @param       $headerSize             Size of file header
+        * @return      void
+        */
+       public final function setHeaderSize ($headerSize) {
+               // Set it
+               $this->headerSize = $headerSize;
+       }
+
+       /**
+        * Getter for header array
+        *
+        * @return      $totalEntries   Size of file header
+        */
+       public final function getHeader () {
+               // Get it
+               return $this->header;
+       }
+
+       /**
+        * Setter for header
+        *
+        * @param       $header         Array for a file header
+        * @return      void
+        */
+       public final function setHeader (array $header) {
+               // Set it
+               $this->header = $header;
+       }
+
+       /**
+        * Getter for seek position
+        *
+        * @return      $seekPosition   Current seek position (stored here in object)
+        */
+       protected final function getSeekPosition () {
+               // Get it
+               return $this->seekPosition;
+       }
+
+       /**
+        * Setter for seek position
+        *
+        * @param       $seekPosition   Current seek position (stored here in object)
+        * @return      void
+        */
+       protected final function setSeekPosition ($seekPosition) {
+               // And set it
+               $this->seekPosition = $seekPosition;
+       }
+
+       /**
+        * Updates seekPosition attribute from file to avoid to much access on file.
+        *
+        * @return      void
+        */
+       public function updateSeekPosition () {
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
+
+               // Get key (= seek position)
+               $seekPosition = $this->key();
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Setting seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
+
+               // And set it here
+               $this->setSeekPosition($seekPosition);
+
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
+       }
+
+       /**
+        * Seeks to beginning of file, updates seek position in this object and
+        * flushes the header.
+        *
+        * @return      void
+        */
+       protected function rewindUpdateSeekPosition () {
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
+
+               // flushFileHeader must be callable
+               assert(is_callable(array($this, 'flushFileHeader')));
+
+               // Seek to beginning of file
+               $this->rewind();
+
+               // And update seek position ...
+               $this->updateSeekPosition();
+
+               // ... to write it back into the file
+               $this->flushFileHeader();
+
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
+       }
+
+       /**
+        * Seeks to old position
+        *
+        * @return      void
+        */
+       protected function seekToOldPosition () {
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
+
+               // Seek to currently ("old") saved position
+               $this->seek($this->getSeekPosition());
+
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
+       }
+
+       /**
+        * Checks whether the block separator has been found
+        *
+        * @param       $str            String to look in
+        * @return      $isFound        Whether the block separator has been found
+        */
+       public static function isBlockSeparatorFound ($str) {
+               // Determine it
+               $isFound = (strpos($str, chr(self::SEPARATOR_ENTRIES)) !== FALSE);
+
+               // Return result
+               return $isFound;
+       }
+
        /**
         * Getter for the file pointer
         *
@@ -194,6 +505,23 @@ y   * @return      void
                //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
        }
 
+       /**
+        * Marks the currently loaded block as empty (with length of the block)
+        *
+        * @param       $length         Length of the block
+        * @return      void
+        */
+       protected function markCurrentBlockAsEmpty ($length) {
+               // Get current seek position
+               $currentPosition = $this->key();
+
+               // Now add it as gap entry
+               array_push($this->gaps, array(
+                       self::GAPS_INDEX_START  => ($currentPosition - $length),
+                       self::GAPS_INDEX_END    => $currentPosition,
+               ));
+       }
+
        /**
         * Checks whether the file header is initialized
         *
@@ -270,7 +598,7 @@ y    * @return      void
                $this->flushFileHeader();
 
                // Rewind seek position (to beginning of file) and update/flush file header
-               $this->rewineUpdateSeekPosition();
+               $this->rewindUpdateSeekPosition();
 
                //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
        }
@@ -308,7 +636,7 @@ y    * @return      void
                $this->writeData($seekPosition, chr(0));
 
                // Rewind seek position (to beginning of file) and update/flush file header
-               $this->rewineUpdateSeekPosition();
+               $this->rewindUpdateSeekPosition();
 
                //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
        }
@@ -449,10 +777,41 @@ y  * @return      void
                        // Get current entry
                        $current = $this->getCurrentBlock();
 
+                       /*
+                        * If the block is empty, maybe the whole file is? This could mean
+                        * that the file has been pre-allocated.
+                        */
+                       if (empty($current)) {
+                               // Then skip this part
+                               continue;
+                       } // END - if
+
                        // Debug message
                        /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] current()=%s', __METHOD__, __LINE__, strlen($current)));
                } // END - while
 
+               // If the last read block is empty, check gaps
+               if (empty($current)) {
+                       // Output message
+                       self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Found a total of %s gaps.', __METHOD__, __LINE__, count($this->gaps)));
+
+                       // Check gaps, if the whole file is empty.
+                       if ($this->isFileOnlyGaps()) {
+                               // Only gaps, so don't continue here.
+                               return;
+                       } // END - if
+
+                       /*
+                        * The above call has calculated a total size of all gaps. If the
+                        * percentage of gaps passes a "soft" limit and last
+                        * defragmentation is to far in the past, or if a "hard" limit has
+                        * reached, run defragmentation.
+                        */
+                       if ($this->isDefragmentationNeeded()) {
+                               // Run "defragmentation"
+                               $this->doRunDefragmentation();
+                       } // END - if
+               } // END - if
                //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
        }
 
@@ -460,7 +819,6 @@ y    * @return      void
         * Advances to next "block" of bytes
         *
         * @return      void
-        * @todo        This method will load large but empty files in a whole
         */
        public function next () {
                // Is there nothing to read?
@@ -469,26 +827,53 @@ y  * @return      void
                        return;
                } // END - if
 
+               // Debug message
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d] key()=%s', __FUNCTION__, __LINE__, $this->key()));
+
                // Make sure the block instance is set
-               assert($this->getBlockInstance() instanceof Block);
+               assert($this->getBlockInstance() instanceof CalculatableBlock);
 
                // First calculate minimum block length
                $length = $this->getBlockInstance()->calculateMinimumBlockLength();
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d] length=%s', __FUNCTION__, __LINE__, $length));
 
                // Short be more than zero!
                assert($length > 0);
 
-               // Wait until a entry/block separator has been found
+               // Read possibly back-buffered bytes from previous call of next().
                $data = $this->getBackBuffer();
+
+               /*
+                * Read until a entry/block separator has been found. The next read
+                * "block" may not fit, so this loop will continue until the EOB or EOF
+                * has been reached whatever comes first.
+                */
                while ((!$this->isEndOfFileReached()) && (!self::isBlockSeparatorFound($data))) {
-                       // Then read the block
-                       $data .= $this->read($length);
-                       //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('data()=' . strlen($data));
-               } // END - if
+                       // Then read the next possible block
+                       $block = $this->read($length);
+
+                       // Debug message
+                       //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d] block()=%s,length=%s', __FUNCTION__, __LINE__, strlen($block), $length));
+
+                       // Is it all empty?
+                       if (strlen(trim($block)) == 0) {
+                               // Mark this block as empty
+                               $this->markCurrentBlockAsEmpty(strlen($block));
+
+                               // Skip to next block
+                               continue;
+                       } // END - if
+
+                       // At this block then
+                       $data .= $block;
+
+                       // A debug message
+                       //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d] data()=%s', __FUNCTION__, __LINE__, strlen($data)));
+               } // END - while
 
                // EOF reached?
                if ($this->isEndOfFileReached()) {
-                       // Set whole data as current block
+                       // Set whole data as current read block
                        //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('Calling setCurrentBlock(' . strlen($data) . ') ...');
                        $this->setCurrentBlock($data);
 
@@ -504,7 +889,7 @@ y    * @return      void
                $this->initBackBuffer();
 
                // Separate data
-               $dataArray = explode(self::getBlockSeparator(), $data);
+               $dataArray = explode(chr(self::SEPARATOR_ENTRIES), $data);
 
                // This array must contain two elements
                //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('dataArray=' . print_r($dataArray, TRUE));
@@ -541,7 +926,7 @@ y    * @return      void
                $isValid = ((is_string($data)) && (strlen($data) > 0));
 
                // Get header size
-               $headerSize = $this->getBlockInstance()->getHeaderSize();
+               $headerSize = $this->getHeaderSize();
 
                // Is the seek position at or beyond the header?
                if ($seekPosition >= $headerSize) {