X-Git-Url: https://git.mxchange.org/?p=shipsimu.git;a=blobdiff_plain;f=inc%2Fclasses%2Fmain%2Fdatabase%2Fdatabases%2Fclass_LocalFileDatabase.php;h=29e2e5e0f397f3dab498d8dc80ce70565a1598ce;hp=f599673a400e259ce5865038ff492b8336f12a32;hb=7def3a8992918dd9fe5f55d14d89c9cad4db0cbd;hpb=55b327a3f5f2fe1d244532e07be7444e94b2a768 diff --git a/inc/classes/main/database/databases/class_LocalFileDatabase.php b/inc/classes/main/database/databases/class_LocalFileDatabase.php index f599673..29e2e5e 100644 --- a/inc/classes/main/database/databases/class_LocalFileDatabase.php +++ b/inc/classes/main/database/databases/class_LocalFileDatabase.php @@ -6,7 +6,7 @@ * * @author Roland Haeder * @version 0.0.0 - * @copyright Copyright(c) 2007, 2008 Roland Haeder, this is free software + * @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 * @@ -26,8 +26,9 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontendInterface { // Constants for MySQL backward-compatiblity (PLEASE FIX THEM!) - const DB_CODE_TABLE_MISSING = 0x000; - const DB_CODE_TABLE_UNWRITEABLE = 0x001; + const DB_CODE_TABLE_MISSING = 0x100; + const DB_CODE_TABLE_UNWRITEABLE = 0x101; + const DB_CODE_DATA_FILE_CORRUPT = 0x102; /** * Save path for "file database" @@ -64,6 +65,16 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend */ private $lastException = null; + /** + * Table information array + */ + private $tableInfo = array(); + + /** + * Element for index + */ + private $indexKey = "__idx"; + /** * The protected constructor. Do never instance from outside! You need to * set a local file path. The class will then validate it. @@ -74,13 +85,8 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend // Call parent constructor parent::__construct(__CLASS__); - // Set description - $this->setObjectDescription("Class for local file databases"); - - // Create unique ID - $this->generateUniqueId(); - // Clean up a little + $this->removeNumberFormaters(); $this->removeSystemArray(); } @@ -149,157 +155,15 @@ 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. - * - * @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 - * @throws InvalidArrayCountException If the array contains less or - * more than two elements - */ - public function isUniqueIdUsed ($uniqueID, $inConstructor = false) { - // Currently not used... ;-) - $isUsed = false; - - // 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 - if ($inConstructor) { - return false; - } else { - throw new NoArrayCreatedException(array($this, "pathFile"), self::EXCEPTION_ARRAY_EXPECTED); - } - } elseif (count($pathFile) != 2) { - // Invalid ID returned! - if ($inConstructor) { - return false; - } else { - throw new InvalidArrayCountException(array($this, "pathFile", count($pathFile), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT); - } - } - - // Create full path name - $pathName = $this->getSavePath() . $pathFile[0]; - - // Check if the file is there with a file handler - if ($inConstructor) { - // No exceptions in constructors and destructors! - $dirInstance = FrameworkDirectoryPointer::createFrameworkDirectoryPointer($pathName, true); - - // Has an object being created? - if (!is_object($dirInstance)) return false; - } else { - // Outside a constructor - try { - $dirInstance = FrameworkDirectoryPointer::createFrameworkDirectoryPointer($pathName); - } catch (PathIsNoDirectoryException $e) { - // Okay, path not found - return false; - } - } - - // Initialize the search loop - $isValid = false; - while ($dataFile = $dirInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn"))) { - // Generate FQFN for testing - $fqfn = sprintf("%s/%s", $pathName, $dataFile); - $this->setLastFile($fqfn); - - // Get instance for file handler - $inputHandler = $this->getFileIoInstance(); - - // Try to read from it. This makes it sure that the file is - // readable and a valid database file - $this->setLastFileContents($inputHandler->loadFileContents($fqfn)); - - // Extract filename (= unique ID) from it - $ID = substr(basename($fqfn), 0, -(strlen($this->getFileExtension()) + 1)); - - // Is this the required unique ID? - if ($ID == $pathFile[1]) { - // Okay, already in use! - $isUsed = true; - } - } - - // Close the directory handler - $dirInstance->closeDirectory(); - - // Now the same for the file... - return $isUsed; - } - /** * Setter for the last read file * - * @param $fqfn The FQFN of the last read file + * @param $fqfn The FQFN of the last read file * @return void */ private final function setLastFile ($fqfn) { - // Cast string - $fqfn = (string) $fqfn; - $this->lastFile = $fqfn; + // Cast string and set it + $this->lastFile = (string) $fqfn; } /** @@ -328,9 +192,8 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend * @param $contents An array with header and data elements * @return void */ - private final function setLastFileContents ($contents) { - // Cast array - $contents = (array) $contents; + private final function setLastFileContents (array $contents) { + // Set array $this->lastContents = $contents; } @@ -353,175 +216,137 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend } /** - * Get cached (last fetched) data from the local file database + * Getter for index key * - * @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 - */ - 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); - } + * @return $indexKey Index key + */ + public final function getIndexKey () { + return $this->indexKey; + } - // 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); - } + /** + * Reads a local data file and returns it's contents in an array + * + * @param $fqfn The FQFN for the requested file + * @return $dataArray + */ + 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()); + + // Close the file and throw the instance away + $fileInstance->closeFile(); + unset($fileInstance); + + // 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 - */ - 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); - } - - // Create full path name - $pathName = $this->getSavePath() . $pathFile[0]; - - // Nothing cached, so let's create a FQFN first - $fqfn = sprintf("%s/%s.%s", $pathName, $pathFile[1], $this->getFileExtension()); - $this->setLastFile($fqfn); - } + * @param $fqfn The FQFN of the local file + * @param $dataArray An array with all the data we shall write + * @return void + */ + 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 repaired FQFN - return $fqfn; + // Close the file pointer + $fileInstance->closeFile(); } /** - * Private method for re-gathering the contents of a given file + * Getter for table information file contents or an empty if the info file was not created * - * @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 - */ - 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); + * @param $dataSetInstance An instance of a database set class + * @return $infoArray An array with all table informations + */ + private function getContentsFromTableInfoFile (StoreableCriteria $dataSetInstance) { + // Default content is no data + $infoArray = array(); + + // Create FQFN for getting the table information file + $fqfn = $this->getSavePath() . $dataSetInstance->getTableName() . '/info.' . $this->getFileExtension(); + + // Get the file contents + try { + $infoArray = $this->getDataArrayFromFile($fqfn); + } catch (FileNotFoundException $e) { + // Not found, so ignore it here } - // Return the repaired contents - return $contents; + // ... and return it + return $infoArray; } /** - * Makes sure that the database connection is alive + * Creates the table info file from given dataset instance * + * @param $dataSetInstance An instance of a database set class * @return void */ - public function connectToDatabase () { - // @TODO Do some checks on the database directory and files here + private function createTableInfoFile (StoreableCriteria $dataSetInstance) { + // Create FQFN for creating the table information file + $fqfn = $this->getSavePath() . $dataSetInstance->getTableName() . '/info.' . $this->getFileExtension(); + + // Get the data out from dataset in a local array + $this->tableInfo[$dataSetInstance->getTableName()] = array( + 'primary' => $dataSetInstance->getPrimaryKey(), + 'created' => time(), + 'last_updated' => time() + ); + + // Write the data to the file + $this->writeDataArrayToFqfn($fqfn, $this->tableInfo[$dataSetInstance->getTableName()]); } /** - * 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. + * Updates the primary key information or creates the table info file if not found * - * @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); + * @param $dataSetInstance An instance of a database set class + * @return void + */ + private function updatePrimaryKey (StoreableCriteria $dataSetInstance) { + // Get the information array from lower method + $infoArray = $this->getContentsFromTableInfoFile($dataSetInstance); + + // Is the primary key there? + if (!isset($this->tableInfo['primary'])) { + // Then create the info file + $this->createTableInfoFile($dataSetInstance); + } elseif (($this->getConfigInstance()->readConfig('db_update_primary_forced') === "Y") && ($dataSetInstance->getPrimaryKey() != $this->tableInfo['primary'])) { + // Set the array element + $this->tableInfo[$dataSetInstance->getTableName()]['primary'] = $dataSetInstance->getPrimaryKey(); + + // Update the entry + $this->updateTableInfoFile($dataSetInstance); } + } - // "Connection" established... ;-) - $this->alreadyConnected = true; + /** + * Makes sure that the database connection is alive + * + * @return void + * @todo Do some checks on the database directory and files here + */ + public function connectToDatabase () { } /** @@ -555,30 +380,25 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend ); // Initialize limit/skip - $limitFound = 0; $skipFound = 0; + $limitFound = 0; + $skipFound = 0; + $idx = 1; // Read the directory with some exceptions - while (($dataFile = $directoryInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn"))) && ($limitFound < $criteriaInstance->getLimit())) { - // Open this file for reading - $filePointer = FrameworkFileInputPointer::createFrameworkFileInputPointer($pathName . $dataFile); - - // Get the raw data and BASE64-decode it - $compressedData = base64_decode($filePointer->readLinesFromFile()); - - // Close the file and throw the instance away - $filePointer->closeFile(); - unset($filePointer); - - // Decompress it - $serializedData = $this->getCompressorChannel()->getCompressor()->decompressStream($compressedData); + while (($dataFile = $directoryInstance->readDirectoryExcept(array(".", "..", ".htaccess", ".svn", "info." . $this->getFileExtension()))) && ($limitFound < $criteriaInstance->getLimit())) { + // Does the extension match? + if (substr($dataFile, -(strlen($this->getFileExtension()))) !== $this->getFileExtension()) { + // Skip this file! + continue; + } // END - if - // Unserialize it - $dataArray = unserialize($serializedData); + // 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) { + foreach ($dataArray as $key => $value) { // Get criteria element $criteria = $criteriaInstance->getCriteriaElemnent($key); @@ -595,13 +415,24 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend } // END - if } // END - if + // Set id number + $dataArray[$this->getIndexKey()] = $idx; + // Entry found! - $resultData['rows'][] = $dataArray; + $resultData['rows'][] = $dataArray; + + // Count found entries up $limitFound++; break; } // END - if } // END - foreach - } // END - if + } else { + // Throw an exception here + throw new SqlException(array($this, sprintf("File '%s' contains invalid data.", $dataFile), self::DB_CODE_DATA_FILE_CORRUPT), self::EXCEPTION_SQL_QUERY); + } + + // Count entry up + $idx++; } // END - while // Close directory and throw the instance away @@ -644,21 +475,103 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend ); // Try to save the request away + try { + // Write the data away + $this->writeDataArrayToFqfn($fqfn, $dataSetInstance->getCriteriaArray()); + + // Update the primary key + $this->updatePrimaryKey($dataSetInstance); + + // 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 - $filePointer = FrameworkFileOutputPointer::createFrameworkFileOutputPointer($fqfn, 'w'); + $directoryInstance = FrameworkDirectoryPointer::createFrameworkDirectoryPointer($pathName); + + // Initialize limit/skip + $limitFound = 0; + $skipFound = 0; // Get the criteria array from the dataset $criteriaArray = $dataSetInstance->getCriteriaArray(); - // Serialize and compress it - $compressedData = $this->getCompressorChannel()->getCompressor()->compressStream(serialize($criteriaArray)); + // Get search criteria + $searchInstance = $dataSetInstance->getSearchInstance(); - // Write this data BASE64 encoded to the file - $filePointer->writeToFile(base64_encode($compressedData)); + // Read the directory with some exceptions + 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()) { + // Skip this file! + continue; + } + + // 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 - $filePointer->closeFile(); + $directoryInstance->closeDirectory(); + + // Update the primary key + $this->updatePrimaryKey($dataSetInstance); // Reset last error message and exception $this->resetLastError(); @@ -668,9 +581,30 @@ class LocalFileDatabase extends BaseDatabaseFrontend implements DatabaseFrontend $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); + throw new SqlException (array($this, sprintf("Cannot write data to table '%s'", $dataSetInstance->getTableName()), self::DB_CODE_TABLE_UNWRITEABLE), self::EXCEPTION_SQL_QUERY); } } + + /** + * Getter for primary key of specified table or if not found null will be + * returned. This must be database-specific. + * + * @param $tableName Name of the table we need the primary key from + * @return $primaryKey Primary key column of the given table + */ + public function getPrimaryKeyOfTable ($tableName) { + // Default key is null + $primaryKey = null; + + // Does the table information exist? + if (isset($this->tableInfo[$tableName])) { + // Then return the primary key + $primaryKey = $this->tableInfo[$tableName]['primary']; + } // END - if + + // Return the column + return $primaryKey; + } } // [EOF]