eZ Publish  [trunk]
ezfs2filehandler.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZFS2FileHandler 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  * This class implements the new version of the FS file handler.
00013  * It provides support for stalecache, but can not be used on every platform:
00014  * support for unlink(), widely used, is not available on windows platforms
00015  * before PHP 5.3. If you use windows, you can not use this handler unless you
00016  * use this PHP version (beta at this time).
00017  * It is perfectly safe to use it on linux / unix
00018  *
00019  * @property-read cacheType
00020  */
00021 class eZFS2FileHandler extends eZFSFileHandler
00022 {
00023     function __construct(  $filePath = false  )
00024     {
00025         parent::__construct( $filePath );
00026         if ( !isset( $GLOBALS['eZFS2FileHandler'] ) )
00027         {
00028             $siteINI = eZINI::instance();
00029             $GLOBALS['eZFS2FileHandler']['GenerationTimeout'] = $siteINI->variable( "ContentSettings", "CacheGenerationTimeout" );
00030             unset( $siteINI );
00031 
00032             $fileINI = eZINI::instance( 'file.ini' );
00033             $GLOBALS['eZFS2FileHandler']['NonExistantStaleCacheHandling'] = $fileINI->variable( "ClusteringSettings", "NonExistantStaleCacheHandling" );
00034             unset( $fileINI );
00035         }
00036         $this->generationTimeout = $GLOBALS['eZFS2FileHandler']['GenerationTimeout'];
00037         $this->nonExistantStaleCacheHandling = $GLOBALS['eZFS2FileHandler']['NonExistantStaleCacheHandling'];
00038     }
00039 
00040     /**
00041      * Creates a single transaction out of the typical file operations for accessing caches.
00042      * Caches are normally ready from the database or local file, if the entry does not exist
00043      * or is expired then it generates the new cache data and stores it.
00044      * This method takes care of these operations and handles the custom code by performing
00045      * callbacks when needed.
00046      *
00047      * The $retrieveCallback is used when the file contents can be used (ie. not re-generation) and
00048      * is called when the file is ready locally.
00049      * The function will be called with the file path as the first parameter, the mtime as the second
00050      * and optionally $extraData as the third.
00051      * The function must return the file contents or an instance of eZClusterFileFailure which can
00052      * be used to tell the system that the retrieve data cannot be used after all.
00053      * $retrieveCallback can be set to null which makes the system go directly to the generation.
00054      *
00055      * The $generateCallback is used when the file content is expired or does not exist, in this
00056      * case the content must be re-generated and stored.
00057      * The function will be called with the file path as the first parameter and optionally $extraData
00058      * as the second.
00059      * The function must return an array with information on the contents, the array consists of:
00060      *   - scope    - The current scope of the file, is optional.
00061      *   - datatype - The current datatype of the file, is optional.
00062      *   - content  - The file content, this can be any type except null.
00063      *   - binarydata - The binary data which is written to the file.
00064      *   - store      - Whether *content* or *binarydata* should be stored to the file, if false it will simply return the data. Optional, by default it is true.
00065      * Note: Set $generateCallback to false to disable generation callback.
00066      * Note: Set $generateCallback to null to tell the function to perform a write lock but not do any generation, the generation must done be done by the caller by calling storeCache().
00067      *
00068      * Either *content* or *binarydata* must be supplied, if not an error is issued and it returns null.
00069      * If *content* is set it will be used as the return value of this function, if not it will return the binary data.
00070      * If *binarydata* is set it will be used as the binary data for the file, if not it will perform a var_export on *content* and use that as the binary data.
00071      *
00072      * For convenience the $generateCallback function can return a string which will be considered as the
00073      * binary data for the file and returned as the content.
00074      *
00075      * For controlling how long a cache entry can be used the parameters $expiry and $ttl is used.
00076      * $expiry can be set to a timestamp which controls the absolute max time for the cache, after this
00077      * time/date the cache will never be used. If the value is set to a negative value or null there the
00078      * expiration check is disabled.
00079      *
00080      * $ttl (time to live) tells how many seconds the cache can live from the time it was stored. If the
00081      * value is set to negative or null there is no limit for the lifetime of the cache. A value of 0 means
00082      * that the cache will always expire and practically disables caching.
00083      * For the cache to be used both the $expiry and $ttl check must hold.
00084      *
00085      * @param mixed $retrieveCallback
00086      * @param mixed $generateCallback
00087      * @param int   $ttl
00088      * @param int   $expiry
00089      * @param array $extraData
00090      */
00091     public function processCache( $retrieveCallback, $generateCallback = null, $ttl = null, $expiry = null, $extraData = null )
00092     {
00093         $curtime   = time();
00094         $tries     = 0;
00095         $noCache   = false;
00096 
00097         if ( $expiry < 0 )
00098             $expiry = null;
00099         if ( $ttl < 0 )
00100             $ttl = null;
00101 
00102         while ( true )
00103         {
00104             $forceGeneration = false;
00105             $storeCache      = true;
00106 
00107             // a local, non expired cache file exists (it may still be expired by
00108             // expiry timestamp, this is tested by the retrieve callback)
00109             if ( $retrieveCallback !== null && !$this->isExpired( $expiry, $curtime, $ttl ) )
00110             {
00111                 $mtime = @filemtime( $this->filePath );
00112                 $args = array( $this->filePath, $mtime );
00113                 if ( $extraData !== null )
00114                     $args[] = $extraData;
00115                 $retval = call_user_func_array( $retrieveCallback, $args );
00116                 if ( !( $retval instanceof eZClusterFileFailure ) )
00117                 {
00118                     return $retval;
00119                 }
00120 
00121                 // if the retrieve callback returns an cluster failure,
00122                 // data have to be generated
00123                 $forceGeneration = true;
00124             }
00125             // stale cache mode has been triggered, we use the old cache file
00126             // if it exists
00127             elseif ( $this->useStaleCache )
00128             {
00129                 // a stalecache file exists
00130                 if ( $this->exists() )
00131                 {
00132                     $args = array( $this->filePath, $curtime );
00133                     if ( $extraData !== null )
00134                         $args[] = $extraData;
00135                     $retval = call_user_func_array( $retrieveCallback, $args );
00136                     if ( !( $retval instanceof eZClusterFileFailure ) )
00137                     {
00138                         return $retval;
00139                     }
00140                     $forceGeneration = true;
00141                 }
00142                 // no stalecache file exists, what we do depends on settings
00143                 else
00144                 {
00145                     // generate the dynamic data without storage
00146                     if ( $this->nonExistantStaleCacheHandling[ $this->cacheType ] == 'generate' )
00147                     {
00148                         eZDebugSetting::writeDebug( 'kernel-clustering', $this->filePath, "Generation is being processed, generating own version" );
00149                         break;
00150                     }
00151                     // wait for the generating process to be finished (or timedout)
00152                     else
00153                     {
00154                         // write a specific wait loop that uses the max generation time as a limit
00155                         eZDebugSetting::writeDebug( 'kernel-clustering', "Generation of '$this->filePath' is being processed, waiting", __METHOD__ );
00156                         $waitedSeconds = 0;
00157                         while ( $waitedSeconds < $this->remainingCacheGenerationTime )
00158                         {
00159                             sleep( 1 );
00160                             $this->loadMetaData( true );
00161                             if ( $this->exists() )
00162                             {
00163                                 eZDebugSetting::writeDebug( 'kernel-clustering', "'$this->filePath' has been generated while waiting, using this newer file", __METHOD__ );
00164                                 $this->useStaleCache = false;
00165                                 $this->remainingCacheGenerationTime = false;
00166 
00167                                 // this continues to the main loop 'while (true)'
00168                                 continue 2;
00169                             }
00170                             $waitedSeconds++;
00171                         }
00172 
00173                         // if we reached this point, it means that we are over the estimated timeout value
00174                         // we try to take the generation over by starting the cache generation. IF this
00175                         // fails again, it is probably because another waiting process has taken the generation
00176                         // over. Maybe add a counter here to prevent some kind of death loop ?
00177                         eZDebugSetting::writeDebug( 'kernel-clustering', "The cache file '$this->filePath' was not generated during the WAIT loop, restarting process", __METHOD__ );
00178                         $this->useStaleCache = false;
00179                         $this->remainingCacheGenerationTime = false;
00180                         $tries++;
00181 
00182                         // this continues to the main loop 'while (true)'
00183                         continue;
00184                     }
00185                 }
00186             }
00187 
00188             if ( $tries >= 2 )
00189             {
00190                 eZDebugSetting::writeDebug( 'kernel-clustering', "Reading was retried $tries times and reached the maximum, forcing generation", __METHOD__ );
00191                 $forceGeneration = true; // We will now generate the cache but not store it
00192                 $storeCache = false; // This disables the cache storage
00193             }
00194 
00195             // Generation part starts here
00196             if ( isset( $retval ) &&
00197                 $retval instanceof eZClusterFileFailure )
00198             {
00199                 // This specific error means that the retrieve callback told
00200                 // us NOT to enter generation mode and therefore NOT to store this
00201                 // cache.
00202                 // This parameter will then be passed to the generate callback,
00203                 // and this will set store to false
00204                 if ( $retval->errno() == 3 )
00205                 {
00206                     $noCache = true;
00207                 }
00208                 // check for non-expiry error codes
00209                 elseif ( $retval->errno() != 1 )
00210                 {
00211                     eZDebug::writeError( "Failed to retrieve data from callback: " . print_r( $retrieveCallback, true ), __METHOD__ );
00212                     return null;
00213                 }
00214                 $message = $retval->message();
00215                 if ( strlen( $message ) > 0 )
00216                 {
00217                     eZDebugSetting::writeDebug( 'kernel-clustering', $retval->message(), __METHOD__ );
00218                 }
00219                 // the retrieved data was expired so we need to generate it, let's continue
00220             }
00221 
00222             // We need to lock if we have a generate-callback or
00223             // the generation is deferred to the caller.
00224             // Note: false means no generation
00225             if ( $generateCallback !== false )
00226             {
00227                 if ( !$this->useStaleCache and !$noCache )
00228                 {
00229                     $res = $this->startCacheGeneration();
00230 
00231                     // an integer means that the file is being generated by another process
00232                     // and we should use an old cache if available.
00233                     // We do this by setting $useStaleCache
00234                     // to true, and restarting the loop from the beggining
00235                     if ( $res !== true )
00236                     {
00237                         eZDebugSetting::writeDebug( 'kernel-clustering', "{$this->filePath} is being generated, switching to staleCache mode", __METHOD__ );
00238                         $this->useStaleCache = true;
00239                         $this->remainingCacheGenerationTime = $res;
00240                         continue;
00241                     }
00242                 }
00243             }
00244 
00245             // File in DB is outdated or non-existing, call write-callback to generate content
00246             if ( $generateCallback )
00247             {
00248                 $args = array( $this->filePath );
00249                 if ( $noCache )
00250                     $extraData['noCache'] = true;
00251                 if ( $extraData !== null )
00252                     $args[] = $extraData;
00253                 $fileData = call_user_func_array( $generateCallback, $args );
00254 
00255                 if( !isset( $fileData['store'] ) )
00256                     $storeCache = false;
00257                 else
00258                     $storeCache = $fileData['store'];
00259 
00260                 return $this->storeCache( $fileData, $storeCache );
00261             }
00262 
00263             break;
00264         }
00265 
00266         return new eZClusterFileFailure( 2, "Manual generation of file data is required, calling storeCache is required" );
00267     }
00268 
00269     /**
00270      * Stores the data in $fileData to the remote and local file and commits the
00271      * transaction.
00272      * The parameter $fileData must contain the same as information as the
00273      * $generateCallback returns as explained in processCache().
00274      * @note This method is just a continuation of the code in processCache() and
00275      *       is not meant to be called alone since it relies on specific state in
00276      *       the database.
00277      */
00278     function storeCache( $fileData, $storeCache = true )
00279     {
00280         $scope       = false;
00281         $datatype    = false;
00282         $binaryData  = null;
00283         $fileContent = null;
00284         $store       = true;
00285         if ( is_array( $fileData ) )
00286         {
00287             if ( isset( $fileData['scope'] ) )
00288                 $scope = $fileData['scope'];
00289             if ( isset( $fileData['datatype'] ) )
00290                 $datatype = $fileData['datatype'];
00291             if ( isset( $fileData['content'] ) )
00292                 $fileContent = $fileData['content'];
00293             if ( isset( $fileData['binarydata'] ) )
00294                 $binaryData = $fileData['binarydata'];
00295             if ( isset( $fileData['store'] ) )
00296                 $store = $fileData['store'];
00297         }
00298         else
00299             $binaryData = $fileData;
00300 
00301         // if generation timedout, the .generating file's timeout has changed,
00302         // and we must not store the cache file
00303         if ( $store and !$this->checkCacheGenerationTimeout() )
00304             $storeCache = false;
00305 
00306         $mtime = false;
00307         $result = null;
00308         if ( $binaryData === null &&
00309             $fileContent === null )
00310         {
00311             eZDebug::writeError( "Write callback need to set the 'content' or 'binarydata' entry" );
00312             return null;
00313         }
00314 
00315         if ( $binaryData === null )
00316             $binaryData = "<" . "?php\n\treturn ". var_export( $fileContent, true ) . ";\n?" . ">\n";
00317 
00318         if ( $fileContent === null )
00319             $result = $binaryData;
00320         else
00321             $result = $fileContent;
00322 
00323         // stale cache handling: we just return the result, no lock has been set
00324         if ( $this->useStaleCache )
00325         {
00326             eZDebugSetting::writeDebug( 'kernel-clustering', "Returning locally generated data without storing", __METHOD__ );
00327             return $result;
00328         }
00329 
00330         if ( !$storeCache )
00331         {
00332             $this->abortCacheGeneration();
00333             return $result;
00334         }
00335 
00336         // Store content locally
00337         $this->storeContents( $binaryData, $scope, $datatype, true );
00338 
00339         // we end the cache generation process, so that the .generating file
00340         // is renamed to its final name
00341         $this->endCacheGeneration();
00342 
00343         return $result;
00344     }
00345 
00346     /**
00347      * Starts cache generation for the current file.
00348      *
00349      * This is done by creating a file named by the original file name, prefixed
00350      * with '.generating'.
00351      *
00352      * @todo add timeout handling...
00353      *
00354      * @return mixed true if generation lock was granted, an integer matching the
00355      *               time before the current generation times out
00356      */
00357     public function startCacheGeneration()
00358     {
00359         eZDebugSetting::writeDebug( "kernel-clustering", $this->filePath, __METHOD__ );
00360 
00361         $ret = true;
00362 
00363         $generatingFilePath = $this->filePath . '.generating';
00364 
00365         // the x flag will throw a warning if the file exists. Allows existence
00366         // check AND creation at the same time
00367         if ( !$fp = @fopen( $generatingFilePath, 'x' ) )
00368         {
00369             $directory = dirname( $generatingFilePath ) . DIRECTORY_SEPARATOR;
00370 
00371             // the directory we're trying to create the file in does not exist
00372             eZDebugSetting::writeDebug( 'kernel-clustering', $this->filePath . " creation failed, checking if '$directory' exists", __METHOD__ );
00373 
00374             if ( !file_exists( $directory ) )
00375             {
00376                 eZDebugSetting::writeDebug( 'kernel-clustering', $this->filePath . " target directory does not exist, creating and trying again", __METHOD__ );
00377 
00378                 if ( eZDir::mkdir( $directory, false, true ) )
00379                     eZDebugSetting::writeDebug( 'kernel-clustering', "Directory '$directory' created, trying to start generation again", __METHOD__ );
00380                 else
00381                     eZDebugSetting::writeDebug( 'kernel-clustering', "Directory '$directory' failed to be created, it might have been created by another process", __METHOD__ );
00382 
00383                 // we check again since the folder may have been created by another
00384                 // process in between. Not likely, though.
00385                 if ( !$fp = @fopen( $generatingFilePath, 'x' ) )
00386                 {
00387                     $ret = $this->remainingCacheGenerationTime( $generatingFilePath );
00388                 }
00389             }
00390             // directory exists, we now check for timeout
00391             else
00392             {
00393                 // timeout check
00394                 if ( $mtime = @filemtime( $generatingFilePath ) )
00395                 {
00396                     $remainingGenerationTime = $this->remainingCacheGenerationTime( $generatingFilePath );
00397                     eZDebugSetting::writeDebug( 'kernel-clustering', "Remaining generation time: $remainingGenerationTime", __METHOD__ );
00398                     if ( $remainingGenerationTime < 0 )
00399                     {
00400                         eZDebugSetting::writeDebug( 'kernel-clustering', $this->filePath . " generating file exists, but generation has timedout, taking over", __METHOD__ );
00401                         touch( $generatingFilePath, time(), time() );
00402                     }
00403                     else
00404                     {
00405                         eZDebugSetting::writeDebug( 'kernel-clustering', $this->filePath . " failed opening file for writing, generation is underway", __METHOD__ );
00406                         $ret = $remainingGenerationTime;
00407                     }
00408                 }
00409             }
00410         }
00411 
00412         // if the generation lock was granted, we can perform our specific file
00413         // operations: change the file name to the generation name, and store
00414         // required generating informations
00415         if ( $ret === true )
00416         {
00417             // $fp might not be a valid handle if timeout has occured
00418             if ( $fp )
00419                 fclose( $fp );
00420 
00421             eZClusterFileHandler::addGeneratingFile( $this );
00422             $this->realFilePath = $this->filePath;
00423             $this->filePath = $generatingFilePath;
00424             $this->generationStartTimestamp = filemtime( $this->filePath );
00425         }
00426 
00427         return $ret;
00428     }
00429 
00430     /**
00431      * Ends the cache generation started by startCacheGeneration().
00432      *
00433      * If $rename is set to true (default), the .generating file is renamed and
00434      * overwrites the real file.
00435      * If set to false, the .generating file is removed, and the real file made
00436      * available.
00437      *
00438      * True should be used when actual data is stored in the standard file and
00439      * not the .generating one, for instance when using image alias generation.
00440      *
00441      * @param bool $rename Rename (true) or delete (false) the generating file
00442      *
00443      * @return bool
00444      */
00445     public function endCacheGeneration( $rename = true)
00446     {
00447         if ( $this->realFilePath === null )
00448         {
00449             $this->loadMetaData();
00450             eZDebugSetting::writeDebug( 'kernel-clustering', "$this->filePath is not generating", "fs2::endCacheGeneration( '{$this->filePath}' )" );
00451             return false;
00452         }
00453 
00454         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00455 
00456         $ret = false;
00457         eZDebugSetting::writeDebug( "kernel-clustering", $this->realFilePath, __METHOD__ );
00458 
00459         // rename the file to its final name
00460         if ( $rename === true )
00461         {
00462             if ( eZFile::rename( $this->filePath, $this->realFilePath, false, eZFile::CLEAN_ON_FAILURE ) )
00463             {
00464                 $this->filePath = $this->realFilePath;
00465                 $this->realFilePath = null;
00466                 eZClusterFileHandler::removeGeneratingFile( $this );
00467                 $this->remainingCacheGenerationTime = false;
00468                 $ret = true;
00469                 $this->loadMetaData();
00470             }
00471             else
00472             {
00473                 eZLog::write( "eZFS2FileHandler::endCacheGeneration: Failed renaming '$this->filePath' to '$this->realFilePath'", 'cluster.log' );
00474             }
00475         }
00476         else
00477         {
00478             unlink( $this->filePath );
00479             $this->filePath = $this->realFilePath;
00480             $this->realFilePath = null;
00481             $this->loadMetaData();
00482         }
00483 
00484         eZDebug::accumulatorStop( 'dbfile' );
00485 
00486         return $ret;
00487     }
00488 
00489     /**
00490      * Aborts the current cache generation process.
00491      *
00492      * Does so by rolling back the current transaction, which should be the
00493      * .generating file lock
00494      */
00495     public function abortCacheGeneration()
00496     {
00497         eZDebugSetting::writeDebug( 'kernel-clustering', $this->realFilePath, __METHOD__ );
00498         @unlink( $this->filePath );
00499         $this->filePath = $this->realFilePath;
00500         $this->realFilePath = null;
00501         $this->remainingCacheGenerationTime = false;
00502         eZClusterFileHandler::removeGeneratingFile( $this );
00503     }
00504 
00505     /**
00506      * Checks if the .generating file was changed, which would mean that generation
00507      * timed out. If not timed out, refreshes the timestamp so that storage won't
00508      * be stolen
00509      */
00510     public function checkCacheGenerationTimeout()
00511     {
00512         clearstatcache();
00513         // file_exists = false: another process stole the lock and finished the generation
00514         // filemtime != stored one: another process is generating the file
00515         if ( !file_exists( $this->filePath ) or ( @filemtime( $this->filePath ) != $this->generationStartTimestamp ) )
00516         {
00517             eZDebugSetting::writeDebug( 'kernel-clustering', "'$this->filePath' was changed during generation, looks like a generation timeout", __METHOD__ );
00518             eZLog::write( "Generation of '$this->filePath' timed out", 'cluster.log' );
00519             return false;
00520         }
00521         else
00522         {
00523             $mtime = time();
00524             touch( $this->filePath, $mtime, $mtime );
00525             return true;
00526         }
00527     }
00528 
00529     /**
00530      * Returns the remaining time, in seconds, before the generating file times
00531      * out
00532      *
00533      * @param string $filePath
00534      *
00535      * @return int Remaining generation seconds. A negative value indicates a timeout.
00536      */
00537     private function remainingCacheGenerationTime( $filePath )
00538     {
00539         clearstatcache();
00540         eZDebugSetting::writeDebug( 'kernel-clustering', "clearstatcache called on $filePath", __METHOD__ );
00541         $mtime = @filemtime( $filePath );
00542         $remainingGenerationTime = ( $mtime + $this->generationTimeout ) - time();
00543         return $remainingGenerationTime;
00544     }
00545 
00546     /**
00547      * Delete files located in a directories from dirList, with common prefix specified by
00548      * commonPath, and common suffix with added wildcard at the end
00549      *
00550      * \public
00551      * \static
00552      * \sa fileDeleteByRegex()
00553      */
00554     function fileDeleteByDirList( $dirList, $commonPath, $commonSuffix )
00555     {
00556         $dirs = implode( ',', $dirList );
00557         $wildcard = $commonPath .'/{' . $dirs . '}/' . $commonSuffix . '*';
00558 
00559         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDeleteByDirList( '".implode(', ', $dirList)."', '$commonPath', '$commonSuffix' )", __METHOD__ );
00560 
00561         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00562         array_map( array( __CLASS__, '_expire' ), eZSys::globBrace( $wildcard ) );
00563         eZDebug::accumulatorStop( 'dbfile' );
00564     }
00565 
00566     /**
00567      * Deletes the file(s) or directory matching $path and $fnamePart if given
00568      * @param $path path of the file to delete
00569      * @param $fnamePart path of the file to delete
00570      */
00571     function fileDelete( $path, $fnamePart = false )
00572     {
00573         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDelete( '$path' )", __METHOD__ );
00574 
00575         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00576 
00577         $list = array();
00578         if ( $fnamePart !== false )
00579         {
00580             $list = glob( $path . "/" . $fnamePart . "*" );
00581         }
00582         else
00583         {
00584             $list = array( $path );
00585         }
00586 
00587         foreach ( $list as $path )
00588         {
00589             if ( is_file( $path ) )
00590             {
00591                 self::_expire( $path );
00592             }
00593             else
00594             {
00595                 self::_recursiveExpire( $path );
00596             }
00597         }
00598 
00599         eZDebug::accumulatorStop( 'dbfile' );
00600     }
00601 
00602     /**
00603      * Deletes specified file/directory.
00604      *
00605      * If a directory specified it is deleted recursively.
00606      *
00607      * \public
00608      * \static
00609      */
00610     function delete()
00611     {
00612         $path = $this->filePath;
00613         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::delete( '$path' )", __METHOD__ );
00614 
00615         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00616 
00617         if ( is_file( $path ) )
00618         {
00619             self::_expire( $path );
00620         }
00621         elseif ( is_dir( $path ) )
00622         {
00623             self::_recursiveExpire( $path );
00624         }
00625 
00626         eZDebug::accumulatorStop( 'dbfile' );
00627     }
00628 
00629     /**
00630      * Deletes a file that has been fetched before.
00631      *
00632      * In case of fetching from filesystem does nothing.
00633      *
00634      * \public
00635      */
00636     function deleteLocal()
00637     {
00638         $path = $this->filePath;
00639         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::deleteLocal( '$path' )", __METHOD__ );
00640 
00641         eZClusterFileHandler::cleanupEmptyDirectories( $path );
00642     }
00643 
00644     /**
00645      * Purge local and remote file data for current file.
00646      */
00647     function purge( $printCallback = false, $microsleep = false, $max = false, $expiry = false )
00648     {
00649         $file = $this->filePath;
00650         if ( $max === false )
00651             $max = 100;
00652         $count = 0;
00653         if ( is_file( $file ) )
00654             $list = array( $file );
00655         else
00656             $list = glob( $file . "/*" );
00657         do
00658         {
00659             if ( ( $count % $max ) == 0 && $microsleep )
00660                 usleep( $microsleep ); // Sleep a bit to make the filesystem happier
00661 
00662             $count = 0;
00663             $file = array_shift( $list );
00664 
00665             if ( is_file( $file ) )
00666             {
00667                 $mtime = @filemtime( $file );
00668                 if ( $expiry === false ||
00669                     $mtime < $expiry ) // remove it if it is too old
00670                 {
00671                     @unlink( $file );
00672 
00673                     eZClusterFileHandler::cleanupEmptyDirectories( $file );
00674                 }
00675                 ++$count;
00676             }
00677             else if ( is_dir( $file ) )
00678             {
00679                 $list = array_merge( $list, glob( $file . "/*" ) );
00680             }
00681 
00682             if ( $printCallback )
00683                 call_user_func_array( $printCallback,
00684                                       array( $file, 1 ) );
00685         } while ( count( $list ) > 0 );
00686     }
00687 
00688     /**
00689      * Expire the given file
00690      * @param string $path Path of the file to expire
00691      * @return bool
00692      */
00693     private static function _expire( $path )
00694     {
00695         eZDebugSetting::writeDebug( 'kernel-clustering', $path, __METHOD__ );
00696         $ret = touch( $path, self::EXPIRY_TIMESTAMP, self::EXPIRY_TIMESTAMP );
00697         eZDebugSetting::writeDebug( 'kernel-clustering', date( 'd/m/Y', filemtime( $path ) ), __METHOD__ . ': mtime' );
00698         return $ret;
00699     }
00700 
00701     /**
00702      * Expires all files in a directory
00703      * @param string $directory
00704      * @return void
00705      */
00706     private static function _recursiveExpire( $directory )
00707     {
00708         eZDebugSetting::writeDebug( 'kernel-clustering', $directory, __METHOD__ );
00709 
00710         if ( $handle = @opendir( $directory ) )
00711         {
00712             while ( ( $file = readdir( $handle ) ) !== false )
00713             {
00714                 if ( ( $file == "." ) || ( $file == ".." ) )
00715                 {
00716                     continue;
00717                 }
00718                 if ( is_dir( $directory . '/' . $file ) )
00719                 {
00720                     self::_recursiveExpire( $directory . '/' . $file );
00721                 }
00722                 else
00723                 {
00724                     self::_expire( $directory . '/' . $file );
00725                 }
00726             }
00727             @closedir( $handle );
00728         }
00729     }
00730 
00731     /**
00732      * Determines the cache type based on the path
00733      * @return string viewcache, cacheblock or misc
00734      */
00735     private function _cacheType()
00736     {
00737         if ( strstr( $this->filePath, 'cache/content' ) !== false )
00738             return 'viewcache';
00739         elseif ( strstr( $this->filePath, 'cache/template-block' ) !== false )
00740             return 'cacheblock';
00741         else
00742             return 'misc';
00743     }
00744 
00745     /**
00746      * Magic getter
00747      */
00748     function __get( $propertyName )
00749     {
00750         switch ( $propertyName )
00751         {
00752             case 'cacheType':
00753             {
00754                 if ( $this->internalCacheType === null )
00755                 {
00756                     $this->internalCacheType = $this->_cacheType();
00757                 }
00758                 return $this->internalCacheType;
00759             } break;
00760         }
00761     }
00762 
00763     /**
00764      * eZFS2 doesn't require clusterizing, as it only uses the filesystem
00765      * @return bool
00766      */
00767     public function requiresClusterizing()
00768     {
00769         return false;
00770     }
00771 
00772     /**
00773      * eZFS2 doesn't require purge as it already purges files in realtime
00774      * (FS based)
00775      *
00776      * @since 4.3
00777      * @deprecated Deprecated as of 4.5, use {@link eZFS2FileHandler::requiresPurge()} instead.
00778      * @return bool
00779      */
00780     public function requiresBinaryPurge()
00781     {
00782         return false;
00783     }
00784 
00785     /**
00786      * eZFS2 doesn't require purge as it already purges files in realtime
00787      * (FS based)
00788      *
00789      * @since 4.5.0
00790      * @return bool
00791      */
00792     public function requiresPurge()
00793     {
00794         return false;
00795     }
00796 
00797     public function hasStaleCacheSupport()
00798     {
00799         return true;
00800     }
00801 
00802     /**
00803      * Checks if the given $path exists.
00804      *
00805      * @param string $path
00806      * @return bool
00807      */
00808     function fileExists( $path )
00809     {
00810         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileExists( '$path' )", __METHOD__ );
00811 
00812         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00813         $rc = file_exists( $path ) && ( filemtime( $path ) != self::EXPIRY_TIMESTAMP );
00814         eZDebug::accumulatorStop( 'dbfile' );
00815 
00816         return $rc;
00817     }
00818 
00819     /**
00820      * Check if given file/dir exists.
00821      *
00822      * NOTE: this function does not interact with filesystem.
00823      * Instead, it just returns existance status determined in the constructor.
00824      *
00825      * \public
00826      */
00827     function exists()
00828     {
00829         $path = $this->filePath;
00830         if ( isset( $this->metaData['mtime'] ) )
00831         {
00832             $return = ( $this->metaData['mtime'] != self::EXPIRY_TIMESTAMP );
00833         }
00834         else
00835         {
00836             $return = false;
00837         }
00838         eZDebugSetting::writeDebug( 'kernel-clustering', "fs2::exists( '$path' ): " . ( $return ? 'true' :'false' ), __METHOD__ );
00839         return $return;
00840     }
00841 
00842 
00843     /**
00844      * holds the real file path. This is only used when we are generating a cache
00845      * file, in which case $filePath holds the generating cache file name,
00846      * and $realFilePath holds the real name
00847      */
00848     public $realFilePath = null;
00849 
00850     /**
00851      * Indicates that the current cache item is being generated and an old version
00852      * should be used
00853      * @var bool
00854      */
00855     protected $useStaleCache = false;
00856 
00857     /**
00858      * Generation timeout, in seconds. If a generating file exists for more than
00859      * $generationTimeout seconds, it is taken over
00860      * @var int
00861      */
00862     protected $generationTimeout;
00863 
00864     /**
00865      * Holds the preferences used when stale cache is activated and no expired
00866      * file is available.
00867      * This is loaded from file.ini, ClusteringSettings.NonExistantStaleCacheHandling
00868      */
00869     protected $nonExistantStaleCacheHandling;
00870 
00871     /**
00872      * Holds the number of seconds remaining before the generating cache times out
00873      * @var int
00874      */
00875     protected $remainingCacheGenerationTime = false;
00876 
00877     /**
00878      * When the instance generates the cached version for a file, this property
00879      * holds the timestamp at which generation was started. This is used to control
00880      * a possible generation timeout
00881      * @var int
00882      */
00883     protected $generationStartTimestamp = false;
00884 
00885     /**
00886      * Cached value of cache type
00887      *
00888      * @var string|null
00889      */
00890     protected $internalCacheType = null;
00891 }
00892 ?>