diff --git a/clustering/dfs/ezpostsgresqlbackend.php b/clustering/dfs/ezpostsgresqlbackend.php index e89d353..1d83449 100644 --- a/clustering/dfs/ezpostsgresqlbackend.php +++ b/clustering/dfs/ezpostsgresqlbackend.php @@ -9,7 +9,7 @@ */ /** - * This class allows DFS based clustering using PostgreSQL + * This class allows DFS based clustering using PostgresSQL * @package Cluster */ class eZDFSFileHandlerPostgresqlBackend @@ -26,9 +26,27 @@ class eZDFSFileHandlerPostgresqlBackend * @var int */ protected $maxCopyTries; - + + protected static function writeError($string, $label = "", $backgroundClass = "") + { + $logName = 'cluster_error.log'; + //if ( isset( $GLOBALS['eZCurrentAccess']['name'] ) ){ + // $logName = $GLOBALS['eZCurrentAccess']['name'] . '_cluster_error.log'; + //} + + $instanceName = OpenPABase::getCurrentSiteaccessIdentifier(); + $message = "[$instanceName] "; + + if ($label){ + $message .= "[$label] $string"; + }else{ + $message .= $string; + } + eZLog::write($message, $logName); + } + public function __construct() - { + { $this->eventHandler = ezpEvent::getInstance(); $fileINI = eZINI::instance( 'file.ini' ); $this->maxCopyTries = (int)$fileINI->variable( 'eZDFSClusteringSettings', 'MaxCopyRetries' ); @@ -45,7 +63,7 @@ public function __construct() $this->cacheDir = eZINI::instance( 'site.ini' )->variable( 'FileSettings', 'CacheDir' ); $this->storageDir = eZINI::instance( 'site.ini' )->variable( 'FileSettings', 'StorageDir' ); } - + /** * Returns the database table name to use for the specified file. * @@ -60,14 +78,23 @@ protected function dbTable( $filePath ) if ( $this->metaDataTableCache == $this->metaDataTable ) return $this->metaDataTable; - if ( strpos( $filePath, $this->cacheDir ) !== false && strpos( $filePath, $this->storageDir ) === false ) + $isInCacheDir = strpos( $filePath, $this->cacheDir ); + $isInStorageDir = strpos( $filePath, $this->storageDir ); + + if ( $isInCacheDir !== false && $isInStorageDir === false ) + { + return $this->metaDataTableCache; + } + + // example var/site/cache/my_custom_cache/storage.cache + if ( $isInCacheDir !== false && $isInStorageDir !== false && ( $isInCacheDir < $isInStorageDir ) ) { return $this->metaDataTableCache; } return $this->metaDataTable; } - + /** * Connects to the database. * @@ -116,7 +143,7 @@ public function _connect() try { $this->db = new PDO( $connectString, self::$dbparams['user'], self::$dbparams['pass'] ); } catch ( PDOException $e ) { - eZDebug::writeError( $e->getMessage() ); + self::writeError( $e->getMessage() ); ++$tries; continue; } @@ -138,11 +165,16 @@ public function _connect() // DFS setup if ( $this->dfsbackend === null ) - $this->dfsbackend = new eZDFSFileHandlerDFSBackend(); + $this->dfsbackend = eZDFSFileHandlerBackendFactory::build(); + //$this->dfsbackend = new eZDFSFileHandlerDFSBackend(); + } /** * Disconnects the handler from the database + * + * @see eZDFSFileHandler::disconnect + * @return void */ public function _disconnect() { @@ -154,13 +186,17 @@ public function _disconnect() /** * Creates a copy of a file in DB+DFS + * + * @see eZDFSFileHandler::fileCopy + * @see _copyInner + * * @param string $srcFilePath Source file * @param string $dstFilePath Destination file - * @param string $fname + * @param bool|string $fname Optional caller name for debugging + * * @return bool * - * @see _copyInner - **/ + */ public function _copy( $srcFilePath, $dstFilePath, $fname = false ) { if ( $fname ) @@ -178,7 +214,7 @@ public function _copy( $srcFilePath, $dstFilePath, $fname = false ) return false; } return $this->_protect( array( $this, "_copyInner" ), $fname, - $srcFilePath, $dstFilePath, $fname, $metaData ); + $srcFilePath, $dstFilePath, $fname, $metaData ); } /** @@ -192,7 +228,7 @@ public function _copy( $srcFilePath, $dstFilePath, $fname = false ) * * @see _copy */ - private function _copyInner( $srcFilePath, $dstFilePath, $fname, $metaData ) + protected function _copyInner( $srcFilePath, $dstFilePath, $fname, $metaData ) { $this->_delete( $dstFilePath, true, $fname ); @@ -205,40 +241,42 @@ private function _copyInner( $srcFilePath, $dstFilePath, $fname, $metaData ) // Copy file metadata. if ( $this->_insertUpdate( $this->dbTable( $dstFilePath ), - array( 'datatype'=> $datatype, - 'name' => $dstFilePath, - 'name_trunk' => $nameTrunk, - 'name_hash' => $filePathHash, - 'scope' => $scope, - 'size' => $contentLength, - 'mtime' => $fileMTime, - 'expired' => ( $fileMTime < 0 ) ? 1 : 0 ), - array( 'datatype', 'scope', 'size', 'mtime', 'expired' ), - $fname ) === false ) + array( 'datatype'=> $datatype, + 'name' => $dstFilePath, + 'name_trunk' => $nameTrunk, + 'name_hash' => $filePathHash, + 'scope' => $scope, + 'size' => $contentLength, + 'mtime' => $fileMTime, + 'expired' => ( $fileMTime < 0 ) ? 1 : 0 ), + array( 'datatype', 'scope', 'size', 'mtime', 'expired' ), + $fname ) === false ) { - return $this->_fail( $srcFilePath, "Failed to insert file metadata on copying." ); + $this->_fail( $srcFilePath, "Failed to insert file metadata on copying." ); } // Copy file data. if ( !$this->dfsbackend->copyFromDFSToDFS( $srcFilePath, $dstFilePath ) ) { - return $this->_fail( $srcFilePath, "Failed to copy DFS://$srcFilePath to DFS://$dstFilePath" ); + $this->_fail( $srcFilePath, "Failed to copy DFS://$srcFilePath to DFS://$dstFilePath" ); } return true; } /** * Purges meta-data and file-data for a file entry - * * Will only expire a single file. Use _purgeByLike to purge multiple files * + * @see eZDFSFileHandler::purge + * @see _purgeByLike + * * @param string $filePath Path of the file to purge * @param bool $onlyExpired Only purges expired files * @param bool|int $expiry - * @param bool $fname + * @param bool|string $fname Optional caller name for debugging * - * @see _purgeByLike - **/ + * @return bool + */ public function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname = false ) { if ( $fname ) @@ -256,7 +294,7 @@ public function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname } if ( !$stmt = $this->_query( $sql, $fname ) ) { - return $this->_fail( "Purging file metadata for $filePath failed" ); + $this->_fail( "Purging file metadata for $filePath failed" ); } if ( $stmt->rowCount() == 1 ) { @@ -268,6 +306,10 @@ public function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname /** * Purges meta-data and file-data for files matching a pattern using a SQL * LIKE syntax. + * This method should also remove the files from disk + * + * @see eZDFSFileHandler::purge + * @see _purge * * @param string $like * SQL LIKE string applied to ezdfsfile.name to look for files to @@ -275,13 +317,12 @@ public function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname * @param bool $onlyExpired * Only purge expired files (ezdfsfile.expired = 1) * @param integer $limit Maximum number of items to purge in one call - * @param integer $expiry + * @param integer|bool $expiry * Timestamp used to limit deleted files: only files older than this * date will be deleted - * @param mixed $fname Optional caller name for debugging - * @see _purge + * @param bool|string $fname Optional caller name for debugging + * * @return bool|int false if it fails, number of affected rows otherwise - * @todo This method should also remove the files from disk */ public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry = false, $fname = false ) { @@ -311,9 +352,10 @@ public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry if ( !$stmt = $this->_query( $selectSQL, $fname ) ) { $this->_rollback( $fname ); - return $this->_fail( "Selecting file metadata by like statement $like failed" ); + $this->_fail( "Selecting file metadata by like statement $like failed" ); } + $files = array(); // if there are no results, we can just return 0 and stop right here if ( $stmt->rowCount() == 0 ) { @@ -330,12 +372,12 @@ public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry } // delete query - $deleteSQL = "DELETE FROM " . $this->dbTable( $like ) . " " . "WHERE name_hash IN " . + $deleteSQL = "DELETE FROM " . $this->dbTable( $like ) . " WHERE name_hash IN " . "(SELECT name_hash FROM ". $this->dbTable( $like ) . " $where $sqlLimit)"; if ( !$stmt = $this->_query( $deleteSQL, $fname ) ) { $this->_rollback( $fname ); - return $this->_fail( "Purging file metadata by like statement $like failed" ); + $this->_fail( "Purging file metadata by like statement $like failed" ); } $deletedDBFiles = $stmt->rowCount(); $this->dfsbackend->delete( $files ); @@ -347,17 +389,20 @@ public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry /** * Deletes a file from DB - * * The file won't be removed from disk, _purge has to be used for this. * Only single files will be deleted, to delete multiple files, * _deleteByLike has to be used. * + * @see eZDFSFileHandler::fileDelete + * @see eZDFSFileHandler::delete + * @see _deleteInner + * @see _deleteByLike + * * @param string $filePath Path of the file to delete * @param bool $insideOfTransaction * Wether or not a transaction is already started * @param bool|string $fname Optional caller name for debugging - * @see _deleteInner - * @see _deleteByLike + * * @return bool */ public function _delete( $filePath, $insideOfTransaction = false, $fname = false ) @@ -366,21 +411,18 @@ public function _delete( $filePath, $insideOfTransaction = false, $fname = false $fname .= "::_delete($filePath)"; else $fname = "_delete($filePath)"; - // @todo Check if this is requried: _protec will already take care of + // @todo Check if this is required: _protect will already take care of // checking if a transaction is running. But leave it like this // for now. if ( $insideOfTransaction ) { - $res = $this->_deleteInner( $filePath, $fname ); - if ( !$res || $res instanceof eZMySQLBackendError ) - { - $this->_handleErrorType( $res ); - } + return $this->_deleteInner( $filePath, $fname ); + } else { return $this->_protect( array( $this, '_deleteInner' ), $fname, - $filePath, $insideOfTransaction, $fname ); + $filePath, $insideOfTransaction, $fname ); } } @@ -394,23 +436,25 @@ public function _delete( $filePath, $insideOfTransaction = false, $fname = false protected function _deleteInner( $filePath, $fname ) { if ( !$this->_query( "UPDATE " . $this->dbTable( $filePath ) . " SET mtime=-ABS(mtime), expired=1 WHERE name_hash=" . $this->_md5( $filePath ), $fname ) ) - return $this->_fail( "Deleting file $filePath failed" ); + $this->_fail( "Deleting file $filePath failed" ); return true; } /** * Deletes multiple files using a SQL LIKE statement - * * Use _delete if you need to delete single files * + * @see eZDFSFileHandler::fileDelete + * @see _deleteByLikeInner + * @see _delete + * * @param string $like * SQL LIKE condition applied to ezdfsfile.name to look for files * to delete. Will use name_trunk if the LIKE string matches a * filetype that supports name_trunk. - * @param string $fname Optional caller name for debugging + * @param bool|string $fname Optional caller name for debugging + * * @return bool - * @see _deleteByLikeInner - * @see _delete */ public function _deleteByLike( $like, $fname = false ) { @@ -419,22 +463,27 @@ public function _deleteByLike( $like, $fname = false ) else $fname = "_deleteByLike($like)"; return $this->_protect( array( $this, '_deleteByLikeInner' ), $fname, - $like, $fname ); + $like, $fname ); } /** - * Callback used by _deleteByLike to perform the deletion + * @see _deleteByLike * * @param string $like - * @param mixed $fname - * @return + * SQL LIKE condition applied to ezdfsfile.name to look for files + * to delete. Will use name_trunk if the LIKE string matches a + * filetype that supports name_trunk. + * @param bool|string $fname Optional caller name for debugging + * + * @return bool|void + * @throws Exception */ - private function _deleteByLikeInner( $like, $fname ) + protected function _deleteByLikeInner( $like, $fname ) { $sql = "UPDATE " . $this->dbTable( $like ) . " SET mtime=-ABS(mtime), expired=1\nWHERE name like ". $this->_quote( $like ); if ( !$res = $this->_query( $sql, $fname ) ) { - return $this->_fail( "Failed to delete files by like: '$like'" ); + $this->_fail( "Failed to delete files by like: '$like'" ); } return true; } @@ -454,23 +503,23 @@ public function _deleteByRegex( $regex, $fname = false ) else $fname = "_deleteByRegex($regex)"; return $this->_protect( array( $this, '_deleteByRegexInner' ), $fname, - $regex, $fname ); + $regex, $fname ); } /** - * Callback used by _deleteByRegex to perform the deletion + * Deletes DB files by using a SQL regular expression applied to file names * - * @param mixed $regex + * @param string $regex * @param mixed $fname - * @return - * @deprecated Has severe performances issues + * @return bool + * @deprecated Has severe performance issues */ - public function _deleteByRegexInner( $regex, $fname ) + protected function _deleteByRegexInner( $regex, $fname ) { $sql = "UPDATE " . $this->dbTable( $regex ) . " SET mtime=-ABS(mtime), expired=1\nWHERE name REGEXP " . $this->_quote( $regex ); if ( !$res = $this->_query( $sql, $fname ) ) { - return $this->_fail( "Failed to delete files by regex: '$regex'" ); + $this->_fail( "Failed to delete files by regex: '$regex'" ); } return true; } @@ -490,7 +539,7 @@ public function _deleteByWildcard( $wildcard, $fname = false ) else $fname = "_deleteByWildcard($wildcard)"; return $this->_protect( array( $this, '_deleteByWildcardInner' ), $fname, - $wildcard, $fname ); + $wildcard, $fname ); } /** @@ -507,29 +556,42 @@ protected function _deleteByWildcardInner( $wildcard, $fname ) $regex = '^' . pg_escape_string( $this->db, $wildcard ) . '$'; $regex = str_replace( array( '.' ), - array( '\.' ), - $regex ); + array( '\.' ), + $regex ); $regex = str_replace( array( '?', '*', '{', '}', ',' ), - array( '.', '.*', '(', ')', '|' ), - $regex ); + array( '.', '.*', '(', ')', '|' ), + $regex ); $sql = "UPDATE " . $this->dbTable( $wildcard ) . " SET mtime=-ABS(mtime), expired=1\nWHERE name REGEXP '$regex'"; if ( !$res = $this->_query( $sql, $fname ) ) { - return $this->_fail( "Failed to delete files by wildcard: '$wildcard'" ); + $this->_fail( "Failed to delete files by wildcard: '$wildcard'" ); } return true; } + /** + * Deletes a list of files based on directory / filename components + * + * @see eZDFSFileHandler::fileDeleteByDirList + * + * @param array $dirList Array of directory that will be prefixed with + * $commonPath when looking for files + * @param string $commonPath Starting path common to every delete request + * @param string $commonSuffix Suffix appended to every delete request + * @param bool|string $fname Optional caller name for debugging + * + * @return bool + */ public function _deleteByDirList( $dirList, $commonPath, $commonSuffix, $fname = false ) { if ( $fname ) - $fname .= "::_deleteByDirList($dirList, $commonPath, $commonSuffix)"; + $fname .= "::_deleteByDirList(" . implode( ' ',$dirList ) . ", $commonPath, $commonSuffix)"; else - $fname = "_deleteByDirList($dirList, $commonPath, $commonSuffix)"; + $fname = "_deleteByDirList(" . implode( ' ',$dirList ) . ", $commonPath, $commonSuffix)"; return $this->_protect( array( $this, '_deleteByDirListInner' ), $fname, - $dirList, $commonPath, $commonSuffix, $fname ); + $dirList, $commonPath, $commonSuffix, $fname ); } protected function _deleteByDirListInner( $dirList, $commonPath, $commonSuffix, $fname ) @@ -547,12 +609,25 @@ protected function _deleteByDirListInner( $dirList, $commonPath, $commonSuffix, $sql = "UPDATE " . $this->dbTable( $commonPath ) . " SET mtime=-ABS(mtime), expired=1\n$where"; if ( !$stmt = $this->_query( $sql, $fname ) ) { - eZDebug::writeError( "Failed to delete files in dir: '$commonPath/$dirItem/$commonSuffix%'", __METHOD__ ); + self::writeError( "Failed to delete files in dir: '$commonPath/$dirItem/$commonSuffix%'", __METHOD__ ); } } return true; } + /** + * Check if given file/dir exists. + * + * @see eZDFSFileHandler::fileExists + * @see eZDFSFileHandler::exists + * + * @param $filePath + * @param bool|string $fname Optional caller name for debugging + * @param bool $ignoreExpiredFiles ignore ezdfsfile.mtime + * @param bool $checkOnDFS Checks if a file exists on the DFS + * + * @return bool + */ public function _exists( $filePath, $fname = false, $ignoreExpiredFiles = true, $checkOnDFS = false ) { if ( $fname ) @@ -560,7 +635,7 @@ public function _exists( $filePath, $fname = false, $ignoreExpiredFiles = true, else $fname = "_exists($filePath)"; $row = $this->_selectOneRow( "SELECT name, mtime FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $filePath ), - $fname, "Failed to check file '$filePath' existance: ", true ); + $fname, "Failed to check file '$filePath' existence: ", true ); if ( $row === false ) return false; @@ -573,7 +648,7 @@ public function _exists( $filePath, $fname = false, $ignoreExpiredFiles = true, { $rc = $this->dfsbackend->existsOnDFS( $filePath ); } - + return $rc; } @@ -608,25 +683,34 @@ protected function __mkdir_p( $dir ) } /** - * Fetches the file $filePath from the database to its own name - * - * Saving $filePath locally with its original name, or $uniqueName if given - * - * @param string $filePath - * @param string $uniqueName Alternative name to save the file to - * @return string|bool the file physical path, or false if fetch failed - **/ + * Fetches the file $filePath from the database to its own name + * Saving $filePath locally with its original name, or $uniqueName if given + * + * @see eZDFSFileHandler::fileFetch + * @see eZDFSFileHandler::fetchUnique + * + * @param string $filePath + * @param bool|string $uniqueName Alternative name to save the file to + * + * @return string|bool the file physical path, or false if fetch failed + */ public function _fetch( $filePath, $uniqueName = false ) { $metaData = $this->_fetchMetadata( $filePath ); if ( !$metaData ) { // @todo Throw an exception - eZDebug::writeError( "File '$filePath' does not exist while trying to fetch.", __METHOD__ ); + self::writeError( "File '$filePath' does not exist while trying to fetch.", __METHOD__ ); return false; } $dfsFileSize = $this->dfsbackend->getDfsFileSize( $filePath ); + if ( !$dfsFileSize ) + { + // @todo Throw an exception + self::writeError( "Error getting filesize of file '$filePath'.", __METHOD__ ); + return false; + } $loopCount = 0; $localFileSize = 0; @@ -645,7 +729,7 @@ public function _fetch( $filePath, $uniqueName = false ) // @todo Throw an exception if ( !$this->dfsbackend->copyFromDFS( $filePath, $tmpFilePath ) ) { - eZDebug::writeError("Failed copying DFS://$filePath to FS://$tmpFilePath "); + self::writeError("Failed copying DFS://$filePath to FS://$tmpFilePath "); usleep( self::TIME_UNTIL_RETRY ); ++$loopCount; continue; @@ -682,10 +766,21 @@ public function _fetch( $filePath, $uniqueName = false ) while ( $dfsFileSize > $localFileSize && $loopCount < $this->maxCopyTries ); // Copy from DFS has failed :-( - eZDebug::writeError( "Size ({$localFileSize}) of written data for file '{$filePath}' does not match expected size {$metaData['size']}", __METHOD__ ); + self::writeError( "Size ({$localFileSize}) of written data for file '{$filePath}' does not match expected size {$metaData['size']}", __METHOD__ ); return false; } + /** + * Returns file contents. + * + * @see eZDFSFileHandler::fileFetchContents + * @see eZDFSFileHandler::fetchContents + * + * @param string $filePath + * @param bool|string $fname Optional caller name for debugging + * + * @return string|bool contents string, or false in case of an error. + */ public function _fetchContents( $filePath, $fname = false ) { if ( $fname ) @@ -696,23 +791,26 @@ public function _fetchContents( $filePath, $fname = false ) // @todo Throw an exception if ( !$metaData ) { - eZDebug::writeError( "File '$filePath' does not exist while trying to fetch its contents.", __METHOD__ ); + self::writeError( "File '$filePath' does not exist while trying to fetch its contents.", __METHOD__ ); return false; } // @todo Catch an exception if ( !$contents = $this->dfsbackend->getContents( $filePath ) ) { - eZDebug::writeError("An error occured while reading contents of DFS://$filePath", __METHOD__ ); + self::writeError("An error occurred while reading contents of DFS://$filePath", __METHOD__ ); return false; } return $contents; } /** - * Fetches and returns metadata for $filePath - * @return array|false file metadata, or false if the file does not exist in - * database. + * Fetches and returns metadata for $filePath + * + * @see eZDFSFileHandler::loadMetaData + * @param string $filePath + * @param bool|string $fname Optional caller name for debugging + * @return array|false file metadata, or false if the file does not exist in database. */ function _fetchMetadata( $filePath, $fname = false ) { @@ -722,10 +820,21 @@ function _fetchMetadata( $filePath, $fname = false ) $fname = "_fetchMetadata($filePath)"; $sql = "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $filePath ); return $this->_selectOneAssoc( $sql, $fname, - "Failed to retrieve file metadata: $filePath", - true ); + "Failed to retrieve file metadata: $filePath", + true ); } + /** + * Create symbolic or hard link to file. Alias of copy + * + * @see eZDFSFileHandler::fileLinkCopy + * + * @param string $srcPath Source file + * @param string $dstPath Destination file + * @param bool|string $fname Optional caller name for debugging + * + * @return mixed + */ public function _linkCopy( $srcPath, $dstPath, $fname = false ) { if ( $fname ) @@ -736,11 +845,16 @@ public function _linkCopy( $srcPath, $dstPath, $fname = false ) } /** - * Passes $filePath content through - * @param string $filePath - * @deprecated should not be used since it cannot handle reading errors - **/ - public function _passThrough( $filePath, $fname = false ) + * Passes $filePath content through + * + * @param string $filePath + * @param int $startOffset Byte offset to start download from + * @param int|bool $length Byte length to be sent + * @param bool|string $fname Optional caller name for debugging + * + * @return bool + */ + public function _passThrough( $filePath, $startOffset = 0, $length = false, $fname = false ) { if ( $fname ) $fname .= "::_passThrough($filePath)"; @@ -753,7 +867,7 @@ public function _passThrough( $filePath, $fname = false ) return false; // @todo Catch an exception - $this->dfsbackend->passthrough( $filePath ); + $this->dfsbackend->passthrough( $filePath, $startOffset, $length ); return true; } @@ -761,14 +875,18 @@ public function _passThrough( $filePath, $fname = false ) /** * Renames $srcFilePath to $dstFilePath * + * @see eZDFSFileHandler::fileMove + * @see eZDFSFileHandler::move + * * @param string $srcFilePath * @param string $dstFilePath + * * @return bool */ public function _rename( $srcFilePath, $dstFilePath ) { if ( strcmp( $srcFilePath, $dstFilePath ) == 0 ) - return; + return false; // fetch source file metadata $metaData = $this->_fetchMetadata( $srcFilePath ); @@ -779,16 +897,15 @@ public function _rename( $srcFilePath, $dstFilePath ) $this->_begin( __METHOD__ ); - $srcFilePathStr = $this->_quote( $srcFilePath ); $dstFilePathStr = $this->_quote( $dstFilePath ); $dstNameTrunkStr = $this->_quote( self::nameTrunk( $dstFilePath, $metaData['scope'] ) ); // Mark entry for update to lock it - $sql = "SELECT * FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash=MD5($srcFilePathStr) FOR UPDATE"; + $sql = "SELECT * FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash=" . $this->_md5( $srcFilePath ) . " FOR UPDATE"; if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) ) { // @todo Throw an exception - eZDebug::writeError( "Failed locking file '$srcFilePath'", __METHOD__ ); + self::writeError( "Failed locking file '$srcFilePath'", __METHOD__ ); $this->_rollback( __METHOD__ ); return false; } @@ -799,12 +916,12 @@ public function _rename( $srcFilePath, $dstFilePath ) // Create a new meta-data entry for the new file to make foreign keys happy. $sql = "INSERT INTO " . $this->dbTable( $srcFilePath ) . " ". "(name, name_trunk, name_hash, datatype, scope, size, mtime, expired) " . - "SELECT $dstFilePathStr AS name, $dstNameTrunkStr as name_trunk, MD5( $dstFilePathStr ) AS name_hash, " . + "SELECT $dstFilePathStr AS name, $dstNameTrunkStr as name_trunk, " . $this->_md5( $dstFilePath ) . " AS name_hash, " . "datatype, scope, size, mtime, expired FROM " . $this->dbTable( $srcFilePath ) . " " . - "WHERE name_hash=MD5($srcFilePathStr)"; + "WHERE name_hash=" . $this->_md5( $srcFilePath ); if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) ) { - eZDebug::writeError( "Failed making new file entry '$dstFilePath'", __METHOD__ ); + self::writeError( "Failed making new file entry '$dstFilePath'", __METHOD__ ); $this->_rollback( __METHOD__ ); // @todo Throw an exception return false; @@ -812,14 +929,14 @@ public function _rename( $srcFilePath, $dstFilePath ) if ( !$this->dfsbackend->copyFromDFSToDFS( $srcFilePath, $dstFilePath ) ) { - return $this->_fail( "Failed to copy DFS://$srcFilePath to DFS://$dstFilePath" ); + $this->_fail( "Failed to copy DFS://$srcFilePath to DFS://$dstFilePath" ); } // Remove old entry - $sql = "DELETE FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash=MD5($srcFilePathStr)"; + $sql = "DELETE FROM " . $this->dbTable( $srcFilePath ) . " WHERE name_hash=" . $this->_md5( $srcFilePath ); if ( !$this->_query( $sql, "_rename($srcFilePath, $dstFilePath)" ) ) { - eZDebug::writeError( "Failed removing old file '$srcFilePath'", __METHOD__ ); + self::writeError( "Failed removing old file '$srcFilePath'", __METHOD__ ); $this->_rollback( __METHOD__ ); // @todo Throw an exception return false; @@ -837,27 +954,29 @@ public function _rename( $srcFilePath, $dstFilePath ) /** * Stores $filePath to cluster * + * @see eZDFSFileHandler::fileStore + * * @param string $filePath * @param string $datatype * @param string $scope - * @param string $fname - * @return void + * @param bool|string $fname Optional caller name for debugging + * + * @return bool */ function _store( $filePath, $datatype, $scope, $fname = false ) { if ( !is_readable( $filePath ) ) { - eZDebug::writeError( "Unable to store file '$filePath' since it is not readable.", __METHOD__ ); - return; + self::writeError( "Unable to store file '$filePath' since it is not readable.", __METHOD__ ); + return false; } if ( $fname ) $fname .= "::_store($filePath, $datatype, $scope)"; else $fname = "_store($filePath, $datatype, $scope)"; - $return = $this->_protect( array( $this, '_storeInner' ), $fname, - $filePath, $datatype, $scope, $fname ); - return $return; + return $this->_protect( array( $this, '_storeInner' ), $fname, + $filePath, $datatype, $scope, $fname ); } /** @@ -879,24 +998,24 @@ function _storeInner( $filePath, $datatype, $scope, $fname ) $nameTrunk = self::nameTrunk( $filePath, $scope ); if ( $this->_insertUpdate( $this->dbTable( $filePath ), - array( 'datatype' => $datatype, - 'name' => $filePath, - 'name_trunk' => $nameTrunk, - 'name_hash' => $filePathHash, - 'scope' => $scope, - 'size' => $contentLength, - 'mtime' => $fileMTime, - 'expired' => ( $fileMTime < 0 ) ? 1 : 0 ), - array( 'datatype', 'scope', 'size', 'mtime', 'expired' ), - $fname ) === false ) + array( 'datatype' => $datatype, + 'name' => $filePath, + 'name_trunk' => $nameTrunk, + 'name_hash' => $filePathHash, + 'scope' => $scope, + 'size' => $contentLength, + 'mtime' => $fileMTime, + 'expired' => ( $fileMTime < 0 ) ? 1 : 0 ), + array( 'datatype', 'scope', 'size', 'mtime', 'expired' ), + $fname ) === false ) { - return $this->_fail( "Failed to insert file metadata while storing. Possible race condition" ); + $this->_fail( "Failed to insert file metadata while storing. Possible race condition" ); } // copy given $filePath to DFS if ( !$this->dfsbackend->copyToDFS( $filePath ) ) { - return $this->_fail( "Failed to copy FS://$filePath to DFS://$filePath" ); + $this->_fail( "Failed to copy FS://$filePath to DFS://$filePath" ); } return true; @@ -905,13 +1024,17 @@ function _storeInner( $filePath, $datatype, $scope, $fname ) /** * Stores $contents as the contents of $filePath to the cluster * + * @see eZDFSFileHandler::fileStore + * @see eZDFSFileHandler::storeContents + * * @param string $filePath * @param string $contents * @param string $scope * @param string $datatype - * @param int $mtime - * @param string $fname - * @return void + * @param bool|int $mtime + * @param bool|string $fname Optional caller name for debugging + * + * @return bool */ function _storeContents( $filePath, $contents, $scope, $datatype, $mtime = false, $fname = false ) { @@ -921,7 +1044,7 @@ function _storeContents( $filePath, $contents, $scope, $datatype, $mtime = false $fname = "_storeContents($filePath, ..., $scope, $datatype)"; return $this->_protect( array( $this, '_storeContentsInner' ), $fname, - $filePath, $contents, $scope, $datatype, $mtime, $fname ); + $filePath, $contents, $scope, $datatype, $mtime, $fname ); } function _storeContentsInner( $filePath, $contents, $scope, $datatype, $mtime, $fname ) @@ -932,7 +1055,6 @@ function _storeContentsInner( $filePath, $contents, $scope, $datatype, $mtime, $ $nameTrunk = self::nameTrunk( $filePath, $scope ); if ( $mtime === false ) $mtime = time(); - $expired = ( $mtime < 0 ) ? '1' : '0'; // Copy file metadata. $result = $this->_insertUpdate( @@ -950,26 +1072,43 @@ function _storeContentsInner( $filePath, $contents, $scope, $datatype, $mtime, $ ); if ( $result === false ) { - return $this->_fail( "Failed to insert file metadata while storing contents. Possible race condition", $result ); + $this->_fail( "Failed to insert file metadata while storing contents. Possible race condition", $result ); } if ( !$this->dfsbackend->createFileOnDFS( $filePath, $contents ) ) { - return $this->_fail( "Failed to open DFS://$filePath for writing" ); + $this->_fail( "Failed to open DFS://$filePath for writing" ); } return true; } - public function _getFileList( $scopes = false, $excludeScopes = false ) + /** + * Gets the list of cluster files, filtered by the optional params + * + * @see eZDFSFileHandler::getFileList + * + * @param array|bool $scopes filter by array of scopes to include in the list + * @param bool $excludeScopes if true, $scopes param acts as an exclude filter + * @param array|bool $limit limits the search to offset limit[0], limit limit[1] + * @param string|bool $path filter to include entries only including $path + * + * @return array|false the db list of entries of false if none found + */ + public function _getFileList( + $scopes = false, + $excludeScopes = false, + $limit = false, + $path = false + ) { $filePathList = array(); $tables = array_unique( array( $this->metaDataTable, $this->metaDataTableCache ) ); - + foreach ( $tables as $table ) { $query = 'SELECT name FROM ' . $table; - + if ( is_array( $scopes ) && count( $scopes ) > 0 ) { $query .= ' WHERE scope '; @@ -977,7 +1116,19 @@ public function _getFileList( $scopes = false, $excludeScopes = false ) $query .= 'NOT '; $query .= "IN ('" . implode( "', '", $scopes ) . "')"; } - + if ( $path != false && $scopes == false) + { + $query .= " WHERE name LIKE '" . $path . "%'"; + } + else if ( $path != false) + { + $query .= " AND name LIKE '" . $path . "%'"; + } + if ( $limit && array_sum($limit) ) + { + $query .= " LIMIT {$limit[0]}, {$limit[1]}"; + } + $stmt = $this->_query( $query, "_getFileList( array( " . implode( ', ', $scopes ) . " ), $excludeScopes )" ); if ( !$stmt ) { @@ -985,44 +1136,47 @@ public function _getFileList( $scopes = false, $excludeScopes = false ) // @todo Throw an exception return false; } - + $filePathList = array(); - while ( $row = $stmt->fetch( PDO::FETCH_NUM ) ) - $filePathList[] = $row[0]; - + foreach ($stmt->fetch( PDO::FETCH_NUM ) as $row) + $filePathList[] = $row; + unset( $stmt ); } return $filePathList; } /** - * Handles a DB error, displaying it as an eZDebug error - * @see eZDebug::writeError - * @param string $msg Message to display - * @param string $sql SQL query to display error for - * @return void - **/ + * Handles a DB error, displaying it as an eZDebug error + * @see self::writeError + * @param string $msg Message to display + * @param string $sql SQL query to display error for + * @return void + **/ protected function _die( $msg, $sql = null ) { if ( $this->db ) { $error = $this->db->errorInfo(); - eZDebug::writeError( $sql, "$msg: {$error[2]}" ); + self::writeError( $sql, "$msg: {$error[2]}" ); } else { - eZDebug::writeError( $sql, $msg ); + self::writeError( $sql, $msg ); } } /** - * Performs an insert of the given items in $array. - * @param string $table Name of table to execute insert on. - * @param array $array Associative array with data to insert, the keys are - * the field names and the values will be quoted - * according to type. - * @param string $fname Name of caller function (for logging purpuse) - **/ + * Performs an insert of the given items in $array. + * + * @param string $table Name of table to execute insert on. + * @param array $array Associative array with data to insert, the keys are + * the field names and the values will be quoted + * according to type. + * @param string $fname Name of caller function + * + * @return bool + */ function _insert( $table, $array, $fname ) { $keys = array_keys( $array ); @@ -1030,28 +1184,30 @@ function _insert( $table, $array, $fname ) $res = $this->_query( $query, $fname ); if ( !$res ) { - // @todo Throw an exception return false; } + return true; } /** - * Performs an insert of the given items in $insert. - * - * If entry specified already exists, fields in $update are updated with the values from $insert - * - * @param string $table Name of table to execute insert on. - * @param array $insert Associative array with data to insert, the keys - * are the field names and the values are the quoted field values - * @param string $update Array of fields that must be updated if an entry exists - * @param string $fname Name of caller function (for logging purpuse) - * @throws InvalidArgumentException when either name or name_hash aren't provided in $insert - **/ + * Performs an insert of the given items in $insert. + * + * If entry specified already exists, fields in $update are updated with the values from $insert + * + * @param string $table Name of table to execute insert on. + * @param array $insert Associative array with data to insert, the keys + * are the field names and the values are the quoted field values + * @param array $update Array of fields that must be updated if an entry exists + * @param string $fname Name of caller function + * @param bool $reportError + * + * @throws InvalidArgumentException when either name or name_hash aren't provided in $insert + */ protected function _insertUpdate( $table, $insert, $update, $fname, $reportError = true ) { if ( !isset( $insert['name'] ) || !isset( $insert['name_hash'] ) ) { - throw new InvalidArgumentException( "Insert array must contain both name and name_hash" ); + $this->_fail( "Insert array must contain both name and name_hash" ); } if ( $row = $this->_fetchMetadata( $insert['name'] ) ) @@ -1078,21 +1234,24 @@ protected function _insertUpdate( $table, $insert, $update, $fname, $reportError "VALUES( " . implode( ', ', $quotedValues ) . ")"; } - try { - $stmt = $this->_query( $sql, $fname, $reportError ); - } catch( PDOException $e ) { + try + { + $this->_query( $sql, $fname, $reportError ); + } + catch ( PDOException $e ) + { $this->_fail( "Failed insert/updating: " . $e->getMessage() ); - return false; } + return true; } /** - * Formats a list of entries as an SQL list which is separated by commas. - * Each entry in the list is quoted using _quote(). - * - * @param array $array - * @return array - **/ + * Formats a list of entries as an SQL list which is separated by commas. + * Each entry in the list is quoted using _quote(). + * + * @param array $array + * @return array + **/ protected function _sqlList( $array ) { $text = ""; @@ -1107,18 +1266,18 @@ protected function _sqlList( $array ) } /** - * Runs a select query and returns one numeric indexed row from the result - * If there are more than one row it will fail and exit, if 0 it returns - * false. - * - * @param string $query - * @param string $fname The function name that started the query, should - * contain relevant arguments in the text. - * @param string $error Sent to _error() in case of errors - * @param bool $debug If true it will display the fetched row in addition - * to the SQL. - * @return array|false - **/ + * Runs a select query and returns one numeric indexed row from the result + * If there are more than one row it will fail and exit, if 0 it returns + * false. + * + * @param string $query + * @param string $fname The function name that started the query, should + * contain relevant arguments in the text. + * @param bool|string $error Sent to _error() in case of errors + * @param bool $debug If true it will display the fetched row in addition + * to the SQL. + * @return array|false + **/ protected function _selectOneRow( $query, $fname, $error = false, $debug = false ) { return $this->_selectOne( $query, $fname, $error, $debug, PDO::FETCH_NUM ); @@ -1133,7 +1292,7 @@ protected function _selectOneRow( $query, $fname, $error = false, $debug = false * @param string $query * @param string $fname The function name that started the query, should * contain relevant arguments in the text. - * @param string $error Sent to _error() in case of errors + * @param bool|string $error Sent to _error() in case of errors * @param bool $debug If true it will display the fetched row in addition * to the SQL. * @return array|false @@ -1144,16 +1303,18 @@ protected function _selectOneAssoc( $query, $fname, $error = false, $debug = fal } /** - * Runs a select query, applying the $fetchCall callback to one result - * If there are more than one row it will fail and exit, if 0 it returns false. - * - * @param string $fname The function name that started the query, should - * contain relevant arguments in the text. - * @param string $error Sent to _error() in case of errors - * @param bool $debug If true it will display the fetched row in addition to the SQL. - * @param callback $fetchCall The callback to fetch the row. - * @return mixed - **/ + * Runs a select query, applying the $fetchCall callback to one result + * If there are more than one row it will fail and exit, if 0 it returns false. + * + * @param $query + * @param string $fname The function name that started the query, should + * contain relevant arguments in the text. + * @param bool|string $error Sent to _error() in case of errors + * @param bool $debug If true it will display the fetched row in addition to the SQL. + * @param int $fetchCall The callback to fetch the row. + * + * @return mixed + **/ protected function _selectOne( $query, $fname, $error = false, $debug = false, $fetchCall ) { eZDebug::accumulatorStart( 'postgresql_cluster_query', 'PostgreSQL Cluster', 'DB queries' ); @@ -1162,7 +1323,7 @@ protected function _selectOne( $query, $fname, $error = false, $debug = false, $ $stmt = $this->db->query( $query ); if ( !$stmt ) { - $this->_error( $query, $fname, $error ); + $this->_error( $query, $stmt, $fname, $error ); eZDebug::accumulatorStop( 'postgresql_cluster_query' ); // @todo Throw an exception return false; @@ -1171,7 +1332,7 @@ protected function _selectOne( $query, $fname, $error = false, $debug = false, $ $numRows = $stmt->rowCount(); if ( $numRows > 1 ) { - eZDebug::writeError( 'Duplicate entries found', $fname ); + self::writeError( 'Duplicate entries found', $fname ); eZDebug::accumulatorStop( 'postgresql_cluster_query' ); // @todo throw an exception instead. Should NOT happen. } @@ -1194,65 +1355,54 @@ protected function _selectOne( $query, $fname, $error = false, $debug = false, $ } /** - * Starts a new transaction by executing a BEGIN call. - * If a transaction is already started nothing is executed. - **/ - protected function _begin( $fname = false ) + * Starts a new transaction by executing a BEGIN call. + * If a transaction is already started nothing is executed. + **/ + protected function _begin() { - if ( $fname ) - $fname .= "::_begin"; - else - $fname = "_begin"; $this->transactionCount++; if ( $this->transactionCount == 1 ) $this->db->beginTransaction(); } /** - * Stops a current transaction and commits the changes by executing a COMMIT call. - * If the current transaction is a sub-transaction nothing is executed. - **/ - protected function _commit( $fname = false ) + * Stops a current transaction and commits the changes by executing a COMMIT call. + * If the current transaction is a sub-transaction nothing is executed. + **/ + protected function _commit() { - if ( $fname ) - $fname .= "::_commit"; - else - $fname = "_commit"; $this->transactionCount--; if ( $this->transactionCount == 0 ) $this->db->commit(); } /** - * Stops a current transaction and discards all changes by executing a - * ROLLBACK call. - * If the current transaction is a sub-transaction nothing is executed. - **/ - protected function _rollback( $fname = false ) + * Stops a current transaction and discards all changes by executing a + * ROLLBACK call. + * If the current transaction is a sub-transaction nothing is executed. + **/ + protected function _rollback() { - if ( $fname ) - $fname .= "::_rollback"; - else - $fname = "_rollback"; $this->transactionCount--; if ( $this->transactionCount == 0 ) $this->db->rollBack(); } /** - * Protects a custom function with SQL queries in a database transaction. - * If the function reports an error the transaction is ROLLBACKed. - * - * The first argument to the _protect() is the callback and the second is the - * name of the function (for query reporting). The remainder of arguments are - * sent to the callback. - * - * A return value of false from the callback is considered a failure, any - * other value is returned from _protect(). For extended error handling call - * _fail() and return the value. - **/ + * Protects a custom function with SQL queries in a database transaction. + * If the function reports an error the transaction is ROLLBACKed. + * + * The first argument to the _protect() is the callback and the second is the + * name of the function (for query reporting). The remainder of arguments are + * sent to the callback. + * + * A return value of false from the callback is considered a failure, any + * other value is returned from _protect(). For extended error handling call + * _fail() and return the value. + **/ protected function _protect() { + $result = false; $args = func_get_args(); $callback = array_shift( $args ); $fname = array_shift( $args ); @@ -1268,34 +1418,14 @@ protected function _protect() } catch( PDOException $e ) { - print_r( compact( 'callback', 'args' ) ); - eZDebug::writeError( $e ); + self::writeError( $e->getMessage(), __METHOD__ ); return false; } - - /*// @todo Investigate the right function - $errno = pg_result_error( $result, PGSQL_DIAG_SQLSTATE ); - if ( $errno == 1205 || // Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT) - $errno == 1213 ) // Error: 1213 SQLSTATE: 40001 (ER_LOCK_DEADLOCK) + catch( Exception $e ) { - $tries++; - $this->_rollback( $fname ); - continue; - } - - // @todo replace with an exception - if ( $result === false ) - { - $this->_rollback( $fname ); + self::writeError( $e->getMessage(), __METHOD__ ); return false; } - elseif ( $result instanceof eZMySQLBackendError ) - { - eZDebug::writeError( $result->errorValue, $result->errorText ); - $this->_rollback( $fname ); - return false; - }*/ - break; // All is good, so break out of loop } @@ -1303,39 +1433,13 @@ protected function _protect() return $result; } - protected function _handleErrorType( $res ) - { - if ( $res === false ) - { - eZDebug::writeError( "SQL failed" ); - } - elseif ( $res instanceof eZMySQLBackendError ) - { - eZDebug::writeError( $res->errorValue, $res->errorText ); - } - } - - /** - * Checks if $result is a failure type and returns true if so, false - * otherwise. - * - * A failure is either the value false or an error object of type - * eZMySQLBackendError. - **/ - protected function _isFailure( $result ) - { - if ( $result === false || ($result instanceof eZMySQLBackendError ) ) - { - return true; - } - return false; - } - /** - * Creates an error object which can be read by some backend functions. - * @param mixed $value The value which is sent to the debug system. - * @param PDOStatement $result The failed SQL result - **/ + * Creates an error object which can be read by some backend functions. + * + * @param mixed $message The value which is sent to the debug system. + * @param PDOStatement|bool $result The failed SQL result + * @throws Exception + **/ protected function _fail( $message, $result = false) { // @todo Investigate the right function @@ -1352,17 +1456,19 @@ protected function _fail( $message, $result = false) } /** - * Performs mysql query and returns mysql result. - * Times the sql execution, adds accumulator timings and reports SQL to - * debug. - * @param string $query - * @param string $fname The function name that started the query, should - * contain relevant arguments in the text. - * @return PDOStatement The resulting PDOStatement object, or false if an error occured - **/ + * Performs mysql query and returns mysql result. + * Times the sql execution, adds accumulator timings and reports SQL to + * debug. + * + * @param string $query + * @param bool|string $fname Optional caller name for debugging + * @param bool $reportError + * + * @return PDOStatement The resulting PDOStatement object, or false if an error occurred + **/ protected function _query( $query, $fname = false, $reportError = true ) { - eZDebug::accumulatorStart( 'postgresql_cluster_query', 'MySQL Cluster', 'DB queries' ); + eZDebug::accumulatorStart( 'postgresql_cluster_query', 'PostgreSQL Cluster', 'DB queries' ); $time = microtime( true ); $stmt = $this->db->query( $query ); @@ -1384,12 +1490,12 @@ protected function _query( $query, $fname = false, $reportError = true ) } /** - * Make sure that $value is escaped and qouted according to type and returned - * as a string. - * - * @param string $value a SQL parameter to escape - * @return string a string that can safely be used in SQL queries - **/ + * Make sure that $value is escaped and quoted according to type and returned + * as a string. + * + * @param string $value a SQL parameter to escape + * @return string a string that can safely be used in SQL queries + **/ protected function _quote( $value ) { if ( $value === null ) @@ -1405,25 +1511,28 @@ protected function _quote( $value ) } /** - * Provides the SQL calls to convert $value to MD5 - * The returned value can directly be put into SQLs. - **/ + * Provides the SQL calls to convert $value to MD5 + * The returned value can directly be put into SQLs. + * @param $value + * + * @return string + */ protected function _md5( $value ) { - return "MD5(" . $this->_quote( $value ) . ")"; + return $this->_quote( md5( $value ) ); } /** - * Prints error message $error to debug system. - * @param string $query The query that was attempted, will be printed if - * $error is \c false - * @param resource $res The result resource the error occured on - * @param string $fname The function name that started the query, should - * contain relevant arguments in the text. - * @param string $error The error message, if this is an array the first - * element is the value to dump and the second the error - * header (for eZDebug::writeNotice). If this is \c - * false a generic message is shown. + * Prints error message $error to debug system. + * @param string $query The query that was attempted, will be printed if + * $error is \c false + * @param PDOStatement|resource $res The result resource the error occurred on + * @param string $fname The function name that started the query, should + * contain relevant arguments in the text. + * @param string $error The error message, if this is an array the first + * element is the value to dump and the second the error + * header (for eZDebug::writeNotice). If this is \c + * false a generic message is shown. */ protected function _error( $query, $res, $fname, $error = "Failed to execute SQL for function:" ) { @@ -1438,16 +1547,18 @@ protected function _error( $query, $res, $fname, $error = "Failed to execute SQL } // @todo Investigate error methods - eZDebug::writeError( "$error\n" . pg_result_error_field( $res, PGSQL_DIAG_SQLSTATE ) . ': ' . pg_result_error_field( $res, PGSQL_DIAG_MESSAGE_PRIMARY ), $fname ); + self::writeError( "$error\n" . pg_result_error_field( $res, PGSQL_DIAG_SQLSTATE ) . ': ' . pg_result_error_field( $res, PGSQL_DIAG_MESSAGE_PRIMARY ) . ' ' .$query, $fname ); } /** - * Report SQL $query to debug system. - * - * @param string $fname The function name that started the query, should contain relevant arguments in the text. - * @param int $timeTaken Number of seconds the query + related operations took (as float). - * @param int $numRows Number of affected rows. - **/ + * Report SQL $query to debug system. + * + * @param string $query The query that was attempted, will be printed if + * $error is \c false + * @param string $fname The function name that started the query, should contain relevant arguments in the text. + * @param int $timeTaken Number of seconds the query + related operations took (as float). + * @param int|bool $numRows Number of affected rows. + **/ function _report( $query, $fname, $timeTaken, $numRows = false ) { if ( !self::$dbparams['sql_output'] ) @@ -1464,19 +1575,23 @@ function _report( $query, $fname, $timeTaken, $numRows = false ) } /** - * Attempts to begin cache generation by creating a new file named as the - * given filepath, suffixed with .generating. If the file already exists, - * insertion is not performed and false is returned (means that the file - * is already being generated) - * @param string $filePath - * @return array array with 2 indexes: 'result', containing either ok or ko, - * and another index that depends on the result: - * - if result == 'ok', the 'mtime' index contains the generating - * file's mtime - * - if result == 'ko', the 'remaining' index contains the remaining - * generation time (time until timeout) in seconds - * @throws RuntimeException - **/ + * Attempts to begin cache generation by creating a new file named as the + * given filepath, suffixed with .generating. If the file already exists, + * insertion is not performed and false is returned (means that the file + * is already being generated) + * + * @see eZDFSFileHandler::startCacheGeneration + * + * @param string $filePath + * @param string $generatingFilePath + * + * @return array array with 2 indexes: 'result', containing either ok or ko, + * and another index that depends on the result: + * - if result == 'ok', the 'mtime' index contains the generating + * file's mtime + * - if result == 'ko', the 'remaining' index contains the remaining + * generation time (time until timeout) in seconds + */ public function _startCacheGeneration( $filePath, $generatingFilePath ) { $fname = "_startCacheGeneration( {$filePath} )"; @@ -1494,9 +1609,15 @@ public function _startCacheGeneration( $filePath, $generatingFilePath ) $query = 'INSERT INTO ' . $this->dbTable( $filePath ) . ' ( '. implode(', ', array_keys( $insertData ) ) . ' ) ' . "VALUES(" . implode( ', ', $insertData ) . ")"; - try { + //per testare scommenta la riga 1503 e sposta righe 1516-1548 fuori dal catch @todo + //$query .= " WHERE NOT EXISTS ( SELECT name_hash FROM ' . $this->dbTable( $filePath ) . ' WHERE name_hash = {$nameHash} );"; + + try + { $stmt = $this->_query( $query, "_startCacheGeneration( $filePath )", false ); - } catch( PDOException $e ) { + } + catch( PDOException $e ) + { $errno = $e->getCode(); if ( $errno != self::ERROR_UNIQUE_VIOLATION ) { @@ -1505,7 +1626,7 @@ public function _startCacheGeneration( $filePath, $generatingFilePath ) // error self::ERROR_UNIQUE_VIOLATION is expected, since it means duplicate key (file is being generated) else { - // generation timout check + // generation timeout check $query = "SELECT mtime FROM " . $this->dbTable( $filePath ) . " WHERE name_hash = {$nameHash}"; $row = $this->_selectOneRow( $query, $fname, false, false ); @@ -1518,7 +1639,7 @@ public function _startCacheGeneration( $filePath, $generatingFilePath ) { $previousMTime = $row[0]; - eZDebugSetting::writeDebug( 'kernel-clustering', "$filePath generation has timedout, taking over", __METHOD__ ); + eZDebugSetting::writeDebug( 'kernel-clustering', "$filePath generation has timed out, taking over", __METHOD__ ); $updateQuery = "UPDATE " . $this->dbTable( $filePath ) . " SET mtime = {$mtime} WHERE name_hash = {$nameHash} AND mtime = {$previousMTime}"; // we run the query manually since the default _query won't @@ -1530,8 +1651,8 @@ public function _startCacheGeneration( $filePath, $generatingFilePath ) } else { - throw new RuntimeException( "An error occured taking over timedout generating cache file $generatingFilePath" ); - return array( 'result' => 'error' ); + throw new RuntimeException( "An error occurred taking over timed out generating cache file $generatingFilePath" ); + //return array( 'result' => 'error' ); } } else @@ -1545,12 +1666,19 @@ public function _startCacheGeneration( $filePath, $generatingFilePath ) } /** - * Ends the cache generation for the current file: moves the (meta)data for - * the .generating file to the actual file, and removed the .generating - * @param string $filePath - * @return bool - * @throws RuntimeException - **/ + * Ends the cache generation for the current file: moves the (meta)data for + * the .generating file to the actual file, and removed the .generating + * + * @see eZDFSFileHandler::endCacheGeneration + * + * @param string $filePath + * @param string $generatingFilePath + * @param bool $rename if false the .generating entry is just deleted + * + * @return bool true + * + * @throw RuntimeException + */ public function _endCacheGeneration( $filePath, $generatingFilePath, $rename ) { $fname = "_endCacheGeneration( $filePath )"; @@ -1558,7 +1686,7 @@ public function _endCacheGeneration( $filePath, $generatingFilePath, $rename ) // no rename: the .generating entry is just deleted if ( $rename === false ) { - $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=MD5('$generatingFilePath')", $fname, true ); + $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $generatingFilePath ), $fname, true ); $this->dfsbackend->delete( $generatingFilePath ); return true; } @@ -1569,15 +1697,15 @@ public function _endCacheGeneration( $filePath, $generatingFilePath, $rename ) $this->_begin( $fname ); // both files are locked for update - if ( !$stmt = $this->_query( "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=MD5('$generatingFilePath') FOR UPDATE", $fname, true ) ) + if ( !$stmt = $this->_query( "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $generatingFilePath ) . " FOR UPDATE", $fname, true ) ) { $this->_rollback( $fname ); - throw new RuntimeException( "An error occcured getting a lock on $generatingFilePath" ); + throw new RuntimeException( "An error occurred getting a lock on $generatingFilePath" ); } $generatingMetaData = $stmt->fetch( PDO::FETCH_ASSOC ); // the original file does not exist: we move the generating file - $stmt = $this->_query( "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=MD5('$filePath') FOR UPDATE", $fname, false ); + $stmt = $this->_query( "SELECT * FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $filePath ) . " FOR UPDATE", $fname, false ); if ( $stmt->rowCount() == 0 ) { $metaData = $generatingMetaData; @@ -1589,16 +1717,16 @@ public function _endCacheGeneration( $filePath, $generatingFilePath, $rename ) if ( !$this->_query( $insertSQL, $fname, true ) ) { $this->_rollback( $fname ); - throw new RuntimeException( "An error occured creating the metadata entry for $filePath" ); + throw new RuntimeException( "An error occurred creating the metadata entry for $filePath" ); } // here we rename the actual FILE. The .generating file has been // created on DFS, and should be renamed if ( !$this->dfsbackend->renameOnDFS( $generatingFilePath, $filePath ) ) { $this->_rollback( $fname ); - throw new RuntimeException("An error occured renaming DFS://$generatingFilePath to DFS://$filePath" ); + throw new RuntimeException("An error occurred renaming DFS://$generatingFilePath to DFS://$filePath" ); } - $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=MD5('$generatingFilePath')", $fname, true ); + $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $generatingFilePath ), $fname, true ); } // the original file exists: we move the generating data to this file // and update it @@ -1607,17 +1735,17 @@ public function _endCacheGeneration( $filePath, $generatingFilePath, $rename ) if ( !$this->dfsbackend->renameOnDFS( $generatingFilePath, $filePath ) ) { $this->_rollback( $fname ); - throw new RuntimeException( "An error occured renaming DFS://$generatingFilePath to DFS://$filePath" ); + throw new RuntimeException( "An error occurred renaming DFS://$generatingFilePath to DFS://$filePath" ); } $mtime = $generatingMetaData['mtime']; $filesize = $generatingMetaData['size']; - if ( !$this->_query( "UPDATE " . $this->dbTable( $filePath ) . " SET mtime = '{$mtime}', expired = 0, size = '{$filesize}' WHERE name_hash=MD5('$filePath')", $fname, true ) ) + if ( !$this->_query( "UPDATE " . $this->dbTable( $filePath ) . " SET mtime = '{$mtime}', expired = 0, size = '{$filesize}' WHERE name_hash=" . $this->_md5( $filePath ), $fname, true ) ) { $this->_rollback( $fname ); throw new RuntimeException( "An error marking '$filePath' as not expired in the database" ); } - $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=MD5('$generatingFilePath')", $fname, true ); + $this->_query( "DELETE FROM " . $this->dbTable( $filePath ) . " WHERE name_hash=" . $this->_md5( $generatingFilePath ), $fname, true ); } $this->_commit( $fname ); @@ -1627,20 +1755,21 @@ public function _endCacheGeneration( $filePath, $generatingFilePath, $rename ) } /** - * Checks if generation has timed out by looking for the .generating file - * and comparing its timestamp to the one assigned when the file was created - * - * @param string $generatingFilePath - * @param int $generatingFileMtime - * - * @return bool true if the file didn't timeout, false otherwise - **/ + * Checks if generation has timed out by looking for the .generating file + * and comparing its timestamp to the one assigned when the file was created + * + * @param string $generatingFilePath + * @param int $generatingFileMtime + * + * @return bool true if the file didn't timeout, false otherwise + **/ public function _checkCacheGenerationTimeout( $generatingFilePath, $generatingFileMtime ) { + $generatingFileMtime = intval($generatingFileMtime); $fname = "_checkCacheGenerationTimeout( $generatingFilePath, $generatingFileMtime )"; // reporting - eZDebug::accumulatorStart( 'postgresql_cluster_query', 'MySQL Cluster', 'DB queries' ); + eZDebug::accumulatorStart( 'postgresql_cluster_query', 'PostgreSQL Cluster', 'DB queries' ); $time = microtime( true ); $nameHash = $this->_md5( $generatingFilePath ); @@ -1652,7 +1781,7 @@ public function _checkCacheGenerationTimeout( $generatingFilePath, $generatingFi if ( !$stmt ) { // @todo Throw an exception - $this->_error( $query, $fname ); + $this->_error( $query, $stmt, $fname ); return false; } $numRows = $stmt->rowCount(); @@ -1670,11 +1799,10 @@ public function _checkCacheGenerationTimeout( $generatingFilePath, $generatingFi $query = "SELECT mtime FROM " . $this->dbTable( $generatingFilePath ) . " WHERE name_hash = {$nameHash}"; $stmt = $this->db->query( $query ); $row = $stmt->fetch( PDO::FETCH_NUM ); - if ( isset( $row[0] ) and $row[0] == $generatingFileMtime ); + if ( isset( $row[0] ) && $row[0] == $generatingFileMtime ) { return true; } - // @todo Check if an exception makes sense here return false; } @@ -1691,11 +1819,14 @@ public function _checkCacheGenerationTimeout( $generatingFilePath, $generatingFi } /** - * Aborts the cache generation process by removing the .generating file - * @param string $filePath Real cache file path - * @param string $generatingFilePath .generating cache file path - * @return void - **/ + * Aborts the cache generation process by removing the .generating file + * + * @see eZDFSFileHandler::abortCacheGeneration + * + * @param string $generatingFilePath .generating cache file path + * + * @return void + */ public function _abortCacheGeneration( $generatingFilePath ) { $fname = "_abortCacheGeneration( $generatingFilePath )"; @@ -1710,11 +1841,11 @@ public function _abortCacheGeneration( $generatingFilePath ) } /** - * Returns the name_trunk for a file path - * @param string $filePath - * @param string $scope - * @return string - **/ + * Returns the name_trunk for a file path + * @param string $filePath + * @param string $scope + * @return string + **/ static protected function nameTrunk( $filePath, $scope ) { switch ( $scope ) @@ -1749,13 +1880,13 @@ static protected function nameTrunk( $filePath, $scope ) } /** - * Returns the remaining time, in seconds, before the generating file times - * out - * - * @param resource $fileRow - * - * @return int Remaining generation seconds. A negative value indicates a timeout. - **/ + * Returns the remaining time, in seconds, before the generating file times + * out + * + * @param array $row + * + * @return int Remaining generation seconds. A negative value indicates a timeout. + **/ protected function remainingCacheGenerationTime( $row ) { if( !isset( $row[0] ) ) @@ -1767,24 +1898,27 @@ protected function remainingCacheGenerationTime( $row ) /** * Returns the list of expired files * + * @see eZDFSFileHandler::fetchExpiredItems + * * @param array $scopes Array of scopes to consider. At least one. - * @param int $limit Max number of items. Set to false for unlimited. + * @param array|bool $limit Max number of items. Set to false for unlimited. + * @param int|bool $expiry Number of seconds, only items older than this will be returned. * * @return array(filepath) * * @since 4.3 */ - public function expiredFilesList( $scopes, $limit = array( 0, 100 ) ) + public function expiredFilesList( $scopes, $limit = array( 0, 100 ), $expiry = false ) { $tables = array( $this->metaDataTable, $this->metaDataTableCache ); - - if ( count( $scopes ) == 0 ) + + if ( count( $scopes ) == 0 || $scopes == false ) throw new ezcBaseValueException( 'scopes', $scopes, "array of scopes", "parameter" ); $scopeString = $this->_sqlList( $scopes ); - + $filePathList = array(); - + foreach ( $tables as $table) { $query = "SELECT name FROM " . $table . " WHERE expired = 1 AND scope IN( $scopeString )"; @@ -1793,7 +1927,6 @@ public function expiredFilesList( $scopes, $limit = array( 0, 100 ) ) $query .= " LIMIT {$limit[1]} OFFSET {$limit[0]}"; } $stmt = $this->_query( $query, __METHOD__ ); - $filePathList = array(); while ( $row = $stmt->fetch( PDO::FETCH_NUM ) ) $filePathList[] = $row[0]; unset( $stmt ); @@ -1801,12 +1934,21 @@ public function expiredFilesList( $scopes, $limit = array( 0, 100 ) ) return $filePathList; } - + + /** + * Transforms $filePath so that it contains a valid href to the file, wherever it is stored. + * + * @see eZDFSFileHandler::applyServerUri + * + * @param string $filePath + * + * @return string + */ public function applyServerUri( $filePath ) { return $this->dfsbackend->applyServerUri( $filePath ); } - + /** * Deletes a batch of cache files from the storage table. * @@ -1827,7 +1969,7 @@ public function deleteCacheFiles( $limit ) $like = addcslashes( eZSys::cacheDirectory(), '_' ) . DIRECTORY_SEPARATOR . '%'; $query = "DELETE FROM {$this->metaDataTable} WHERE name LIKE '$like' LIMIT $limit"; - if ( !$stmt = $this->_query( $sql ) ) + if ( !$stmt = $this->_query( $query ) ) { throw new RuntimeException( "Error in $query" ); } @@ -1839,7 +1981,7 @@ public function deleteCacheFiles( $limit ) /** * DB connexion handle - * @var handle + * @var PDO|resource */ public $db = null; @@ -1858,7 +2000,7 @@ public function deleteCacheFiles( $limit ) /** * Current transaction level. * Will be used to decide wether we can BEGIN (if it's the first BEGIN call) - * or COMMIT (if we're commiting the last running transaction + * or COMMIT (if we're committing the last running transaction * @var int */ protected $transactionCount = 0; @@ -1907,4 +2049,4 @@ public function deleteCacheFiles( $limit ) * @var int */ const ERROR_UNIQUE_VIOLATION = 23505; -} \ No newline at end of file +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8decb2c --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "opencontent/ezpostgresqlcluster-ls", + "description": "PostgreSQL Cluster", + "type": "ezpublish-legacy-extension", + "license": "GPL-2.0", + "minimum-stability": "dev", + "require": { + "ezsystems/ezpublish-legacy-installer": "*" + }, + "extra": { + "ezpublish-legacy-extension-name": "ezpostgresqlcluster" + } +} diff --git a/sql/postgresql/cluster_dfs_schema.sql b/sql/postgresql/cluster_dfs_schema.sql index aabdca4..46cc92e 100644 --- a/sql/postgresql/cluster_dfs_schema.sql +++ b/sql/postgresql/cluster_dfs_schema.sql @@ -25,3 +25,4 @@ CREATE TABLE ezdfsfile_cache ( CREATE INDEX ezdfsfile_cache_name ON ezdfsfile_cache ( name ); CREATE INDEX ezdfsfile_cache_mtime ON ezdfsfile_cache ( mtime ); +