eZ Publish  [trunk]
ezdfsfilehandler.php
Go to the documentation of this file.
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 ?>