X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;ds=sidebyside;f=inc%2Fclasses%2Fmain%2Fdatabase%2Fdatabases%2Fclass_LocalFileDatabase.php;h=b3ee46ecb7b2b22851e2337439289394b4797417;hb=4c35e8280767c1e33832097060e39a32b80ffd01;hp=db1d87e90aa7ccac0db9deaf5a22a26bbf733f8d;hpb=b941582981610e1e07e1dbb3adb149d4b727808c;p=shipsimu.git diff --git a/inc/classes/main/database/databases/class_LocalFileDatabase.php b/inc/classes/main/database/databases/class_LocalFileDatabase.php index db1d87e..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,8 +24,11 @@ * 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 = 0x000; + const DB_CODE_TABLE_MISSING = 0x010; + const DB_CODE_TABLE_UNWRITEABLE = 0x011; + const DB_CODE_DATA_FILE_CORRUPT = 0x012; /** * Save path for "file database" @@ -76,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(); @@ -147,58 +150,6 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend return $this->lastException; } - /** - * Saves a given object to the local file system by serializing and - * transparently compressing it - * - * @param $object The object we shall save to the local file system - * @return void - */ - public final function saveObject (FrameworkInterface $object) { - // 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)); - } - - /** - * Get a serialized string from the given object - * - * @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 - */ - private function serializeObject (FrameworkInterface $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; - } - /** * Analyses if a unique ID has already been used or not by search in the * local database folder. @@ -351,175 +302,60 @@ 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); - } - - // 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 (!$object instanceof FrameworkInterface) { - // A highly required method was not found... :-( - throw new MissingMethodException(array($object, '__toString'), self::EXCEPTION_MISSING_METHOD); - } + private function getDataArrayFromFile ($fqfn) { + // Get a file pointer + $fileInstance = FrameworkFileInputPointer::createFrameworkFileInputPointer($fqfn); - // And return the object - return $object; - } + // Get the raw data and BASE64-decode it + $compressedData = base64_decode($fileInstance->readLinesFromFile()); - /** - * Private method for re-gathering (repairing) the FQFN - * - * @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 - */ - 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); - } + // Close the file and throw the instance away + $fileInstance->closeFile(); + unset($fileInstance); - // Create full path name - $pathName = $this->getSavePath() . $pathFile[0]; + // Decompress it + $serializedData = $this->getCompressorChannel()->getCompressor()->decompressStream($compressedData); - // Nothing cached, so let's create a FQFN first - $fqfn = sprintf("%s/%s.%s", $pathName, $pathFile[1], $this->getFileExtension()); - $this->setLastFile($fqfn); - } + // Unserialize it + $dataArray = unserialize($serializedData); - // Return repaired FQFN - return $fqfn; + // Finally return it + return $dataArray; } /** - * Private method for re-gathering the contents of a given file + * Writes data array to local file * - * @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 + * @param $fqfn The FQFN of the local file + * @param $dataArray An array with all the data we shall write + * @return void */ - 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); - } + private function writeDataArrayToFqfn ($fqfn, array $dataArray) { + // Get a file pointer instance + $fileInstance = FrameworkFileOutputPointer::createFrameworkFileOutputPointer($fqfn, 'w'); + + // Serialize and compress it + $compressedData = $this->getCompressorChannel()->getCompressor()->compressStream(serialize($dataArray)); + + // Write this data BASE64 encoded to the file + $fileInstance->writeToFile(base64_encode($compressedData)); - // Return the repaired contents - return $contents; + // Close the file pointer + $fileInstance->closeFile(); } /** * Makes sure that the database connection is alive * * @return void + * @todo Do some checks on the database directory and files here */ public function connectToDatabase () { - // @TODO Do some checks on the database directory and files here - } - - /** - * Loads data saved with saveObject from the database and re-creates a - * full object from it. - * If limitObject() was called before a new object ObjectContainer with - * all requested attributes will be returned instead. - * - * @return Object The fully re-created object or instance to - * ObjectContainer - * @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 function loadObject () { - // Already connected? Then abort here - if ($this->alreadyConnected === true) return true; - - // Get the save path - $savePath = $this->getSavePath(); - - 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); - } - - // "Connection" established... ;-) - $this->alreadyConnected = true; } /** @@ -533,16 +369,10 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend * @throws UnsupportedCriteriaException If the criteria is unsupported * @throws SqlException If an "SQL error" occurs */ - public function querySelect ($resultType, $tableName, Criteria $criteriaInstance) { + public function querySelect ($resultType, $tableName, LocalSearchCriteria $criteriaInstance) { // The result is null by any errors $resultData = null; - // Is this criteria supported? - if (!$criteriaInstance instanceof LocalSearchCriteria) { - // Not supported by this database layer - throw new UnsupportedCriteriaException(array($this, $criteriaInstance), self::EXCEPTION_REQUIRED_INTERFACE_MISSING); - } - // Create full path name $pathName = $this->getSavePath() . $tableName . '/'; @@ -558,9 +388,44 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend 'rows' => array() ); + // Initialize limit/skip + $limitFound = 0; $skipFound = 0; + // Read the directory with some exceptions - while ($dataFile = $directoryInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn"))) { - $this->partialStub(sprintf("File %s found.", $dataFile)); + 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 @@ -591,20 +456,102 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend * * @param $dataSetInstance A storeable data set * @return void + * @throws SqlException If an SQL error occurs */ - public function insertDataSet (StoreableCriteria $dataSetInstance) { + public function queryInsertDataSet (StoreableCriteria $dataSetInstance) { // Create full path name $fqfn = sprintf("%s%s/%s.%s", $this->getSavePath(), $dataSetInstance->getTableName(), - $dataSetInstance->getCombinedUniqueKey(), + md5($dataSetInstance->getUniqueValue()), $this->getFileExtension() ); - die($fqfn); // 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(); @@ -612,6 +559,9 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend // 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); } } }