Added better description, as the mind map in 'contrib/mindmaps/' suggest, a new
[core.git] / inc / classes / main / database / databases / class_LocalFileDatabase.php
index 2d4929c80273157a75fa9b143d66484711c2e270..efe6951b3f63fea0e9c0ba283ed77defd7416da3 100644 (file)
@@ -2,11 +2,15 @@
 /**
  * Database backend class for storing objects in locally created files.
  *
- * This class serializes objects and saves them to local files.
+ * This class serializes arrays stored in the dataset instance and saves them
+ * to local files. Every file (except 'info') represents a single line. Every
+ * directory within the 'db' (base) directory represents a table.
+ *
+ * A configurable 'file_io_class' is being used as "storage backend".
  *
  * @author             Roland Haeder <webmaster@ship-simu.org>
  * @version            0.0.0
- * @copyright  Copyright (c) 2007, 2008 Roland Haeder, 2009 Core Developer Team
+ * @copyright  Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Core Developer Team
  * @license            GNU GPL 3.0 or any newer version
  * @link               http://www.ship-simu.org
  *
  * You should have received a copy of the GNU General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontendInterface {
-       // Constants for MySQL backward-compatiblity (PLEASE FIX THEM!)
-       const DB_CODE_TABLE_MISSING     = 0x100;
-       const DB_CODE_TABLE_UNWRITEABLE = 0x101;
-       const DB_CODE_DATA_FILE_CORRUPT = 0x102;
-
-       // Status results
-       const RESULT_OKAY = 'ok';
-
-       /**
-        * Save path for "file database"
-        */
-       private $savePath = '';
-
+class LocalFileDatabase extends BaseDatabaseBackend implements DatabaseBackendInterface {
        /**
         * The file's extension
         */
@@ -53,20 +44,10 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
        private $lastContents = array();
 
        /**
-        * Wether the "connection is already up
+        * Whether the "connection is already up
         */
        private $alreadyConnected = false;
 
-       /**
-        * Last error message
-        */
-       private $lastError = '';
-
-       /**
-        * Last exception
-        */
-       private $lastException = null;
-
        /**
         * Table information array
         */
@@ -83,78 +64,38 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         *
         * @return      void
         */
-       protected function __construct() {
+       protected function __construct () {
                // Call parent constructor
                parent::__construct(__CLASS__);
-
-               // Clean up a little
-               $this->removeNumberFormaters();
-               $this->removeSystemArray();
        }
 
        /**
-        * Create an object of LocalFileDatabase and set the save path for local files.
-        * This method also validates the given file path.
+        * Create an object of LocalFileDatabase and set the save path from
+        * configuration for local files.
         *
-        * @param               $savePath               The local file path string
-        * @param               $ioInstance             The input/output handler. This
-        *                                                              should be FileIoHandler
-        * @return      $dbInstance                     An instance of LocalFileDatabase
+        * @return      $databaseInstance       An instance of LocalFileDatabase
         */
-       public final static function createLocalFileDatabase ($savePath, FileIoHandler $ioInstance) {
+       public static final function createLocalFileDatabase () {
                // Get an instance
-               $dbInstance = new LocalFileDatabase();
+               $databaseInstance = new LocalFileDatabase();
 
-               // Set save path and IO instance
-               $dbInstance->setSavePath($savePath);
-               $dbInstance->setFileIoInstance($ioInstance);
+               // Get a new compressor channel instance
+               $compressorInstance = ObjectFactory::createObjectByConfiguredName('compressor_channel_class');
 
-               // "Connect" to the database
-               $dbInstance->connectToDatabase();
-
-               // Return database instance
-               return $dbInstance;
-       }
+               // Set the compressor channel
+               $databaseInstance->setCompressorChannel($compressorInstance);
 
-       /**
-        * Setter for save path
-        *
-        * @param               $savePath               The local save path where we shall put our serialized classes
-        * @return      void
-        */
-       public final function setSavePath ($savePath) {
-               // Secure string
-               $savePath = (string) $savePath;
+               // Get a file IO handler
+               $fileIoInstance = ObjectFactory::createObjectByConfiguredName('file_io_class');
 
-               // Set save path
-               $this->savePath = $savePath;
-       }
+               // ... and set it
+               $databaseInstance->setFileIoInstance($fileIoInstance);
 
-       /**
-        * Getter for save path
-        *
-        * @return      $savePath               The local save path where we shall put our serialized classes
-        */
-       public final function getSavePath () {
-               return $this->savePath;
-       }
-
-       /**
-        * Getter for last error message
-        *
-        * @return      $lastError      Last error message
-        */
-       public final function getLastError () {
-               return $this->lastError;
-       }
+               // "Connect" to the database
+               $databaseInstance->connectToDatabase();
 
-       /**
-        * Getter for last exception
-        *
-        * @return      $lastException  Last thrown exception
-        */
-       public final function getLastException () {
-               return $this->lastException;
+               // Return database instance
+               return $databaseInstance;
        }
 
        /**
@@ -168,21 +109,10 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                $this->lastFile = (string) $fqfn;
        }
 
-       /**
-        * Reset the last error and exception instance. This should be done after
-        * a successfull "query"
-        *
-        * @return      void
-        */
-       private final function resetLastError () {
-               $this->lastError = '';
-               $this->lastException = null;
-       }
-
        /**
         * Getter for last read file
         *
-        * @return      $lastFile               The last read file's name with full path
+        * @return      $lastFile       The last read file's name with full path
         */
        public final function getLastFile () {
                return $this->lastFile;
@@ -191,7 +121,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
        /**
         * Setter for contents of the last read file
         *
-        * @param               $contents               An array with header and data elements
+        * @param               $contents       An array with header and data elements
         * @return      void
         */
        private final function setLastFileContents (array $contents) {
@@ -233,14 +163,14 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         * @return      $dataArray
         */
        private function getDataArrayFromFile ($fqfn) {
-               // Get a file pointer
-               $fileInstance = FrameworkFileInputPointer::createFrameworkFileInputPointer($fqfn);
+               // Debug message
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: Reading elements from database file ' . $fqfn . ' ...');
 
-               // Get the raw data and BASE64-decode it
-               $compressedData = base64_decode($fileInstance->readLinesFromFile());
+               // Init compressed data
+               $compressedData = $this->getFileIoInstance()->loadFileContents($fqfn);
+               $compressedData = $compressedData['data'];
 
                // Close the file and throw the instance away
-               $fileInstance->closeFile();
                unset($fileInstance);
 
                // Decompress it
@@ -249,6 +179,9 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                // Unserialize it
                $dataArray = unserialize($serializedData);
 
+               // Debug message
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: Read ' . count($dataArray) . ' elements from database file ' . $fqfn . '.');
+
                // Finally return it
                return $dataArray;
        }
@@ -261,17 +194,20 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         * @return      void
         */
        private function writeDataArrayToFqfn ($fqfn, array $dataArray) {
-               // Get a file pointer instance
-               $fileInstance = FrameworkFileOutputPointer::createFrameworkFileOutputPointer($fqfn, 'w');
+               // Debug message
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: Flushing ' . count($dataArray) . ' elements to database file ' . $fqfn . ' ...');
 
                // Serialize and compress it
                $compressedData = $this->getCompressorChannel()->getCompressor()->compressStream(serialize($dataArray));
 
+               // Write data
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: Writing ' . strlen($compressedData) . ' bytes ...');
+
                // Write this data BASE64 encoded to the file
-               $fileInstance->writeToFile(base64_encode($compressedData));
+               $this->getFileIoInstance()->saveFile($fqfn, $compressedData);
 
-               // Close the file pointer
-               $fileInstance->closeFile();
+               // Debug message
+               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: Flushing ' . count($dataArray) . ' elements to database file completed.');
        }
 
        /**
@@ -290,7 +226,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                // Get the file contents
                try {
                        $infoArray = $this->getDataArrayFromFile($fqfn);
-               } catch (FileNotFoundException $e) {
+               } catch (FileIoException $e) {
                        // Not found, so ignore it here
                }
 
@@ -307,7 +243,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         */
        private function generateFqfnFromDataSet (Criteria $dataSetInstance, $rowName) {
                // This is the FQFN
-               $fqfn = $this->getSavePath() . $dataSetInstance->getTableName() . '/' . $rowName . '.' . $this->getFileExtension();
+               $fqfn = $this->getConfigInstance()->getConfigEntry('local_db_path') . $dataSetInstance->getTableName() . '/' . $rowName . '.' . $this->getFileExtension();
 
                // Return it
                return $fqfn;
@@ -370,19 +306,18 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         * Starts a SELECT query on the database by given return type, table name
         * and search criteria
         *
-        * @param       $resultType             Result type ('array', 'object' and 'indexed' are valid)
         * @param       $tableName              Name of the database table
         * @param       $criteria               Local search criteria class
         * @return      $resultData             Result data of the query
         * @throws      UnsupportedCriteriaException    If the criteria is unsupported
         * @throws      SqlException                                    If an 'SQL error' occurs
         */
-       public function querySelect ($resultType, $tableName, LocalSearchCriteria $criteriaInstance) {
+       public function querySelect ($tableName, LocalSearchCriteria $criteriaInstance) {
                // The result is null by any errors
-               $resultData = null;
+               $resultData = NULL;
 
                // Create full path name
-               $pathName = $this->getSavePath() . $tableName . '/';
+               $pathName = $this->getConfigInstance()->getConfigEntry('local_db_path') . $tableName . '/';
 
                // A 'select' query is not that easy on local files, so first try to
                // find the 'table' which is in fact a directory on the server
@@ -392,8 +327,8 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
 
                        // Initialize the result data, this need to be rewritten e.g. if a local file cannot be read
                        $resultData = array(
-                               'status'        => LocalfileDatabase::RESULT_OKAY,
-                               'rows'          => array()
+                               BaseDatabaseBackend::RESULT_INDEX_STATUS => self::RESULT_OKAY,
+                               BaseDatabaseBackend::RESULT_INDEX_ROWS   => array()
                        );
 
                        // Initialize limit/skip
@@ -402,7 +337,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                        $idx = 1;
 
                        // Read the directory with some exceptions
-                       while (($dataFile = $directoryInstance->readDirectoryExcept(array('.', '..', '.htaccess', '.svn', "info." . $this->getFileExtension()))) && ($limitFound < $criteriaInstance->getLimit())) {
+                       while (($dataFile = $directoryInstance->readDirectoryExcept(array('.', '..', '.htaccess', '.svn', 'info.' . $this->getFileExtension()))) && (($limitFound < $criteriaInstance->getLimit()) || ($criteriaInstance->getLimit() == 0))) {
                                // Does the extension match?
                                if (substr($dataFile, -(strlen($this->getFileExtension()))) !== $this->getFileExtension()) {
                                        // Skip this file!
@@ -411,6 +346,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
 
                                // Read the file
                                $dataArray = $this->getDataArrayFromFile($pathName . $dataFile);
+                               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',dataArray='.print_r($dataArray,true));
 
                                // Is this an array?
                                if (is_array($dataArray)) {
@@ -419,9 +355,9 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                                                // Get criteria element
                                                $criteria = $criteriaInstance->getCriteriaElemnent($key);
 
-                                               // Is the criteria met?
-                                               if ((!is_null($criteria)) && ($criteria == $value))  {
-
+                                               // Is the criteria met or none set?
+                                               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: criteria[' . gettype($criteria) . ']=' . $criteria . ';()=' . strlen($criteria) . ',criteriaInstance()=' . $criteriaInstance->count() . ',value(' . strlen($value) . ')=' . $value);
+                                               if (((!is_null($criteria)) && ($criteria == $value)) || ($criteriaInstance->count() == 0))  {
                                                        // Shall we skip this entry?
                                                        if ($criteriaInstance->getSkip() > 0) {
                                                                // We shall skip some entries
@@ -436,7 +372,8 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                                                        $dataArray[$this->getIndexKey()] = $idx;
 
                                                        // Entry found!
-                                                       $resultData['rows'][] = $dataArray;
+                                                       //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: indexKey=' . $this->getIndexKey() . ',idx=' . $idx . ',dataArray=' . print_r($dataArray, true));
+                                                       $resultData[BaseDatabaseBackend::RESULT_INDEX_ROWS][] = $dataArray;
 
                                                        // Count found entries up
                                                        $limitFound++;
@@ -456,19 +393,17 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                        $directoryInstance->closeDirectory();
                        unset($directoryInstance);
 
-                       // Reset last error message and exception
-                       $this->resetLastError();
+                       // Reset last exception
+                       $this->resetLastException();
                } catch (PathIsNoDirectoryException $e) {
                        // Path not found means "table not found" for real databases...
-                       $this->lastException = $e;
-                       $this->lastError = $e->getMessage();
+                       $this->setLastException($e);
 
                        // So throw an SqlException here with faked error message
                        throw new SqlException (array($this, sprintf("Table &#39;%s&#39; not found", $tableName), self::DB_CODE_TABLE_MISSING), self::EXCEPTION_SQL_QUERY);
                } catch (FrameworkException $e) {
                        // Catch all exceptions and store them in last error
-                       $this->lastException = $e;
-                       $this->lastError = $e->getMessage();
+                       $this->setLastException($e);
                }
 
                // Return the gathered result
@@ -494,15 +429,14 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                        // Update the primary key
                        $this->updatePrimaryKey($dataSetInstance);
 
-                       // Reset last error message and exception
-                       $this->resetLastError();
+                       // Reset last exception
+                       $this->resetLastException();
                } catch (FrameworkException $e) {
                        // Catch all exceptions and store them in last error
-                       $this->lastException = $e;
-                       $this->lastError = $e->getMessage();
+                       $this->setLastException($e);
 
                        // Throw an SQL exception
-                       throw new SqlException (array($this, sprintf("Cannot write data to table &#39;%s&#39;", $tableName), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY);
+                       throw new SqlException(array($this, sprintf("Cannot write data to table &#39;%s&#39;, is the table created?", $dataSetInstance->getTableName()), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY);
                }
        }
 
@@ -515,7 +449,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         */
        public function queryUpdateDataSet (StoreableCriteria $dataSetInstance) {
                // Create full path name
-               $pathName = $this->getSavePath() . $dataSetInstance->getTableName() . '/';
+               $pathName = $this->getConfigInstance()->getConfigEntry('local_db_path') . $dataSetInstance->getTableName() . '/';
 
                // Try all the requests
                try {
@@ -533,15 +467,18 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                        $searchInstance = $dataSetInstance->getSearchInstance();
 
                        // Read the directory with some exceptions
-                       while (($dataFile = $directoryInstance->readDirectoryExcept(array('.', '..', '.htaccess', '.svn', "info." . $this->getFileExtension()))) && ($limitFound < $searchInstance->getLimit())) {
+                       while (($dataFile = $directoryInstance->readDirectoryExcept(array('.', '..', '.htaccess', '.svn', 'info.' . $this->getFileExtension()))) && ($limitFound < $searchInstance->getLimit())) {
                                // Does the extension match?
                                if (substr($dataFile, -(strlen($this->getFileExtension()))) !== $this->getFileExtension()) {
+                                       // Debug message
+                                       //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',getFileExtension()=' . $this->getFileExtension() . ' - SKIPPED!');
                                        // Skip this file!
                                        continue;
-                               }
+                               } // END - if
 
                                // Open this file for reading
                                $dataArray = $this->getDataArrayFromFile($pathName . $dataFile);
+                               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',dataArray='.print_r($dataArray,true));
 
                                // Is this an array?
                                if (is_array($dataArray)) {
@@ -549,10 +486,10 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                                        foreach ($dataArray as $key => $value) {
                                                // Get criteria element
                                                $criteria = $searchInstance->getCriteriaElemnent($key);
+                                               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: dataFile=' . $dataFile . ',key=' . $key . ',criteria=' . $criteria);
 
                                                // Is the criteria met?
-                                               if ((!is_null($criteria)) && ($criteria == $value))  {
-
+                                               if (((!is_null($criteria)) && ($criteria == $value)) || ($searchInstance->count() == 0))  {
                                                        // Shall we skip this entry?
                                                        if ($searchInstance->getSkip() > 0) {
                                                                // We shall skip some entries
@@ -565,6 +502,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
 
                                                        // Entry found, so update it
                                                        foreach ($criteriaArray as $criteriaKey => $criteriaValue) {
+                                                               //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('DATABASE: criteriaKey=' . $criteriaKey . ',criteriaValue=' . $criteriaValue);
                                                                $dataArray[$criteriaKey] = $criteriaValue;
                                                        } // END - foreach
 
@@ -585,15 +523,14 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
                        // Update the primary key
                        $this->updatePrimaryKey($dataSetInstance);
 
-                       // Reset last error message and exception
-                       $this->resetLastError();
+                       // Reset last exception
+                       $this->resetLastException();
                } catch (FrameworkException $e) {
                        // Catch all exceptions and store them in last error
-                       $this->lastException = $e;
-                       $this->lastError = $e->getMessage();
+                       $this->setLastException($e);
 
                        // Throw an SQL exception
-                       throw new SqlException (array($this, sprintf("Cannot write data to table &#39;%s&#39;", $dataSetInstance->getTableName()), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY);
+                       throw new SqlException(array($this, sprintf("Cannot write data to table &#39;%s&#39;, is the table created?", $dataSetInstance->getTableName()), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY);
                }
        }
 
@@ -606,7 +543,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend
         */
        public function getPrimaryKeyOfTable ($tableName) {
                // Default key is null
-               $primaryKey = null;
+               $primaryKey = NULL;
 
                // Does the table information exist?
                if (isset($this->tableInfo[$tableName])) {