Rewrote next() to avoid loading whole empty file.
[core.git] / inc / classes / main / file_directories / class_BaseFile.php
index e918c80b07fc391e71b52dfbecfde3a9cbd914b5..1d3dac19e1e0016769992519d5088cc532867e72 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;
+
+       /**
+        * Length of output from hash()
+        */
+       private static $hashLength = NULL;
+
+       /**
+        * 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 +144,188 @@ y   * @return      void
                parent::__destruct();
        }
 
+       /**
+        * 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
+        */
+       protected function initCountersGapsArray () {
+               // Init counter and seek position
+               $this->setCounter(0);
+               $this->setSeekPosition(0);
+
+               // Init arrays
+               $this->gaps = array();
+               $this->damagedEntries = array();
+       }
+
+       /**
+        * Getter for total entries
+        *
+        * @return      $totalEntries   Total entries in this file
+        */
+       protected 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
+        */
+       protected final function setHeaderSize ($headerSize) {
+               // Set it
+               $this->headerSize = $headerSize;
+       }
+
+       /**
+        * Getter for header array
+        *
+        * @return      $totalEntries   Size of file header
+        */
+       protected final function getHeade () {
+               // Get it
+               return $this->header;
+       }
+
+       /**
+        * Setter for header
+        *
+        * @param       $header         Array for a file header
+        * @return      void
+        */
+       protected 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
+        */
+       protected function updateSeekPosition () {
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
+
+               // Get key (= seek position)
+               $seekPosition = $this->getIteratorInstance()->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 rewineUpdateSeekPosition () {
+               //* 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->getIteratorInstance()->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->getIteratorInstance()->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
         *
@@ -208,7 +470,7 @@ y    * @return      void
                // Is the file initialized?
                if ($this->isFileInitialized()) {
                        // Some bytes has been written, so rewind to start of it.
-                       $rewindStatus = $this->getIteratorInstance()->rewind();
+                       $rewindStatus = $this->rewind();
                        //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
 
                        // Is the rewind() call successfull?
@@ -218,7 +480,7 @@ y    * @return      void
                        } // END - if
 
                        // Read file header
-                       $this->readFileHeader()
+                       $this->readFileHeader();
 
                        // The above method does already check the header
                        $isInitialized = TRUE;
@@ -460,7 +722,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?
@@ -478,17 +739,37 @@ y  * @return      void
                // 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));
+                       // Then read the next possible block
+                       $block = $this->read($length);
+
+                       // Is it all empty?
+                       if (strlen(trim($block)) == 0) {
+                               // Mark this block as empty
+                               $this->markCurrentBlockAsEmpty($length);
+
+                               // 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 - if
 
                // 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 +785,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));
@@ -523,7 +804,7 @@ y    * @return      void
         */
        public function valid () {
                // Make sure the block instance is set
-               assert($this->getBlockInstance() instanceof CalculatableBlock);
+               assert($this->getBlockInstance() instanceof Block);
 
                // First calculate minimum block length
                $length = $this->getBlockInstance()->calculateMinimumBlockLength();
@@ -572,8 +853,11 @@ y   * @return      void
         * @return      void
         */
        public function readFileHeader () {
+               // Make sure the block instance is set
+               assert($this->getBlockInstance() instanceof Block);
+
                // Call block instance
-               $this->getBlockInstance()->readFileHeader()
+               $this->getBlockInstance()->readFileHeader();
        }
 
        /**
@@ -582,8 +866,11 @@ y   * @return      void
         * @return      void
         */
        public function flushFileHeader () {
+               // Make sure the block instance is set
+               assert($this->getBlockInstance() instanceof Block);
+
                // Call block instance
-               $this->getBlockInstance()->flushFileHeader()
+               $this->getBlockInstance()->flushFileHeader();
        }
 }