|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the eZDFSFileHandler class. 00004 * 00005 * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved. 00006 * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 00007 * @version //autogentag// 00008 * @package kernel 00009 */ 00010 00011 /** 00012 * Handles file operations for Distributed File Systems (f.e. NFS) 00013 * 00014 * Uses a dual DB / FS approach: 00015 * - files metadata are DB based 00016 * - files data are read/written to a local mount point (outside var/) 00017 * - actual files are locally written, exactly like the DB handler does 00018 * 00019 * Glossary of terms used in the internal doc: 00020 * - DFS: Distributed File System. Local NFS mount point 00021 * - DB: MetaData database 00022 * - LFS: Local file system (var) 00023 * 00024 * @since 4.2.0 00025 */ 00026 class eZDFSFileHandler implements eZClusterFileHandlerInterface, ezpDatabaseBasedClusterFileHandler 00027 { 00028 /** 00029 * Controls whether file data from database is cached on the local filesystem. 00030 * @note This is primarily available for debugging purposes. 00031 * @var int 00032 */ 00033 const LOCAL_CACHE = 1; 00034 00035 /** 00036 * Controls the maximum number of metdata entries to keep in memory for this request. 00037 * If the limit is reached the least used entries are removed. 00038 * @var int 00039 */ 00040 const INFOCACHE_MAX = 200; 00041 00042 /** 00043 * Constructor 00044 * 00045 * If provided with $filePath, will use this file for further operations. 00046 * If not given, the file* methods must be used instead 00047 * 00048 * @param string $filePath Path of the file to handle 00049 * 00050 * @throws eZDBNoConnectionException DB connection failed 00051 */ 00052 function __construct( $filePath = false ) 00053 { 00054 if ( self::$nonExistantStaleCacheHandling === null ) 00055 { 00056 $fileINI = eZINI::instance( 'file.ini' ); 00057 self::$nonExistantStaleCacheHandling = $fileINI->variable( "ClusteringSettings", "NonExistantStaleCacheHandling" ); 00058 unset( $fileINI ); 00059 } 00060 00061 // DB Backend init 00062 if ( self::$dbbackend === null ) 00063 { 00064 self::$dbbackend = eZExtension::getHandlerClass( 00065 new ezpExtensionOptions( 00066 array( 'iniFile' => 'file.ini', 00067 'iniSection' => 'eZDFSClusteringSettings', 00068 'iniVariable' => 'DBBackend' ) ) ); 00069 self::$dbbackend->_connect( false ); 00070 00071 $fileINI = eZINI::instance( 'file.ini' ); 00072 if ( 00073 $fileINI->variable( 'ClusterEventsSettings', 'ClusterEvents' ) === 'enabled' 00074 && self::$dbbackend instanceof eZClusterEventNotifier 00075 ) 00076 { 00077 $listener = eZExtension::getHandlerClass( 00078 new ezpExtensionOptions( 00079 array( 00080 'iniFile' => 'file.ini', 00081 'iniSection' => 'ClusterEventsSettings', 00082 'iniVariable' => 'Listener', 00083 'handlerParams' => array( new eZClusterEventLoggerEzdebug() ) 00084 ) 00085 ) 00086 ); 00087 00088 if ( $listener instanceof eZClusterEventListener ) 00089 { 00090 self::$dbbackend->registerListener( $listener ); 00091 $listener->initialize(); 00092 } 00093 } 00094 } 00095 00096 if ( $filePath !== false ) 00097 { 00098 $filePath = eZDBFileHandler::cleanPath( $filePath ); 00099 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::ctor( '$filePath' )" ); 00100 } 00101 else 00102 { 00103 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::ctor()" ); 00104 } 00105 00106 $this->filePath = $filePath; 00107 } 00108 00109 /** 00110 * Disconnects the cluster handler from the database 00111 */ 00112 public function disconnect() 00113 { 00114 if ( self::$dbbackend !== null ) 00115 { 00116 self::$dbbackend->_disconnect(); 00117 self::$dbbackend= null; 00118 } 00119 } 00120 00121 /** 00122 * Loads file meta information. 00123 * 00124 * @param bool $force File stats will be refreshed if true 00125 * @return void 00126 */ 00127 public function loadMetaData( $force = false ) 00128 { 00129 // Fetch metadata. 00130 if ( $this->filePath === false ) 00131 return; 00132 00133 if ( $force && isset( $GLOBALS['eZClusterInfo'][$this->filePath] ) ) 00134 unset( $GLOBALS['eZClusterInfo'][$this->filePath] ); 00135 00136 // Checks for metadata stored in memory, useful for repeated access 00137 // to the same file in one request 00138 // TODO: On PHP5 turn into static member 00139 if ( isset( $GLOBALS['eZClusterInfo'][$this->filePath] ) ) 00140 { 00141 $GLOBALS['eZClusterInfo'][$this->filePath]['cnt'] += 1; 00142 $this->_metaData = $GLOBALS['eZClusterInfo'][$this->filePath]['data']; 00143 return; 00144 } 00145 00146 $metaData = self::$dbbackend->_fetchMetadata( $this->filePath ); 00147 if ( $metaData ) 00148 $this->_metaData = $metaData; 00149 else 00150 $this->_metaData = false; 00151 00152 // Clean up old entries if the maximum count is reached 00153 if ( isset( $GLOBALS['eZClusterInfo'] ) && 00154 count( $GLOBALS['eZClusterInfo'] ) >= self::INFOCACHE_MAX ) 00155 { 00156 usort( $GLOBALS['eZClusterInfo'], 00157 create_function( '$a, $b', 00158 '$a=$a["cnt"]; $b=$b["cnt"]; if ( $a > $b ) return -1; else if ( $a == $b ) return 0; else return 1;' ) ); 00159 array_pop( $GLOBALS['eZClusterInfo'] ); 00160 } 00161 $GLOBALS['eZClusterInfo'][$this->filePath] = array( 'cnt' => 1, 00162 'data' => $metaData ); 00163 } 00164 00165 /** 00166 * Stores a file by path to the backend 00167 * 00168 * @param string $filePath Path to the file being stored. 00169 * @param string $scope Means something like "file category". May be used 00170 * to clean caches of a certain type. 00171 * @param bool $delete true if the file should be deleted after storing. 00172 * @param string $datatype 00173 * 00174 * @return void 00175 */ 00176 public function fileStore( $filePath, $scope = false, $delete = false, $datatype = false ) 00177 { 00178 $filePath = eZDBFileHandler::cleanPath( $filePath ); 00179 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileStore( '$filePath' )" ); 00180 00181 if ( $scope === false ) 00182 $scope = 'UNKNOWN_SCOPE'; 00183 00184 if ( $datatype === false ) 00185 $datatype = 'misc'; 00186 00187 self::$dbbackend->_store( $filePath, $datatype, $scope ); 00188 00189 if ( $delete ) 00190 @unlink( $filePath ); 00191 } 00192 00193 /** 00194 * 00195 * Store file contents. 00196 * 00197 * @param string $filePath Path to the file being stored. 00198 * @param string $contents Binary file content 00199 * @param string $scope "file category". May be used by cache management 00200 * @param string $datatype Datatype for the file. Also used to clean cache up 00201 * 00202 * @return void 00203 */ 00204 public function fileStoreContents( $filePath, $contents, $scope = false, $datatype = false ) 00205 { 00206 $filePath = eZDBFileHandler::cleanPath( $filePath ); 00207 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileStoreContents( '$filePath' )" ); 00208 00209 if ( $scope === false ) 00210 $scope = 'UNKNOWN_SCOPE'; 00211 00212 if ( $datatype === false ) 00213 $datatype = 'misc'; 00214 00215 // the file is stored with the current time as mtime 00216 self::$dbbackend->_storeContents( $filePath, $contents, $scope, $datatype ); 00217 } 00218 00219 /** 00220 * Store file contents using binary data 00221 * 00222 * @param string $contents Binary file content 00223 * @param string $scope "file category". May be used by cache management 00224 * @param string $datatype Datatype for the file. Also used to clean cache up 00225 * @param bool $storeLocally If true the file will also be stored on the 00226 * local file system. 00227 */ 00228 public function storeContents( $contents, $scope = false, $datatype = false, $storeLocally = false ) 00229 { 00230 if ( $scope === false ) 00231 $scope = 'UNKNOWN_SCOPE'; 00232 00233 if ( $datatype === false ) 00234 $datatype = 'misc'; 00235 00236 $filePath = $this->filePath; 00237 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::storeContents( '$filePath' )" ); 00238 00239 $mtime = time(); 00240 00241 // the file is stored with the current time as mtime 00242 self::$dbbackend->_storeContents( $filePath, $contents, $scope, $datatype ); 00243 00244 if ( $storeLocally ) 00245 { 00246 eZFile::create( basename( $filePath ), dirname( $filePath ), $contents, true ); 00247 } 00248 } 00249 00250 /** 00251 * Fetches file from DFS and saves it in LFS under the same name. 00252 * 00253 * @param string $filePath 00254 * 00255 * @return string|false the file path, or false if fetching failed 00256 */ 00257 function fileFetch( $filePath ) 00258 { 00259 $filePath = eZDBFileHandler::cleanPath( $filePath ); 00260 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileFetch( '$filePath' )" ); 00261 00262 return self::$dbbackend->_fetch( $filePath ); 00263 } 00264 00265 /*public function fileFetch( $filePath ) 00266 { 00267 $filePath = eZDBFileHandler::cleanPath( $filePath ); 00268 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileFetch( '$filePath' )" ); 00269 00270 switch ( self::$dbbackend->_prepareFetch( $filePath ) ) 00271 { 00272 // READ_STATUS_OK: SLOCK granted, DFS read OK 00273 case self::READ_STATUS_OK: 00274 { 00275 // copy file from DFS to FS 00276 00277 // release the SLOCK on the file 00278 self::$dbbackend->_endFetch( $filePath ); 00279 } break; 00280 00281 // READ_STATUS_STALE: SLOCK granted, local version is prefered 00282 case self::READ_STATUS_STALE: 00283 { 00284 00285 } 00286 } 00287 }*/ 00288 00289 /** 00290 * Fetches file from db and saves it in FS under a unique name 00291 * 00292 * @return string filename with path of a saved file. You can use this 00293 * filename to get contents of file from filesystem. 00294 */ 00295 public function fetchUnique() 00296 { 00297 $filePath = $this->filePath; 00298 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fetchUnique( '$filePath' )" ); 00299 00300 $fetchedFilePath = self::$dbbackend->_fetch( $filePath, true ); 00301 $this->uniqueName = $fetchedFilePath; 00302 return $fetchedFilePath; 00303 } 00304 00305 /** 00306 * Fetches file from DFS and saves it in FS under the same name. 00307 * @param bool $noLocalCache 00308 */ 00309 function fetch( $noLocalCache = false ) 00310 { 00311 return $this->fileFetch( $this->filePath ); 00312 } 00313 00314 /** 00315 * Returns file contents. 00316 * @return bool|string contents string, or false in case of an error. 00317 */ 00318 function fileFetchContents( $filePath ) 00319 { 00320 $filePath = eZDBFileHandler::cleanPath( $filePath ); 00321 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileFetchContents( '$filePath' )" ); 00322 00323 $contents = self::$dbbackend->_fetchContents( $filePath ); 00324 return $contents; 00325 } 00326 00327 /** 00328 * Returns file contents. 00329 * @return string|bool contents string, or false in case of an error. 00330 */ 00331 function fetchContents() 00332 { 00333 $filePath = $this->filePath; 00334 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileFetchContents( '$filePath' )" ); 00335 $contents = self::$dbbackend->_fetchContents( $filePath ); 00336 return $contents; 00337 } 00338 00339 /** 00340 * Handles cache requests / write operations 00341 * 00342 * Creates a single transaction out of the typical file operations for 00343 * accessing caches. Caches are normally ready from the database or local 00344 * file, if the entry does not exist or is expired then it generates the new 00345 * cache data and stores it. This method takes care of these operations and 00346 * handles the custom code by performing callbacks when needed. 00347 * 00348 * The $retrieveCallback is used when the file contents can be used (ie. not 00349 * re-generation) and is called when the file is ready locally. 00350 * The function will be called with the file path as the first parameter, the 00351 * mtime as the second and optionally $extraData as the third. 00352 * The function must return the file contents or an instance of 00353 * eZClusterFileFailure which can be used to tell the system that the 00354 * retrieve data cannot be used after all. 00355 * 00356 * $retrieveCallback can be set to null which makes the system go directly 00357 * to the generation. 00358 * 00359 * The $generateCallback is used when the file content is expired or does not 00360 * exist, in this case the content must be re-generated and stored. The 00361 * function will be called with the file path as the first parameter and 00362 * optionally $extraData as the second. 00363 * The function must return an array with information on the contents, the 00364 * array consists of: 00365 * - scope - The current scope of the file, is optional. 00366 * - datatype - The current datatype of the file, is optional. 00367 * - content - The file content, this can be any type except null. 00368 * - binarydata - The binary data which is written to the file. 00369 * - store - Whether *content* or *binarydata* should be stored to the 00370 * file, if false it will simply return the data. Optional, 00371 * by default it is true. 00372 * Note: Set $generateCallback to false to disable generation callback. 00373 * Note: Set $generateCallback to null to tell the function to perform a 00374 * write lock but not do any generation, the generation must done be 00375 * done by the caller by calling @see storeCache(). 00376 * 00377 * Either *content* or *binarydata* must be supplied, if not an error is 00378 * issued and it returns null. 00379 * 00380 * If *content* is set it will be used as the return value of this function, 00381 * if not it will return the binary data. 00382 * If *binarydata* is set it will be used as the binary data for the file, if 00383 * not it will perform a var_export on *content* and use that as the binary 00384 * data. 00385 * 00386 * For convenience the $generateCallback function can return a string which 00387 * will be considered as the binary data for the file and returned as the 00388 * content. 00389 * 00390 * For controlling how long a cache entry can be used the parameters 00391 * @see $expiry and @see $ttl is used. 00392 * @see $expiry can be set to a timestamp which controls the absolute max 00393 * time for the cache, after this time/date the cache will never be used. 00394 * If the value is set to a negative value or null there the expiration check 00395 * is disabled. 00396 * 00397 * $ttl (time to live) tells how many seconds the cache can live from the 00398 * time it was stored. If the value is set to negative or null there is no 00399 * limit for the lifetime of the cache. A value of 0 means that the cache 00400 * will always expire and practically disables caching. For the cache to be 00401 * used both the $expiry and $ttl check must hold. 00402 * 00403 * @todo Reformat the doc so that it's readable 00404 */ 00405 function processCache( $retrieveCallback, $generateCallback = null, $ttl = null, $expiry = null, $extraData = null ) 00406 { 00407 $forceDB = false; 00408 $timestamp = null; 00409 $curtime = time(); 00410 $tries = 0; 00411 $noCache = false; 00412 00413 if ( $expiry < 0 ) 00414 $expiry = null; 00415 if ( $ttl < 0 ) 00416 $ttl = null; 00417 00418 // Main loop 00419 while ( true ) 00420 { 00421 // Start read checks 00422 // Note: The while loop is used to make it easier to break out of the "read" code 00423 while ( true ) 00424 { 00425 // No retrieve method so go directly to generate+store 00426 if ( $retrieveCallback === null || !$this->filePath ) 00427 break; 00428 00429 if ( !self::LOCAL_CACHE ) 00430 { 00431 $forceDB = true; 00432 } 00433 else 00434 { 00435 if ( $this->isLocalFileExpired( $expiry, $curtime, $ttl ) ) 00436 { 00437 // if we are in stale cache mode, we only forceDB if the 00438 // file does not exist at all 00439 if ( $this->useStaleCache ) 00440 { 00441 if ( !file_exists( $this->filePath ) ) 00442 { 00443 eZDebugSetting::writeDebug( 'kernel-clustering', "Local file '{$this->filePath}' does not exist and can not be used for stale cache. Checking with DB", __METHOD__ ); 00444 $forceDB = true; 00445 00446 // forceDB + useStaleCache means that we should check for the DB file. 00447 } 00448 } 00449 else 00450 { 00451 // Local file is older than global timestamp, check with DB 00452 eZDebugSetting::writeDebug( 'kernel-clustering', "Local file (mtime=" . @filemtime( $this->filePath ) . ") is older than timestamp ($expiry) and ttl($ttl), check with DB", __METHOD__ ); 00453 $forceDB = true; 00454 } 00455 } 00456 } 00457 00458 if ( !$forceDB ) 00459 { 00460 // check if DB file is deleted 00461 if ( !$this->useStaleCache && ( $this->metaData === false || $this->metaData['mtime'] < 0 ) ) 00462 { 00463 if ( $generateCallback !== false ) 00464 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, need to regenerate data", __METHOD__ ); 00465 else 00466 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, cannot get data", __METHOD__ ); 00467 break; 00468 } 00469 00470 // check if FS file is older than DB file 00471 if ( !$this->useStaleCache && $this->isLocalFileExpired( $this->metaData['mtime'], $curtime, $ttl ) ) 00472 { 00473 eZDebugSetting::writeDebug( 'kernel-clustering', "Local file (mtime=" . @filemtime( $this->filePath ) . ") is older than DB, checking with DB", __METHOD__ ); 00474 $forceDB = true; 00475 } 00476 else 00477 { 00478 if ( $this->useStaleCache ) 00479 { 00480 // to get the retrieve callback to accept the cache file, 00481 // we force its mtime to the current time 00482 $mtime = $curtime; 00483 eZDebugSetting::writeDebug( 'kernel-clustering', "Processing local stale cache file {$this->filePath}", __METHOD__ ); 00484 } 00485 else 00486 { 00487 $mtime = filemtime( $this->filePath ); 00488 eZDebugSetting::writeDebug( 'kernel-clustering', "Processing local cache file {$this->filePath}", __METHOD__ ); 00489 } 00490 00491 $args = array( $this->filePath, $mtime ); 00492 if ( $extraData !== null ) 00493 $args[] = $extraData; 00494 $retval = call_user_func_array( $retrieveCallback, $args ); 00495 if ( $retval instanceof eZClusterFileFailure ) 00496 { 00497 break; 00498 } 00499 return $retval; 00500 } 00501 } 00502 00503 if ( $forceDB ) 00504 { 00505 // stale cache, and no DB or FS file available 00506 if ( $this->useStaleCache && $this->metaData === false ) 00507 { 00508 // configuration says we have to generate our own version 00509 if ( $this->nonExistantStaleCacheHandling[ $this->cacheType ] == 'generate' ) 00510 { 00511 // no cache available, but a generate callback exists, skip to generation 00512 if ( $generateCallback !== false ) 00513 { 00514 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, need to regenerate data" ); 00515 break; 00516 } 00517 // if no generate callback exists, we can directly skip the main loop 00518 else 00519 { 00520 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, cannot get data" ); 00521 break 2; 00522 } 00523 } 00524 // wait for the generating process to be finished (or timedout) 00525 else 00526 { 00527 while ( $this->remainingCacheGenerationTime-- >= 0 ) 00528 { 00529 // we don't know if the file gets generated on the current 00530 // frontend or not. However, we can still try the FS cache 00531 // first, then the DB cache if FS is not found, since this 00532 // will be much more efficient 00533 if ( !file_exists( $this->filePath ) ) 00534 { 00535 $this->loadMetaData( true ); 00536 if ( $this->metaData === false ) 00537 { 00538 sleep( 1 ); 00539 continue; 00540 } 00541 else 00542 { 00543 break; 00544 } 00545 } 00546 else 00547 { 00548 break; 00549 } 00550 } 00551 00552 // if we reached this point, it means that we are over the estimated timeout value 00553 // we try to take the generation over by starting the cache generation. IF this 00554 // fails again, it is probably because another waiting process has taken the generation 00555 // over. Maybe add a counter here to prevent some kind of death loop ? 00556 eZDebugSetting::writeDebug( 'kernel-clustering', "Checking if {$this->filePath} was generating during the wait loop", __METHOD__ ); 00557 $this->loadMetaData( true ); 00558 $this->useStaleCache = false; 00559 $this->remainingCacheGenerationTime = false; 00560 $forceDB = false; 00561 00562 // this continues to the main loop 'while (true)' 00563 continue 2; 00564 } 00565 } 00566 // no stale cache, and expired DB file 00567 elseif ( !$this->useStaleCache && ( $this->metaData === false || $this->isDBFileExpired( $expiry, $curtime, $ttl ) ) ) // no stalecache, and no DB file, generation is required 00568 { 00569 // no cache available, but a generate callback exists, skip to generation 00570 if ( $generateCallback !== false ) 00571 { 00572 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, need to regenerate data", __METHOD__ ); 00573 00574 // we break out of one loop so that the generateCallback is called 00575 break; 00576 } 00577 // if no generate callback exists, we can directly skip the main loop 00578 else 00579 { 00580 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, cannot get data", __METHOD__ ); 00581 00582 // we break out of two loops so that we directly exit the method and have 00583 // the rest of execution generate the data 00584 break 2; 00585 } 00586 } 00587 else 00588 { 00589 eZDebugSetting::writeDebug( 'kernel-clustering', "Callback from DB file {$this->filePath}", __METHOD__ ); 00590 if ( self::LOCAL_CACHE ) 00591 { 00592 $this->fetch(); 00593 00594 // Figure out which mtime to use for new file, must be larger than 00595 // mtime in DB at least. 00596 $mtime = $this->metaData['mtime'] + 1; 00597 $localmtime = @filemtime( $this->filePath ); 00598 $mtime = max( $mtime, $localmtime ); 00599 touch( $this->filePath, $mtime, $mtime ); 00600 clearstatcache(); // Needed because of touch() call 00601 00602 $args = array( $this->filePath, $mtime ); 00603 if ( $extraData !== null ) 00604 $args[] = $extraData; 00605 $retval = call_user_func_array( $retrieveCallback, $args ); 00606 if ( $retval instanceof eZClusterFileFailure ) 00607 { 00608 break; 00609 } 00610 return $retval; 00611 } 00612 else 00613 { 00614 $uniquePath = $this->fetchUnique(); 00615 00616 $args = array( $uniquePath, $this->metaData['mtime'] ); 00617 if ( $extraData !== null ) 00618 $args[] = $extraData; 00619 $retval = call_user_func_array( $retrieveCallback, $args ); 00620 $this->fileDeleteLocal( $uniquePath ); 00621 if ( $retval instanceof eZClusterFileFailure ) 00622 break; 00623 return $retval; 00624 } 00625 } 00626 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file does not exist, need to regenerate data", __METHOD__ ); 00627 break; 00628 } 00629 } 00630 00631 if ( $tries >= 2 ) 00632 { 00633 eZDebugSetting::writeDebug( 'kernel-clustering', "Reading was retried $tries times and reached the maximum, returning null", __METHOD__ ); 00634 return null; 00635 } 00636 00637 // Generation part starts here 00638 if ( isset( $retval ) && $retval instanceof eZClusterFileFailure ) 00639 { 00640 // This error means that the retrieve callback told 00641 // us NOT to enter generation mode and therefore NOT to store this 00642 // cache. 00643 // This parameter will then be passed to the generate callback, 00644 // and this will set store to false 00645 if ( $retval->errno() == 3 ) 00646 { 00647 $noCache = true; 00648 } 00649 00650 // check for non-expiry error codes 00651 elseif ( $retval->errno() != 1 ) 00652 { 00653 eZDebug::writeError( "Failed to retrieve data from callback", __METHOD__ ); 00654 return null; 00655 } 00656 $message = $retval->message(); 00657 if ( strlen( $message ) > 0 ) 00658 { 00659 eZDebugSetting::writeDebug( 'kernel-clustering', $retval->message(), __METHOD__ ); 00660 } 00661 // the retrieved data was expired so we need to generate it, let's continue 00662 } 00663 00664 // We need to lock if we have a generate-callback or 00665 // the generation is deferred to the caller. 00666 // Note: false means no generation, while null means that generation 00667 // is deferred to the processing that follows (f.i. cache-blocks) 00668 if ( $generateCallback !== false && $this->filePath ) 00669 { 00670 if ( !$this->useStaleCache && !$noCache ) 00671 { 00672 $res = $this->startCacheGeneration(); 00673 if ( $res !== true ) 00674 { 00675 eZDebugSetting::writeDebug( 'kernel-clustering', "{$this->filePath} is being generated, switching to staleCache mode", __METHOD__ ); 00676 $this->useStaleCache = true; 00677 $this->remainingCacheGenerationTime = $res; 00678 $forceDB = false; 00679 continue; 00680 } 00681 } 00682 00683 // File in DB is outdated or non-existing, call write-callback to generate content 00684 if ( $generateCallback ) 00685 { 00686 $args = array( $this->filePath ); 00687 if ( $noCache ) 00688 $extraData['noCache'] = $noCache; 00689 if ( $extraData !== null ) 00690 $args[] = $extraData; 00691 $fileData = call_user_func_array( $generateCallback, $args ); 00692 return $this->storeCache( $fileData ); 00693 } 00694 } 00695 00696 break; 00697 } // End main loop 00698 00699 return new eZClusterFileFailure( 2, "Manual generation of file data is required, calling storeCache is required" ); 00700 } 00701 00702 /** 00703 * Calculates if the file data is expired or not. 00704 * 00705 * @param string $fname Name of file, available for easy debugging. 00706 * @param int $mtime Modification time of file, can be set to false if 00707 * file does not exist. 00708 * @param int $expiry Time when file is to be expired, a value of -1 will 00709 * disable this check. 00710 * @param int $curtime The current time to check against. 00711 * @param int $ttl Number of seconds the data can live, set to null to 00712 * disable TTL. 00713 * @return bool 00714 */ 00715 public function isFileExpired( $fname, $mtime, $expiry, $curtime, $ttl ) 00716 { 00717 if ( $mtime == false or $mtime < 0 ) 00718 { 00719 return true; 00720 } 00721 elseif ( $ttl === null ) 00722 { 00723 return $mtime < $expiry; 00724 } 00725 else 00726 { 00727 return $mtime < max( $expiry, $curtime - $ttl ); 00728 } 00729 } 00730 00731 /** 00732 * Calculates if the current file data is expired or not. 00733 * 00734 * @param int $expiry Time when file is to be expired, a value of -1 will disable this check. 00735 * @param int $curtime The current time to check against. 00736 * @param int $ttl Number of seconds the data can live, set to null to disable TTL. 00737 * @return bool 00738 */ 00739 public function isExpired( $expiry, $curtime, $ttl ) 00740 { 00741 return self::isFileExpired( $this->filePath, $this->metaData['mtime'], $expiry, $curtime, $ttl ); 00742 } 00743 00744 /** 00745 * Calculates if the local file is expired or not. 00746 * @param int $expiry Time when file is to be expired, a value of -1 will disable this check. 00747 * @param int $curtime The current time to check against. 00748 * @param int $ttl Number of seconds the data can live, set to null to disable TTL. 00749 * @return bool 00750 */ 00751 public function isLocalFileExpired( $expiry, $curtime, $ttl ) 00752 { 00753 return self::isFileExpired( $this->filePath, @filemtime( $this->filePath ), $expiry, $curtime, $ttl ); 00754 } 00755 00756 /** 00757 * Calculates if the DB file is expired or not. 00758 * @param int $expiry Time when file is to be expired, a value of -1 will disable this check. 00759 * @param int $curtime The current time to check against. 00760 * @param int $ttl Number of seconds the data can live, set to null to disable TTL. 00761 * @return bool 00762 */ 00763 public function isDBFileExpired( $expiry, $curtime, $ttl ) 00764 { 00765 $mtime = isset( $this->metaData['mtime'] ) ? $this->metaData['mtime'] : 0; 00766 return self::isFileExpired( $this->filePath, $mtime, $expiry, $curtime, $ttl ); 00767 } 00768 00769 /** 00770 * Stores the data in $fileData to the remote and local file and commits the 00771 * transaction. 00772 * 00773 * The parameter $fileData must contain the same as information as the 00774 * $generateCallback returns as explained in processCache(). 00775 * @note This method is just a continuation of the code in processCache() 00776 * and is not meant to be called alone since it relies on specific 00777 * state in the database. 00778 */ 00779 public function storeCache( $fileData ) 00780 { 00781 $scope = false; 00782 $datatype = false; 00783 $binaryData = null; 00784 $fileContent = null; 00785 $store = true; 00786 $storeCache = false; 00787 00788 if ( is_array( $fileData ) ) 00789 { 00790 if ( isset( $fileData['scope'] ) ) 00791 $scope = $fileData['scope']; 00792 if ( isset( $fileData['datatype'] ) ) 00793 $datatype = $fileData['datatype']; 00794 if ( isset( $fileData['content'] ) ) 00795 $fileContent = $fileData['content']; 00796 if ( isset( $fileData['binarydata'] ) ) 00797 $binaryData = $fileData['binarydata']; 00798 if ( isset( $fileData['store'] ) ) 00799 $store = $fileData['store']; 00800 } 00801 else 00802 $binaryData = $fileData; 00803 00804 // This checks if we entered timeout and got our generating file stolen 00805 // If this happens, we don't store our cache 00806 if ( $store and $this->checkCacheGenerationTimeout() ) 00807 $storeCache = true; 00808 00809 $mtime = false; 00810 $result = null; 00811 if ( $binaryData === null && 00812 $fileContent === null ) 00813 { 00814 eZDebug::writeError( "Write callback need to set the 'content' or 'binarydata' entry for '{$this->filePath}'", __METHOD__ ); 00815 $this->abortCacheGeneration(); 00816 return null; 00817 } 00818 00819 if ( $binaryData === null ) 00820 $binaryData = "<" . "?php\n\treturn ". var_export( $fileContent, true ) . ";\n?" . ">\n"; 00821 00822 if ( $fileContent === null ) 00823 $result = $binaryData; 00824 else 00825 $result = $fileContent; 00826 00827 if ( !$this->filePath ) 00828 return $result; 00829 00830 // no store advice from cache generation timeout or disabled viewcache, 00831 // we just return the result 00832 if ( !$storeCache ) 00833 { 00834 eZDebugSetting::writeDebug( 'kernel-clustering', 00835 "Not storing this cache", __METHOD__ ); 00836 $this->abortCacheGeneration(); 00837 return $result; 00838 } 00839 00840 // stale cache handling: we just return the result, no lock has been set 00841 if ( $this->useStaleCache ) 00842 { 00843 eZDebugSetting::writeDebug( 'kernel-clustering', "Stalecache mode enabled for this cache", 00844 "dfs::storeCache( {$this->filePath} )" ); 00845 // we write the generated cache to disk if it does not exist yet, 00846 // to speed up the next uncached operation 00847 // This file will be overwritten by the real file 00848 clearstatcache(); 00849 if ( !file_exists( $this->filePath ) ) 00850 { 00851 eZDebugSetting::writeDebug( 'kernel-clustering', "Writing stale file content to local file {$this->filePath}", __METHOD__ ); 00852 eZFile::create( basename( $this->filePath ), dirname( $this->filePath ), $binaryData, true ); 00853 } 00854 return $result; 00855 } 00856 00857 // Check if we are allowed to store the data, if not just return the result 00858 if ( !$store ) 00859 { 00860 $this->abortCacheGeneration(); 00861 return $result; 00862 } 00863 00864 // the .generating file is stored to DFS. $storeLocally is set to false 00865 // since we don't want to store the .generating file locally, only 00866 // the final file. 00867 $this->storeContents( $binaryData, $scope, $datatype, $storeLocally = false ); 00868 00869 // we end the cache generation process, so that the .generating file 00870 // is removed (we don't need to rename since contents was already stored 00871 // above, using fileStoreContents 00872 $this->endCacheGeneration(); 00873 00874 if ( self::LOCAL_CACHE ) 00875 { 00876 eZDebugSetting::writeDebug( 'kernel-clustering', 00877 "Creating local copy of the file", "dfs::storeCache( '{$this->filePath}' )" ); 00878 eZFile::create( basename( $this->filePath ), dirname( $this->filePath ), $binaryData, true ); 00879 } 00880 00881 return $result; 00882 } 00883 00884 /** 00885 * Provides access to the file contents by downloading the file locally and 00886 * calling $callback with the local filename. The callback can then process 00887 * the contents and return the data in the same way as in processCache(). 00888 * 00889 * Downloading is only done once so the local copy is kept, while updates to 00890 * the remote DB entry is synced with the local one. 00891 * 00892 * The parameters $expiry and $extraData is the same as for processCache(). 00893 * 00894 * @see self::processCache() 00895 * @note Unlike processCache() this returns null if the file cannot be 00896 * accessed. 00897 */ 00898 function processFile( $callback, $expiry = false, $extraData = null ) 00899 { 00900 $result = $this->processCache( $callback, false, null, $expiry, $extraData ); 00901 if ( $result instanceof eZClusterFileFailure ) 00902 { 00903 return null; 00904 } 00905 return $result; 00906 } 00907 00908 /** 00909 * Returns file metadata. 00910 */ 00911 public function stat() 00912 { 00913 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::stat()" ); 00914 return $this->metaData; 00915 } 00916 00917 /** 00918 * Returns file size. 00919 * @return int|null 00920 */ 00921 public function size() 00922 { 00923 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::size()" ); 00924 return isset( $this->metaData['size'] ) ? (int)$this->metaData['size'] : null; 00925 } 00926 00927 /** 00928 * Returns file modification time. 00929 * @return int|null 00930 */ 00931 public function mtime() 00932 { 00933 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::mtime()" ); 00934 return isset( $this->metaData['mtime'] ) ? (int)$this->metaData['mtime'] : null; 00935 } 00936 00937 /** 00938 * Returns file name. 00939 * @return string|null 00940 */ 00941 public function name() 00942 { 00943 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::name()" ); 00944 return $this->filePath; 00945 } 00946 00947 /** 00948 * Deletes multiple files by regex 00949 * @param string $dir An optional directory that will be prepended to the 00950 * regex. Set to false to disable 00951 * @param string $fileRegex The regular expression applied to files 00952 * @return void 00953 * @todo -ceZDFSFileHandler write unit test 00954 */ 00955 public function fileDeleteByRegex( $dir, $fileRegex ) 00956 { 00957 $dir = eZDBFileHandler::cleanPath( $dir ); 00958 $fileRegex = eZDBFileHandler::cleanPath( $fileRegex ); 00959 eZDebug::writeWarning( "Using eZDBFileHandler::fileDeleteByRegex is not recommended since it has some severe performance issues" ); 00960 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileDeleteByRegex( '$dir', '$fileRegex' )" ); 00961 00962 $regex = '^' . ( $dir ? $dir . '/' : '' ) . $fileRegex; 00963 self::$dbbackend->_deleteByRegex( $regex ); 00964 } 00965 00966 /** 00967 * Deletes a list of files by wildcard 00968 * 00969 * @param string $wildcard The wildcard used to look for files. Can contain 00970 * * and ? 00971 * @return void 00972 * @todo -ceZDFSFileHandler write unit test 00973 */ 00974 public function fileDeleteByWildcard( $wildcard ) 00975 { 00976 $wildcard = eZDBFileHandler::cleanPath( $wildcard ); 00977 eZDebug::writeWarning( "Using eZDBFileHandler::fileDeleteByWildcard is not recommended since it has some severe performance issues" ); 00978 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileDeleteByWildcard( '$wildcard' )" ); 00979 00980 self::$dbbackend->_deleteByWildcard( $wildcard ); 00981 } 00982 00983 /** 00984 * Deletes a list of files based on directory / filename components 00985 * 00986 * @param array $dirList Array of directory that will be prefixed with 00987 * $commonPath when looking for files 00988 * @param string $commonPath Starting path common to every delete request 00989 * @param string $commonSuffix Suffix appended to every delete request 00990 * @return void 00991 * @todo -ceZDFSFileHandler write unit test 00992 */ 00993 public function fileDeleteByDirList( $dirList, $commonPath, $commonSuffix ) 00994 { 00995 foreach ( $dirList as $key => $dirItem ) 00996 { 00997 $dirList[$key] = eZDBFileHandler::cleanPath( $dirItem ); 00998 00999 } 01000 $commonPath = eZDBFileHandler::cleanPath( $commonPath ); 01001 $commonSuffix = eZDBFileHandler::cleanPath( $commonSuffix ); 01002 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileDeleteByDirList( '" . join( ", ", $dirList ) . "', '$commonPath', '$commonSuffix' )" ); 01003 01004 self::$dbbackend->_deleteByDirList( $dirList, $commonPath, $commonSuffix ); 01005 } 01006 01007 /** 01008 * Deletes specified file/directory. 01009 * 01010 * @param string $path the file path to delete 01011 * @param bool|string $fnamePart If set to true, $path is a directory and 01012 * its content is deleted. If it is a string, this string 01013 * is appended a wildcard and used for deletion 01014 * @return void 01015 * @todo -ceZDFSFileHandler write unit test 01016 */ 01017 public function fileDelete( $path, $fnamePart = false ) 01018 { 01019 $path = eZDBFileHandler::cleanPath( $path ); 01020 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileDelete( '$path' )" ); 01021 01022 if ( $fnamePart === false ) 01023 { 01024 self::$dbbackend->_delete( $path ); 01025 } 01026 elseif ( $fnamePart === true ) 01027 { 01028 self::$dbbackend->_deleteByLike( $path . '/%' ); 01029 } 01030 else 01031 { 01032 $fnamePart = eZDBFileHandler::cleanPath( $fnamePart ); 01033 self::$dbbackend->_deleteByLike( $path . '/' . $fnamePart . '%' ); 01034 } 01035 } 01036 01037 /** 01038 * Deletes specified file/directory. 01039 * 01040 * If a directory specified it is deleted recursively. 01041 */ 01042 function delete() 01043 { 01044 $path = $this->filePath; 01045 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::delete( '$path' )" ); 01046 01047 self::$dbbackend->_delete( $path ); 01048 01049 $this->metaData = null; 01050 } 01051 01052 /** 01053 * Deletes a file that has been fetched before. 01054 */ 01055 function fileDeleteLocal( $path ) 01056 { 01057 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileDeleteLocal( '$path' )" ); 01058 @unlink( eZDBFileHandler::cleanPath( $path ) ); 01059 01060 eZClusterFileHandler::cleanupEmptyDirectories( $path ); 01061 } 01062 01063 /** 01064 * Deletes a file that has been fetched before. 01065 */ 01066 function deleteLocal() 01067 { 01068 $path = $this->filePath; 01069 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::deleteLocal( '$path' )" ); 01070 @unlink( $path ); 01071 01072 eZClusterFileHandler::cleanupEmptyDirectories( $path ); 01073 } 01074 01075 /** 01076 * Purges local and remote file data for current file path. 01077 * 01078 * Can be given a file or a folder. In order to clear a folder, do NOT add 01079 * a trailing / at the end of the file's path: path/to/file instead of 01080 * path/to/file/. 01081 * 01082 * By default, only expired files will be removed (ezdfsfile.expired = 1). 01083 * If you specify an $expiry time, it will replace the expired test and 01084 * only purge files older than the given expiry timestamp. 01085 * 01086 * @param callback $printCallback 01087 * Callback called after each delete iteration (@see $max) to print 01088 * out a report of the deleted files. This callback expects two 01089 * parameters, $file (delete pattern used to perform deletion) and 01090 * $count (number of deleted items) 01091 * @param int $microsleep 01092 * Wait interval before each purge batch of $max items 01093 * @param int $max 01094 * Maximum number of items to delete in one batch (default: 100) 01095 * @param int $expiry 01096 * If specificed, only files older than this date will be purged 01097 * @return void 01098 */ 01099 function purge( $printCallback = false, $microsleep = false, $max = false, $expiry = false ) 01100 { 01101 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::purge( '$this->filePath' )" ); 01102 01103 $file = $this->filePath; 01104 if ( $max === false ) 01105 { 01106 $max = 100; 01107 } 01108 $count = 0; 01109 /** 01110 * The loop starts without knowing how many files are to be deleted. 01111 * When _purgeByLike is called, it returns the number of affected rows. 01112 * If rows were affected, _purgeByLike will be called again 01113 */ 01114 do 01115 { 01116 // @todo this won't work on windows, make a wrapper that uses 01117 // either usleep or sleep depending on the OS 01118 if ( $count > 0 && $microsleep ) 01119 { 01120 usleep( $microsleep ); // Sleep a bit to make the database happier 01121 } 01122 $count = self::$dbbackend->_purgeByLike( $file . "/%", true, $max, $expiry, 'purge' ); 01123 self::$dbbackend->_purge( $file, true, $expiry, 'purge' ); 01124 if ( $printCallback ) 01125 { 01126 call_user_func_array( $printCallback, array( $file, $count ) ); 01127 } 01128 01129 // @todo Compare $count to $max. If $count < $max, no more files are to 01130 // be purged, and we can exit the loop 01131 } while ( $count > 0 ); 01132 01133 // Remove local copies 01134 if ( is_file( $file ) ) 01135 { 01136 @unlink( $file ); 01137 } 01138 elseif ( is_dir( $file ) ) 01139 { 01140 eZDir::recursiveDelete( $file ); 01141 } 01142 01143 eZClusterFileHandler::cleanupEmptyDirectories( $file ); 01144 } 01145 01146 /** 01147 * Check if given file/dir exists. 01148 * @param string $path File path to test existence for 01149 * @param bool $checkDFSFile if true, also check on the DFS 01150 * @see eZDFSFileHandler::exists() 01151 * @return bool 01152 */ 01153 function fileExists( $path, $checkDFSFile = false ) 01154 { 01155 $path = eZDBFileHandler::cleanPath( $path ); 01156 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileExists( '$path' )" ); 01157 return self::$dbbackend->_exists( $path, false, true, $checkDFSFile ); 01158 } 01159 01160 /** 01161 * Check if given file/dir exists. 01162 * @param bool $checkDFSFile if true, also check on the DFS 01163 * @see eZDFSFileHandler::fileExists() 01164 * @return bool 01165 */ 01166 function exists( $checkDFSFile = false ) 01167 { 01168 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::exists( '$this->filePath' )" ); 01169 return self::$dbbackend->_exists( $this->filePath, false, true, $checkDFSFile ); 01170 } 01171 01172 /** 01173 * Outputs file contents directly 01174 * 01175 * @param int $startOffset Byte offset to start transfer from 01176 * @param int $length Byte length to transfer. NOT end offset, end offset = $startOffset + $length 01177 * 01178 * @return void 01179 */ 01180 function passthrough( $startOffset = 0, $length = false ) 01181 { 01182 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::passthrough( '{$this->filePath}', $startOffset, $length )" ); 01183 self::$dbbackend->_passThrough( $this->filePath, $startOffset, $length, "dfs::passthrough( '{$this->filePath}'" ); 01184 } 01185 01186 /** 01187 * Copy file. 01188 */ 01189 function fileCopy( $srcPath, $dstPath ) 01190 { 01191 $srcPath = eZDBFileHandler::cleanPath( $srcPath ); 01192 $dstPath = eZDBFileHandler::cleanPath( $dstPath ); 01193 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileCopy( '$srcPath', '$dstPath' )" ); 01194 01195 // @todo Add a try... catch block here 01196 self::$dbbackend->_copy( $srcPath, $dstPath ); 01197 } 01198 01199 /** 01200 * Create symbolic or hard link to file. 01201 */ 01202 function fileLinkCopy( $srcPath, $dstPath, $symLink ) 01203 { 01204 $srcPath = eZDBFileHandler::cleanPath( $srcPath ); 01205 $dstPath = eZDBFileHandler::cleanPath( $dstPath ); 01206 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileLinkCopy( '$srcPath', '$dstPath' )" ); 01207 01208 self::$dbbackend->_linkCopy( $srcPath, $dstPath ); 01209 } 01210 01211 /** 01212 * Move file. 01213 */ 01214 function fileMove( $srcPath, $dstPath ) 01215 { 01216 $srcPath = eZDBFileHandler::cleanPath( $srcPath ); 01217 $dstPath = eZDBFileHandler::cleanPath( $dstPath ); 01218 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileMove( '$srcPath', '$dstPath' )" ); 01219 01220 // @todo Catch an exception 01221 self::$dbbackend->_rename( $srcPath, $dstPath ); 01222 01223 $this->metaData = null; 01224 } 01225 01226 /** 01227 * Move/rename file to $dstPath 01228 * @param string $dstPath Destination path 01229 */ 01230 function move( $dstPath ) 01231 { 01232 $dstPath = eZDBFileHandler::cleanPath( $dstPath ); 01233 $srcPath = $this->filePath; 01234 01235 eZDebugSetting::writeDebug( 'kernel-clustering', "dfs::fileMove( '$srcPath', '$dstPath' )" ); 01236 01237 self::$dbbackend->_rename( $srcPath, $dstPath ); 01238 01239 $this->metaData = null; 01240 } 01241 01242 /** 01243 * Get list of files stored in database. 01244 * 01245 * Used in bin/php/clusterize.php. 01246 * 01247 * @param array $scopes return only files that belong to any of these scopes 01248 * @param boolean $excludeScopes if true, then reverse the meaning of $scopes, which is 01249 * return only files that do not belong to any of the scopes listed in $scopes 01250 */ 01251 function getFileList( $scopes = false, $excludeScopes = false ) 01252 { 01253 eZDebugSetting::writeDebug( 'kernel-clustering', 01254 sprintf( "dfs::getFileList( array( %s ), %d )", 01255 is_array( $scopes ) ? implode( ', ', $scopes ) : '', (int) $excludeScopes ) ); 01256 return self::$dbbackend->_getFileList( $scopes, $excludeScopes ); 01257 } 01258 01259 /** 01260 * Returns a clean version of input $path. 01261 * - Backslashes are turned into slashes. 01262 * - Multiple consecutive slashes are turned into one slash. 01263 * - Ending slashes are removed. 01264 * 01265 * Examples: 01266 * - my\windows\path => my/windows/path 01267 * - extra//slashes/\are/fixed => extra/slashes/are/fixed 01268 * - ending/slashes/ => ending/slashes 01269 * 01270 * @todo -ceZDFSFileHandler write unit test 01271 * @return string cleaned up $path 01272 */ 01273 static function cleanPath( $path ) 01274 { 01275 if ( !is_string( $path ) ) 01276 return $path; 01277 return preg_replace( array( "#[/\\\\]+#", "#/$#" ), 01278 array( "/", "" ), 01279 $path ); 01280 } 01281 01282 /** 01283 * Starts cache generation for the current file. 01284 * 01285 * This is done by creating a file named by the original file name, prefixed 01286 * with '.generating'. 01287 * 01288 * @return bool false if the file is being generated, true if it is not 01289 */ 01290 public function startCacheGeneration() 01291 { 01292 eZDebugSetting::writeDebug( 'kernel-clustering', "Starting cache generation", "dfs::startCacheGeneration( '{$this->filePath}' )" ); 01293 01294 $generatingFilePath = $this->filePath . '.generating'; 01295 try { 01296 $ret = self::$dbbackend->_startCacheGeneration( $this->filePath, $generatingFilePath ); 01297 } catch( RuntimeException $e ) { 01298 eZDebug::writeError( $e->getMessage() ); 01299 return false; 01300 } 01301 01302 // generation granted 01303 if ( $ret['result'] == 'ok' ) 01304 { 01305 eZClusterFileHandler::addGeneratingFile( $this ); 01306 $this->realFilePath = $this->filePath; 01307 $this->filePath = $generatingFilePath; 01308 $this->generationStartTimestamp = $ret['mtime']; 01309 return true; 01310 } 01311 // failure: the file is being generated 01312 elseif ( $ret['result'] == 'ko' ) 01313 { 01314 return $ret['remaining']; 01315 } 01316 // unhandled error case, should not happen 01317 else 01318 { 01319 eZLog::write( "An error occured starting cache generation on '$generatingFilePath'", 'cluster.log' ); 01320 return false; 01321 } 01322 } 01323 01324 /** 01325 * Ends the cache generation started by startCacheGeneration(). 01326 */ 01327 public function endCacheGeneration( $rename = true ) 01328 { 01329 if ( $this->realFilePath === null ) 01330 { 01331 eZDebugSetting::writeDebug( 'kernel-clustering', "$this->filePath is not generating", "dfs::endCacheGeneration( '{$this->filePath}' )" ); 01332 return false; 01333 } 01334 01335 eZDebugSetting::writeDebug( 'kernel-clustering', 'Ending cache generation', "dfs::endCacheGeneration( '{$this->realFilePath}' )" ); 01336 try { 01337 if ( self::$dbbackend->_endCacheGeneration( $this->realFilePath, $this->filePath, $rename ) ) 01338 { 01339 $this->filePath = $this->realFilePath; 01340 $this->realFilePath = null; 01341 eZClusterFileHandler::removeGeneratingFile( $this ); 01342 return true; 01343 } 01344 else 01345 { 01346 return false; 01347 } 01348 } catch( RuntimeException $e ) { 01349 eZDebug::writeError( "An error occured ending cache generation on '$this->realFilePath'", 'cluster.log' ); 01350 } 01351 } 01352 01353 /** 01354 * Aborts the current cache generation process. 01355 * 01356 * Does so by rolling back the current transaction, which should be the 01357 * .generating file lock 01358 */ 01359 public function abortCacheGeneration() 01360 { 01361 eZDebugSetting::writeDebug( 'kernel-clustering', 'Aborting cache generation', "dfs::abortCacheGeneration( '{$this->filePath}' )" ); 01362 self::$dbbackend->_abortCacheGeneration( $this->filePath ); 01363 $this->filePath = $this->realFilePath; 01364 $this->realFilePath = null; 01365 eZClusterFileHandler::removeGeneratingFile( $this ); 01366 } 01367 01368 /** 01369 * Checks if the .generating file was changed, which would mean that generation 01370 * timed out. If not timed out, refreshes the timestamp so that storage won't 01371 * be stolen 01372 */ 01373 public function checkCacheGenerationTimeout() 01374 { 01375 eZDebugSetting::writeDebug( 'kernel-clustering', 'Checking cache generation timeout', "dfs::checkCacheGenerationTimeout( '{$this->filePath}' )" ); 01376 return self::$dbbackend->_checkCacheGenerationTimeout( $this->filePath, $this->generationStartTimestamp ); 01377 } 01378 01379 /** 01380 * Determines the cache type based on the current file's path 01381 * @return string viewcache, cacheblock or misc 01382 */ 01383 protected function _cacheType() 01384 { 01385 if ( strpos( $this->filePath, '/cache/content/' ) !== false ) 01386 return 'viewcache'; 01387 elseif ( strpos( $this->filePath, '/cache/template-block/' ) !== false ) 01388 return 'cacheblock'; 01389 else 01390 return 'misc'; 01391 } 01392 01393 /** 01394 * Magic getter 01395 */ 01396 function __get( $propertyName ) 01397 { 01398 switch ( $propertyName ) 01399 { 01400 case 'cacheType': 01401 { 01402 if ( $this->_cacheType === null ) 01403 $this->_cacheType = $this->_cacheType(); 01404 return $this->_cacheType; 01405 } break; 01406 01407 // we only fetch metadata when the status of _metadata is unknown. 01408 // it means that the metadata has not been fetched before 01409 // self::resetMetaData can be called to force reload of metadata 01410 // on the next call 01411 case 'metaData': 01412 { 01413 if ( $this->_metaData === null ) 01414 { 01415 $this->loadMetaData(); 01416 } 01417 return $this->_metaData; 01418 } 01419 } 01420 } 01421 01422 /** 01423 * Since eZDFS uses the database, running clusterize.php is required 01424 * @return bool 01425 */ 01426 public function requiresClusterizing() 01427 { 01428 return true; 01429 } 01430 01431 /** 01432 * eZDFS does require binary purge. 01433 * It does store files in DB + on NFS, and therefore doesn't remove files 01434 * in real time 01435 * 01436 * @since 4.3 01437 * @deprecated Deprecated as of 4.5, use {@link eZDFSFileHandler::requiresPurge()} instead. 01438 * @return bool 01439 */ 01440 public function requiresBinaryPurge() 01441 { 01442 return true; 01443 } 01444 01445 /** 01446 * eZDFS does require binary purge. 01447 * It does store files in DB + on NFS, and therefore doesn't remove files 01448 * in real time 01449 * 01450 * @since 4.5.0 01451 * @return bool 01452 */ 01453 public function requiresPurge() 01454 { 01455 return true; 01456 } 01457 01458 /** 01459 * Fetches the first $limit expired binary items from the DB 01460 * 01461 * @param array $limit A 2 items array( offset, limit ) 01462 * 01463 * @return array(filepath) 01464 * @since 4.3.0 01465 * @deprecated Deprecated as of 4.5, use {@link eZDFSFileHandler::fetchExpiredItems()} instead. 01466 */ 01467 public function fetchExpiredBinaryItems( $limit = array( 0 , 100 ) ) 01468 { 01469 return self::$dbbackend->fetchExpiredItems( array( 'image', 'binaryfile' ), $limit ); 01470 } 01471 01472 /** 01473 * Fetches the first $limit expired files from the DB 01474 * 01475 * @param array $scopes Array of scopes to fetch from 01476 * @param array $limit A 2 items array( offset, limit ) 01477 * @param int $expiry Number of seconds, only items older than this will be returned 01478 * 01479 * @return array(filepath) 01480 * @since 4.5.0 01481 */ 01482 public function fetchExpiredItems( $scopes, $limit = array( 0 , 100 ), $expiry = false ) 01483 { 01484 return self::$dbbackend->expiredFilesList( $scopes, $limit, $expiry ); 01485 } 01486 01487 public function hasStaleCacheSupport() 01488 { 01489 return true; 01490 } 01491 01492 /** 01493 * Database backend class 01494 * Provides metadata operations 01495 * @var eZDFSFileHandlerMySQLBackend 01496 */ 01497 protected static $dbbackend = null; 01498 01499 /** 01500 * Path to the current file 01501 * @var string 01502 */ 01503 public $filePath = null; 01504 01505 /** 01506 * holds the real file path. This is only used when we are generating a cache 01507 * file, in which case $filePath holds the generating cache file name, 01508 * and $realFilePath holds the real name 01509 */ 01510 protected $realFilePath = null; 01511 01512 /** 01513 * Indicates that the current cache item is being generated and an old version 01514 * should be used 01515 * @var bool 01516 */ 01517 protected $useStaleCache = false; 01518 01519 /** 01520 * Remaining time before cache generation times out 01521 * @var int 01522 */ 01523 protected $remainingCacheGenerationTime = false; 01524 01525 /** 01526 * Cache generation start timestamp 01527 * 01528 * When the instance generates the cached version for a file, this property 01529 * holds the timestamp at which generation was started. This is used to control 01530 * a possible generation timeout 01531 * @var int 01532 */ 01533 protected $generationStartTimestamp = false; 01534 01535 /** 01536 * Holds actual file metadata, accessed through the self::metadata 01537 * magic propery. null means the metadata haven't been loaded, false that 01538 * they've been loaded but the file was not found. 01539 * Use eZDFSFileHandler::loadMetaData( true ) to force reloading from DB 01540 * 01541 * @var array 01542 */ 01543 protected $_metaData = null; 01544 01545 /** 01546 * Holds the preferences used when stale cache is activated and no expired 01547 * file is available. 01548 * This is loaded from file.ini, ClusteringSettings/NonExistantStaleCacheHandling 01549 * @var array 01550 */ 01551 protected static $nonExistantStaleCacheHandling = null; 01552 01553 /** 01554 * Type of cache file, used by the nameTrunk feature to determine how nametrunk is computed 01555 * @var string 01556 */ 01557 protected $_cacheType = null; 01558 } 01559 ?>