X-Git-Url: https://git.mxchange.org/?p=shipsimu.git;a=blobdiff_plain;f=inc%2Fclasses%2Fmain%2Fdatabase%2Fdatabases%2Fclass_LocalFileDatabase.php;h=b3ee46ecb7b2b22851e2337439289394b4797417;hp=432a168f04834f7209b6602df110caf2858694ed;hb=4c35e8280767c1e33832097060e39a32b80ffd01;hpb=b1583f59560e2878c10df2c485cc3a63a309ac3c diff --git a/inc/classes/main/database/databases/class_LocalFileDatabase.php b/inc/classes/main/database/databases/class_LocalFileDatabase.php index 432a168..b3ee46e 100644 --- a/inc/classes/main/database/databases/class_LocalFileDatabase.php +++ b/inc/classes/main/database/databases/class_LocalFileDatabase.php @@ -8,7 +8,7 @@ * @version 0.0.0 * @copyright Copyright(c) 2007, 2008 Roland Haeder, this is free software * @license GNU GPL 3.0 or any newer version - * @link http://www.ship-simu.org + * @link http://www.ship-simu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,12 @@ * along with this program. If not, see . */ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontendInterface { + + // Constants for MySQL backward-compatiblity (PLEASE FIX THEM!) + const DB_CODE_TABLE_MISSING = 0x010; + const DB_CODE_TABLE_UNWRITEABLE = 0x011; + const DB_CODE_DATA_FILE_CORRUPT = 0x012; + /** * Save path for "file database" */ @@ -45,8 +51,23 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend private $lastContents = array(); /** - * The private constructor. Do never instance from outside! - * You need to set a local file path. The class will then validate it. + * Wether the "connection is already up + */ + private $alreadyConnected = false; + + /** + * Last error message + */ + private $lastError = ""; + + /** + * Last exception + */ + private $lastException = null; + + /** + * The protected constructor. Do never instance from outside! You need to + * set a local file path. The class will then validate it. * * @return void */ @@ -58,7 +79,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend $this->setObjectDescription("Class for local file databases"); // Create unique ID - $this->createUniqueID(); + $this->generateUniqueId(); // Clean up a little $this->removeSystemArray(); @@ -72,37 +93,18 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend * @param $ioInstance The input/output handler. This * should be FileIoHandler * @return $dbInstance An instance of LocalFileDatabase - * @throws SavePathIsEmptyException If the given save path is an - * empty string - * @throws SavePathIsNoDirectoryException If the save path is no - * path (e.g. a file) - * @throws SavePathReadProtectedException If the save path is read- - * protected - * @throws SavePathWriteProtectedException If the save path is write- - * protected */ public final static function createLocalFileDatabase ($savePath, FileIoHandler $ioInstance) { // Get an instance $dbInstance = new LocalFileDatabase(); - if (empty($savePath)) { - // Empty string - throw new SavePathIsEmptyException($dbInstance, self::EXCEPTION_UNEXPECTED_EMPTY_STRING); - } elseif (!is_dir($savePath)) { - // Is not a dir - throw new SavePathIsNoDirectoryException($savePath, self::EXCEPTION_INVALID_PATH_NAME); - } elseif (!is_readable($savePath)) { - // Path not readable - throw new SavePathReadProtectedException($savePath, self::EXCEPTION_READ_PROTECED_PATH); - } elseif (!is_writeable($savePath)) { - // Path not writeable - throw new SavePathWriteProtectedException($savePath, self::EXCEPTION_WRITE_PROTECED_PATH); - } - // Set save path and IO instance $dbInstance->setSavePath($savePath); $dbInstance->setFileIoInstance($ioInstance); + // "Connect" to the database + $dbInstance->connectToDatabase(); + // Return database instance return $dbInstance; } @@ -131,83 +133,34 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend } /** - * Saves a given object to the local file system by serializing and - * transparently compressing it + * Getter for last error message * - * @param $object The object we shall save to the local file system - * @return void - * @throws NullPointerException If the object instance is null - * @throws NoObjectException If the parameter $object is not - * an object + * @return $lastError Last error message */ - public final function saveObject ($object) { - // Some tests on the parameter... - if (is_null($object)) { - // Is null, throw exception - throw new NullPointerException($object, self::EXCEPTION_IS_NULL_POINTER); - } elseif (!is_object($object)) { - // Is not an object, throw exception - throw new NoObjectException($object, self::EXCEPTION_IS_NO_OBJECT); - } elseif (!method_exists($object, '__toString')) { - // A highly required method was not found... :-( - throw new MissingMethodException(array($object, '__toString'), self::EXCEPTION_MISSING_METHOD); - } - - // Get a string containing the serialized object. We cannot exchange - // $this and $object here because $object does not need to worry - // about it's limitations... ;-) - $serialized = $this->serializeObject($object); - - // Get a path name plus file name and append the extension - $fqfn = $this->getSavePath() . $object->getPathFileNameFromObject() . "." . $this->getFileExtension(); - - // Save the file to disc we don't care here if the path is there, - // this must be done in later methods. - $this->getFileIoInstance()->saveFile($fqfn, array($this->getCompressorChannel()->getCompressorExtension(), $serialized)); + public final function getLastError () { + return $this->lastError; } /** - * Get a serialized string from the given object + * Getter for last exception * - * @param $object The object we want to serialize and transparently - * compress - * @return $serialized A string containing the serialzed/compressed object - * @see ObjectLimits An object holding limition information - * @see SerializationContainer A special container class for e.g. - * attributes from limited objects + * @return $lastException Last thrown exception */ - private function serializeObject ($object) { - // If there is no limiter instance we serialize the whole object - // otherwise only in the limiter object (ObjectLimits) specified - // attributes summarized in a special container class - if ($this->getLimitInstance() === null) { - // Serialize the whole object. This tribble call is the reason - // why we need a fall-back implementation in CompressorChannel - // of the methods compressStream() and decompressStream(). - $serialized = $this->getCompressorChannel()->getCompressor()->compressStream(serialize($object)); - } else { - // Serialize only given attributes in a special container - $container = SerializationContainer::createSerializationContainer($this->getLimitInstance(), $object); - - // Serialize the container - $serialized = $this->getCompressorChannel()->getCompressor()->compressStream(serialize($container)); - } - - // Return the serialized object string - return $serialized; + public final function getLastException () { + return $this->lastException; } /** * Analyses if a unique ID has already been used or not by search in the * local database folder. * - * @param $uniqueID A unique ID number which shall be checked - * before it will be used - * @param $inConstructor If we got called in a de/con-structor or - * from somewhere else + * @param $uniqueID A unique ID number which shall be checked + * before it will be used + * @param $inConstructor If we got called in a de/con-structor or + * from somewhere else * @return $isUnused true = The unique ID was not found in the database, - * false = It is already in use by an other object - * @throws NoArrayCreatedException If explode() fails to create an array + * false = It is already in use by an other object + * @throws NoArrayCreatedException If explode() fails to create an array * @throws InvalidArrayCountException If the array contains less or * more than two elements */ @@ -257,7 +210,7 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend // Initialize the search loop $isValid = false; - while ($dataFile = $dirInstance->readDirectoryExcept(array(".", ".."))) { + while ($dataFile = $dirInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn"))) { // Generate FQFN for testing $fqfn = sprintf("%s/%s", $pathName, $dataFile); $this->setLastFile($fqfn); @@ -298,6 +251,17 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend $this->lastFile = $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 * @@ -338,127 +302,268 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend } /** - * Get cached (last fetched) data from the local file database + * Reads a local data file and returns it's contents in an array * - * @param $uniqueID The ID number for looking up the data - * @return $object The restored object from the maybe compressed - * serialized data - * @throws MismatchingCompressorsException If the compressor from - * the loaded file - * mismatches with the - * current used one. - * @throws NullPointerException If the restored object - * is null - * @throws NoObjectException If the restored "object" - * is not an object instance - * @throws MissingMethodException If the required method - * toString() is missing + * @param $fqfn The FQFN for the requested file + * @return $dataArray */ - public final function getObjectFromCachedData ($uniqueID) { - // Get instance for file handler - $inputHandler = $this->getFileIoInstance(); - - // Get last file's name and contents - $fqfn = $this->repairFQFN($this->getLastFile(), $uniqueID); - $contents = $this->repairContents($this->getLastContents(), $fqfn); - - // Let's decompress it. First we need the instance - $compressInstance = $this->getCompressorChannel(); - - // Is the compressor's extension the same as the one from the data? - if ($compressInstance->getCompressorExtension() != $contents['header'][0]) { - /** - * @todo For now we abort here but later we need to make this a little more dynamic. - */ - throw new MismatchingCompressorsException(array($this, $contents['header'][0], $fqfn, $compressInstance->getCompressorExtension()), self::EXCEPTION_MISMATCHING_COMPRESSORS); - } + private function getDataArrayFromFile ($fqfn) { + // Get a file pointer + $fileInstance = FrameworkFileInputPointer::createFrameworkFileInputPointer($fqfn); - // Decompress the data now - $serialized = $compressInstance->getCompressor()->decompressStream($contents['data']); - - // And unserialize it... - $object = unserialize($serialized); - - // This must become a valid object, so let's check it... - if (is_null($object)) { - // Is null, throw exception - throw new NullPointerException($object, self::EXCEPTION_IS_NULL_POINTER); - } elseif (!is_object($object)) { - // Is not an object, throw exception - throw new NoObjectException($object, self::EXCEPTION_IS_NO_OBJECT); - } elseif (!method_exists($object, '__toString')) { - // A highly required method was not found... :-( - throw new MissingMethodException(array($object, '__toString'), self::EXCEPTION_MISSING_METHOD); - } + // Get the raw data and BASE64-decode it + $compressedData = base64_decode($fileInstance->readLinesFromFile()); + + // Close the file and throw the instance away + $fileInstance->closeFile(); + unset($fileInstance); - // And return the object - return $object; + // Decompress it + $serializedData = $this->getCompressorChannel()->getCompressor()->decompressStream($compressedData); + + // Unserialize it + $dataArray = unserialize($serializedData); + + // Finally return it + return $dataArray; } /** - * Private method for re-gathering (repairing) the FQFN + * Writes data array to local file * - * @param $fqfn The current FQFN we shall validate - * @param $uniqueID The unique ID number - * @return $fqfn The repaired FQFN when it is empty - * @throws NoArrayCreatedException If explode() has not - * created an array - * @throws InvalidArrayCountException If the array count is not - * as the expected + * @param $fqfn The FQFN of the local file + * @param $dataArray An array with all the data we shall write + * @return void */ - private function repairFQFN ($fqfn, $uniqueID) { - // Cast both strings - $fqfn = (string) $fqfn; - $uniqueID = (string) $uniqueID; - - // Is there pre-cached data available? - if (empty($fqfn)) { - // Split the unique ID up in path and file name - $pathFile = explode("@", $uniqueID); - - // Are there two elements? Index 0 is the path, 1 the file name + global extension - if (!is_array($pathFile)) { - // No array found - throw new NoArrayCreatedException(array($this, "pathFile"), self::EXCEPTION_ARRAY_EXPECTED); - } elseif (count($pathFile) != 2) { - // Invalid ID returned! - throw new InvalidArrayCountException(array($this, "pathFile", count($pathFile), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT); - } + private function writeDataArrayToFqfn ($fqfn, array $dataArray) { + // Get a file pointer instance + $fileInstance = FrameworkFileOutputPointer::createFrameworkFileOutputPointer($fqfn, 'w'); - // Create full path name - $pathName = $this->getSavePath() . $pathFile[0]; + // Serialize and compress it + $compressedData = $this->getCompressorChannel()->getCompressor()->compressStream(serialize($dataArray)); - // Nothing cached, so let's create a FQFN first - $fqfn = sprintf("%s/%s.%s", $pathName, $pathFile[1], $this->getFileExtension()); - $this->setLastFile($fqfn); - } + // Write this data BASE64 encoded to the file + $fileInstance->writeToFile(base64_encode($compressedData)); - // Return repaired FQFN - return $fqfn; + // Close the file pointer + $fileInstance->closeFile(); } /** - * Private method for re-gathering the contents of a given file + * Makes sure that the database connection is alive * - * @param $contents The (maybe) already cached contents as an array - * @param $fqfn The current FQFN we shall validate - * @return $contents The repaired contents from the given file + * @return void + * @todo Do some checks on the database directory and files here + */ + public function connectToDatabase () { + } + + /** + * 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 */ - private function repairContents ($contents, $fqfn) { - // Is there some content and header (2 indexes) in? - if ((!is_array($contents)) || (count($contents) != 2) || (!isset($contents['header'])) || (!isset($contents['data']))) { - // No content found so load the file again - $contents = $inputHandler->loadFileContents($fqfn); - - // And remember all data for later usage - $this->setLastContents($contents); + public function querySelect ($resultType, $tableName, LocalSearchCriteria $criteriaInstance) { + // The result is null by any errors + $resultData = null; + + // Create full path name + $pathName = $this->getSavePath() . $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 + try { + // Get a directory pointer instance + $directoryInstance = FrameworkDirectoryPointer::createFrameworkDirectoryPointer($pathName); + + // Initialize the result data, this need to be rewritten e.g. if a local file cannot be read + $resultData = array( + 'status' => "ok", + 'rows' => array() + ); + + // Initialize limit/skip + $limitFound = 0; $skipFound = 0; + + // Read the directory with some exceptions + while (($dataFile = $directoryInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn"))) && ($limitFound < $criteriaInstance->getLimit())) { + // Read the file + $dataArray = $this->getDataArrayFromFile($pathName . $dataFile); + + // Is this an array? + if (is_array($dataArray)) { + // Search in the criteria with FMFW (First Matches, First Wins) + foreach ($dataArray as $key=>$value) { + // Get criteria element + $criteria = $criteriaInstance->getCriteriaElemnent($key); + + // Is the criteria met? + if ((!is_null($criteria)) && ($criteria == $value)) { + + // Shall we skip this entry? + if ($criteriaInstance->getSkip() > 0) { + // We shall skip some entries + if ($skipFound < $criteriaInstance->getSkip()) { + // Skip this entry + $skipFound++; + break; + } // END - if + } // END - if + + // Entry found! + $resultData['rows'][] = $dataArray; + $limitFound++; + break; + } // END - if + } // END - foreach + } else { + // Throw an exception here + throw new SqlException(sprintf("File '%s' contains invalid data.", $dataFile), self::DB_CODE_DATA_FILE_CORRUPT); + } + } // END - while + + // Close directory and throw the instance away + $directoryInstance->closeDirectory(); + unset($directoryInstance); + + // Reset last error message and exception + $this->resetLastError(); + } catch (PathIsNoDirectoryException $e) { + // Path not found means "table not found" for real databases... + $this->lastException = $e; + $this->lastError = $e->getMessage(); + + // So throw an SqlException here with faked error message + throw new SqlException (array($this, sprintf("Table '%s' 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(); } - // Return the repaired contents - return $contents; + // Return the gathered result + return $resultData; } - /* DUMMY */ public final function loadObject () {} + /** + * "Inserts" a data set instance into a local file database folder + * + * @param $dataSetInstance A storeable data set + * @return void + * @throws SqlException If an SQL error occurs + */ + public function queryInsertDataSet (StoreableCriteria $dataSetInstance) { + // Create full path name + $fqfn = sprintf("%s%s/%s.%s", + $this->getSavePath(), + $dataSetInstance->getTableName(), + md5($dataSetInstance->getUniqueValue()), + $this->getFileExtension() + ); + + // Try to save the request away + try { + // Write the data away + $this->writeDataArrayToFqfn($fqfn, $dataSetInstance->getCriteriaArray()); + + // Reset last error message and exception + $this->resetLastError(); + } catch (FrameworkException $e) { + // Catch all exceptions and store them in last error + $this->lastException = $e; + $this->lastError = $e->getMessage(); + + // Throw an SQL exception + throw new SqlException (array($this, sprintf("Cannot write data to table '%s'", $tableName), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY); + } + } + + /** + * "Updates" a data set instance with a database layer + * + * @param $dataSetInstance A storeable data set + * @return void + * @throws SqlException If an SQL error occurs + */ + public function queryUpdateDataSet (StoreableCriteria $dataSetInstance) { + // Create full path name + $pathName = $this->getSavePath() . $dataSetInstance->getTableName() . '/'; + + // Try all the requests + try { + // Get a file pointer instance + $directoryInstance = FrameworkDirectoryPointer::createFrameworkDirectoryPointer($pathName); + + // Initialize limit/skip + $limitFound = 0; $skipFound = 0; + + // Get the criteria array from the dataset + $criteriaArray = $dataSetInstance->getCriteriaArray(); + + // Get search criteria + $searchInstance = $dataSetInstance->getSearchInstance(); + + // Read the directory with some exceptions + while (($dataFile = $directoryInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn"))) && ($limitFound < $searchInstance->getLimit())) { + // Open this file for reading + $dataArray = $this->getDataArrayFromFile($pathName . $dataFile); + + // Is this an array? + if (is_array($dataArray)) { + // Search in the criteria with FMFW (First Matches, First Wins) + foreach ($dataArray as $key=>$value) { + // Get criteria element + $criteria = $searchInstance->getCriteriaElemnent($key); + + // Is the criteria met? + if ((!is_null($criteria)) && ($criteria == $value)) { + + // Shall we skip this entry? + if ($searchInstance->getSkip() > 0) { + // We shall skip some entries + if ($skipFound < $searchInstance->getSkip()) { + // Skip this entry + $skipFound++; + break; + } // END - if + } // END - if + + // Entry found, so update it + foreach ($criteriaArray as $criteriaKey=>$criteriaValue) { + $dataArray[$criteriaKey] = $criteriaValue; + } // END - foreach + + // Write the data to a local file + $this->writeDataArrayToFqfn($pathName . $dataFile, $dataArray); + + // Count it + $limitFound++; + break; + } // END - if + } // END - foreach + } // END - if + } // END - while + + // Close the file pointer + $directoryInstance->closeDirectory(); + + // Reset last error message and exception + $this->resetLastError(); + } catch (FrameworkException $e) { + // Catch all exceptions and store them in last error + $this->lastException = $e; + $this->lastError = $e->getMessage(); + + // Throw an SQL exception + throw new SqlException (array($this, sprintf("Cannot write data to table '%s'", $tableName), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY); + } + } } // [EOF]