eZ Publish  [trunk]
ezfsfilehandler.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZFSFileHandler 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 class eZFSFileHandler
00012 {
00013     /**
00014      * This should be defined in eZFS2FileHandler, but due to static members
00015      * limitations in PHP < 5.3, it is declared here
00016      */
00017     const EXPIRY_TIMESTAMP = 233366400;
00018 
00019     /**
00020      * Constructor.
00021      *
00022      * $filePath File path. If specified, file metadata is fetched in the constructor.
00023      */
00024     function eZFSFileHandler( $filePath = false )
00025     {
00026         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::instance( '$filePath' )", __METHOD__ );
00027         $this->Mutex = null;
00028         $this->filePath = $filePath;
00029         $this->lifetime = 60; // Lifetime of lock
00030         $this->loadMetaData();
00031     }
00032 
00033     /*!
00034      \private
00035      Acquires an exclusive lock to the current file by using eZMutex.
00036 
00037      If a lock is already present it will sleep 0.5 seconds and try again until
00038      the lock lifetime is exceeded and the lock is stolen.
00039 
00040      Note: Lock stealing might be removed.
00041 
00042      \param $fname Name of the calling code (usually function name).
00043      */
00044     function _exclusiveLock( $fname = false )
00045     {
00046         $mutex =& $this->_mutex();
00047         while ( true )
00048         {
00049             $timestamp  = $mutex->lockTS(); // Note: This does not lock, only checks what the timestamp is.
00050             if ( $timestamp === false )
00051             {
00052                 if ( !$mutex->lock() )
00053                 {
00054                     eZDebug::writeWarning( "Failed to acquire lock for file " . $this->filePath );
00055                     return false;
00056                 }
00057                 $mutex->setMeta( 'pid', getmypid() );
00058                 return true;
00059             }
00060             if ( $timestamp >= time() - $this->lifetime )
00061             {
00062                 usleep( 500000 ); // Sleep 0.5 second
00063                 continue;
00064             }
00065 
00066             $oldPid = $mutex->meta( 'pid' );
00067             if ( is_numeric( $oldPid ) &&
00068                  $oldPid != 0 &&
00069                  function_exists( 'posix_kill' ) )
00070             {
00071                 posix_kill( $oldPid, 9 );
00072             }
00073             if ( !$mutex->steal() )
00074             {
00075                 eZDebug::writeWarning( "Failed to steal lock for file " . $this->filePath . " from PID $oldPid" );
00076                 return false;
00077             }
00078             $mutex->setMeta( 'pid', getmypid() );
00079             return true;
00080         }
00081     }
00082 
00083     /*!
00084      \private
00085      Frees the current exclusive lock in use.
00086 
00087      \param $fname Name of the calling code (usually function name).
00088      */
00089     function _freeExclusiveLock( $fname = false )
00090     {
00091         $mutex =& $this->_mutex();
00092         $mutex->unlock();
00093     }
00094 
00095     /*!
00096      \private
00097      Returns the mutex object for the current file.
00098      */
00099     function &_mutex()
00100     {
00101         if ( $this->Mutex !== null )
00102             return $this->Mutex;
00103         $mutex = new eZMutex( $this->filePath );
00104         return $mutex;
00105     }
00106 
00107     /*!
00108      \public
00109      Load file meta information.
00110 
00111      \param $force If true, file stats will be refreshed
00112     */
00113     function loadMetaData( $force = false )
00114     {
00115         if ( $this->filePath !== false )
00116         {
00117             eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00118             if ( $force )
00119             {
00120                 clearstatcache();
00121                 eZDebugSetting::writeDebug( 'kernel-clustering', ' clearstatcache called on ' . $this->filePath, __METHOD__ );
00122             }
00123 
00124             $this->metaData = file_exists( $this->filePath ) ? stat( $this->filePath ) : false;
00125             eZDebug::accumulatorStop( 'dbfile' );
00126         }
00127     }
00128 
00129     /**
00130      * Fetches file from db and saves it in FS under the same name.
00131      *
00132      * In case of fetching from filesystem does nothing.
00133      */
00134     function fileFetch( $filePath )
00135     {
00136         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileFetch( '$filePath' )", __METHOD__ );
00137         return ( file_exists( $filePath ) ? $filePath : false );
00138     }
00139 
00140     /**
00141      * Fetches file from db and saves it in FS under the same name.
00142      *
00143      * In case of fetching from filesystem does nothing.
00144      */
00145     function fetch( $noLocalCache = false )
00146     {
00147         $filePath = $this->filePath;
00148         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fetch( '$filePath' )", __METHOD__ );
00149     }
00150 
00151     /**
00152      * Fetches file from db and saves it in FS under unique name.
00153      *
00154      * In case of fetching from filesystem, does nothing
00155      *
00156      * @return string The unique file path. on FS, the file path.
00157      */
00158     function fetchUnique()
00159     {
00160         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fetchUnique( '{$this->filePath}' )", __METHOD__ );
00161         return $this->filePath;
00162     }
00163 
00164     /**
00165      * Store file.
00166      *
00167      * In case of storing to filesystem does nothing.
00168      *
00169      * @param string $filePath Path to the file being stored.
00170      * @param string $scope    Means something like "file category". May be used to clean caches of a certain type.
00171      * @param string $delete   true if the file should be deleted after storing.
00172      */
00173     function fileStore( $filePath, $scope = false, $delete = false, $datatype = false )
00174     {
00175         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileStore( '$filePath' )", __METHOD__ );
00176     }
00177 
00178     /**
00179      * Store file contents.
00180      *
00181      * \public
00182      * \static
00183      */
00184     function fileStoreContents( $filePath, $contents, $scope = false, $datatype = false )
00185     {
00186         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileStoreContents( '$filePath' )", __METHOD__ );
00187 
00188         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00189 
00190         eZFile::create( basename( $filePath ), dirname( $filePath ), $contents, true );
00191 
00192         $perm = eZINI::instance()->variable( 'FileSettings', 'StorageFilePermissions' );
00193         chmod( $filePath, octdec( $perm ) );
00194 
00195         eZDebug::accumulatorStop( 'dbfile' );
00196     }
00197 
00198     /**
00199      * Store file contents to disk
00200      *
00201      * @param string $contents Binary file data
00202      * @param string $datatype Not used in the FS handler
00203      * @param string $scope Not used in the FS handler
00204      * @param bool $storeLocally Not used in the FS handler
00205      *
00206      * @return void
00207      */
00208     function storeContents( $contents, $scope = false, $datatype = false, $storeLocally = false )
00209     {
00210         $filePath = $this->filePath;
00211 
00212         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::storeContents( '$filePath' )", __METHOD__ );
00213 
00214         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00215 
00216         eZFile::create( basename( $filePath ), dirname( $filePath ), $contents, true );
00217         $perm = eZINI::instance()->variable( 'FileSettings', 'StorageFilePermissions' );
00218         chmod( $filePath, octdec( $perm ) );
00219 
00220         eZDebug::accumulatorStop( 'dbfile' );
00221     }
00222 
00223     /**
00224      * Returns file contents.
00225      *
00226      * @return string|false contents string, or false in case of an error.
00227      */
00228     function fileFetchContents( $filePath )
00229     {
00230         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileFetchContents( '$filePath' )", __METHOD__ );
00231 
00232         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00233         $rslt = is_readable( $filePath ) ? file_get_contents( $filePath ) : false;
00234         eZDebug::accumulatorStop( 'dbfile' );
00235 
00236         return $rslt;
00237     }
00238 
00239     /**
00240      * Returns file contents.
00241      *
00242      * \public
00243      * \return contents string, or false in case of an error.
00244      */
00245     function fetchContents()
00246     {
00247         $filePath = $this->filePath;
00248         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fetchContents( '$filePath' )", __METHOD__ );
00249 
00250         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00251         $rslt = is_readable( $filePath ) ? file_get_contents( $filePath ) : false;
00252         eZDebug::accumulatorStop( 'dbfile' );
00253 
00254         return $rslt;
00255     }
00256 
00257     /*!
00258      Creates a single transaction out of the typical file operations for accessing caches.
00259      Caches are normally ready from the database or local file, if the entry does not exist
00260      or is expired then it generates the new cache data and stores it.
00261      This method takes care of these operations and handles the custom code by performing
00262      callbacks when needed.
00263 
00264      The $retrieveCallback is used when the file contents can be used (ie. not re-generation) and
00265      is called when the file is ready locally.
00266      The function will be called with the file path as the first parameter, the mtime as the second
00267      and optionally $extraData as the third.
00268      The function must return the file contents or an instance of eZClusterFileFailure which can
00269      be used to tell the system that the retrieve data cannot be used after all.
00270      $retrieveCallback can be set to null which makes the system go directly to the generation.
00271 
00272      The $generateCallback is used when the file content is expired or does not exist, in this
00273      case the content must be re-generated and stored.
00274      The function will be called with the file path as the first parameter and optionally $extraData
00275      as the second.
00276      The function must return an array with information on the contents, the array consists of:
00277      - scope    - The current scope of the file, is optional.
00278      - datatype - The current datatype of the file, is optional.
00279      - content  - The file content, this can be any type except null.
00280      - binarydata - The binary data which is written to the file.
00281      - 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.
00282      Note: Set $generateCallback to false to disable generation callback.
00283      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().
00284 
00285      Either *content* or *binarydata* must be supplied, if not an error is issued and it returns null.
00286      If *content* is set it will be used as the return value of this function, if not it will return the binary data.
00287      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.
00288 
00289      For convenience the $generateCallback function can return a string which will be considered as the
00290      binary data for the file and returned as the content.
00291 
00292      For controlling how long a cache entry can be used the parameters $expiry and $ttl is used.
00293      $expiry can be set to a timestamp which controls the absolute max time for the cache, after this
00294      time/date the cache will never be used. If the value is set to a negative value or null there the
00295      expiration check is disabled.
00296 
00297      $ttl (time to live) tells how many seconds the cache can live from the time it was stored. If the
00298      value is set to negative or null there is no limit for the lifetime of the cache. A value of 0 means
00299      that the cache will always expire and practically disables caching.
00300      For the cache to be used both the $expiry and $ttl check must hold.
00301      */
00302     function processCache( $retrieveCallback, $generateCallback = null, $ttl = null, $expiry = null, $extraData = null )
00303     {
00304         $fname = $this->filePath;
00305         $args = array( $fname );
00306         if ( $extraData !== null )
00307             $args[] = $extraData;
00308         $curtime   = time();
00309         $tries     = 0;
00310         $noCache   = false;
00311 
00312         if ( $expiry < 0 )
00313             $expiry = null;
00314         if ( $ttl < 0 )
00315             $ttl = null;
00316 
00317         while ( true )
00318         {
00319             $forceGeneration = false;
00320             $storeCache      = true;
00321             $mtime = file_exists( $fname ) ? filemtime( $fname ) : false;
00322             if ( $retrieveCallback !== null && !$this->isExpired( $expiry, $curtime, $ttl ) )
00323             {
00324                 $args = array( $fname, $mtime );
00325                 if ( $extraData !== null )
00326                     $args[] = $extraData;
00327                 $retval = call_user_func_array( $retrieveCallback, $args );
00328                 if ( !( $retval instanceof eZClusterFileFailure ) )
00329                 {
00330                     return $retval;
00331                 }
00332                 $forceGeneration = true;
00333             }
00334 
00335             if ( $tries >= 2 )
00336             {
00337                 eZDebugSetting::writeDebug( 'kernel-clustering', "Reading was retried $tries times and reached the maximum, forcing generation", __METHOD__ );
00338                 $forceGeneration = true; // We will now generate the cache but not store it
00339                 $storeCache = false; // This disables the cache storage
00340             }
00341 
00342             // Generation part starts here
00343             if ( isset( $retval ) &&
00344                  $retval instanceof eZClusterFileFailure )
00345             {
00346                 // This error means that the retrieve callback told
00347                 // us NOT to enter generation mode and therefore NOT to store this
00348                 // cache.
00349                 // This parameter will then be passed to the generate callback,
00350                 // and this will set store to false
00351                 if ( $retval->errno() == 3 )
00352                 {
00353                     $noCache = true;
00354                 }
00355                 elseif ( $retval->errno() != 1 ) // check for non-expiry error codes
00356                 {
00357                     eZDebug::writeError( "Failed to retrieve data from callback", __METHOD__ );
00358                     return null;
00359                 }
00360                 $message = $retval->message();
00361                 if ( strlen( $message ) > 0 )
00362                 {
00363                     eZDebugSetting::writeDebug( 'kernel-clustering', $retval->message(), __METHOD__ );
00364                 }
00365                 // the retrieved data was expired so we need to generate it, let's continue
00366             }
00367 
00368             // We need to lock if we have a generate-callback or
00369             // the generation is deferred to the caller.
00370             // Note: false means no generation
00371             if ( !$noCache and $generateCallback !== false and $forceGeneration === false )
00372             {
00373                 // Lock the entry for exclusive access, if the entry does not exist
00374                 // it will be inserted with mtime=-1
00375                 if ( !$this->_exclusiveLock( $fname ) )
00376                 {
00377                     // Cannot get exclusive lock, so return null.
00378                     return null;
00379                 }
00380 
00381                 // This is where we perform a two-phase commit. If any other
00382                 // process or machine has generated the file data and it is valid
00383                 // we will retry the retrieval part and not do the generation.
00384                 clearstatcache();
00385                 eZDebugSetting::writeDebug( 'kernel-clustering', "clearstatcache called on $fname", __METHOD__ );
00386                 if ( file_exists( $fname ) && !$this->isExpired( $expiry, $curtime, $ttl ) )
00387                 {
00388                     eZDebugSetting::writeDebug( 'kernel-clustering', "File was generated while we were locked, use that instead", __METHOD__ );
00389                     $this->metaData = false;
00390                     $this->_freeExclusiveLock( 'storeCache' );
00391                     ++$tries;
00392                     continue; // retry reading file
00393                 }
00394             }
00395 
00396             // File in DB is outdated or non-existing, call write-callback to generate content
00397             if ( $generateCallback )
00398             {
00399                 $args = array( $fname );
00400                 if ( $noCache )
00401                     $extraData['noCache'] = true;
00402                 if ( $extraData !== null )
00403                     $args[] = $extraData;
00404                 $fileData = call_user_func_array( $generateCallback, $args );
00405                 return $this->storeCache( $fileData, $storeCache );
00406             }
00407 
00408             break;
00409         }
00410 
00411         return new eZClusterFileFailure( 2, "Manual generation of file data is required, calling storeCache is required" );
00412     }
00413 
00414     /**
00415      * Calculates if the file data is expired or not.
00416      *
00417      * @param string $fname Name of file, available for easy debugging.
00418      * @param int    $mtime Modification time of file, can be set to false if file does not exist.
00419      * @param int    $expiry Time when file is to be expired, a value of -1 will disable this check.
00420      * @param int    $curtime The current time to check against.
00421      * @param int    $ttl Number of seconds the data can live, set to null to disable TTL.
00422      * @return bool
00423      */
00424     public static function isFileExpired( $fname, $mtime, $expiry, $curtime, $ttl )
00425     {
00426         if ( $mtime == false )
00427         {
00428             $ret = true;
00429         }
00430         elseif ( $mtime == self::EXPIRY_TIMESTAMP )
00431         {
00432             $ret = true;
00433         }
00434         elseif ( $expiry != -1 and $ttl === null )
00435         {
00436             $ret = $mtime < $expiry;
00437         }
00438         else
00439         {
00440             $ret = $mtime < max( $expiry, $curtime - $ttl );
00441         }
00442         return $ret;
00443     }
00444 
00445 
00446     /**
00447      * Calculates if the current file data is expired or not.
00448      *
00449      * @param int    $expiry Time when file is to be expired, a value of -1 will disable this check.
00450      * @param int    $curtime The current time to check against.
00451      * @param int    $ttl Number of seconds the data can live, set to null to disable TTL.
00452      * @return bool
00453      */
00454     public function isExpired( $expiry, $curtime, $ttl )
00455     {
00456         if ( !file_exists( $this->filePath ) )
00457         {
00458             return true;
00459         }
00460 
00461         return self::isFileExpired( $this->filePath, filemtime( $this->filePath ), $expiry, $curtime, $ttl );
00462     }
00463 
00464     /*!
00465      \private
00466      Stores the data in $fileData to the remote and local file and commits the transaction.
00467 
00468      The parameter $fileData must contain the same as information as the $generateCallback returns as explained in processCache().
00469 
00470      \note This method is just a continuation of the code in processCache() and is not meant to be called alone since it relies on specific state in the database.
00471      */
00472     function storeCache( $fileData, $storeCache = true )
00473     {
00474         $fname = $this->filePath;
00475 
00476         $scope       = false;
00477         $datatype    = false;
00478         $binaryData  = null;
00479         $fileContent = null;
00480         $store       = true;
00481         if ( is_array( $fileData ) )
00482         {
00483             if ( isset( $fileData['scope'] ) )
00484                 $scope = $fileData['scope'];
00485             if ( isset( $fileData['datatype'] ) )
00486                 $datatype = $fileData['datatype'];
00487             if ( isset( $fileData['content'] ) )
00488                 $fileContent = $fileData['content'];
00489             if ( isset( $fileData['binarydata'] ) )
00490                 $binaryData = $fileData['binarydata'];
00491             if ( isset( $fileData['store'] ) )
00492                 $store = $fileData['store'];
00493         }
00494         else
00495             $binaryData = $fileData;
00496 
00497         // Disable storage if the caller of the function says so
00498         if ( !$storeCache )
00499             $store = false;
00500 
00501         $mtime = false;
00502         $result = null;
00503         if ( $binaryData === null &&
00504              $fileContent === null )
00505         {
00506             eZDebug::writeError( "Write callback need to set the 'content' or 'binarydata' entry" );
00507             return null;
00508         }
00509 
00510         if ( $binaryData === null )
00511             $binaryData = "<" . "?php\n\treturn ". var_export( $fileContent, true ) . ";\n?" . ">\n";
00512 
00513         if ( $fileContent === null )
00514             $result = $binaryData;
00515         else
00516             $result = $fileContent;
00517 
00518         // Check if we are allowed to store the data, if not just return the result
00519         if ( !$store )
00520         {
00521             $this->abortCacheGeneration();
00522             return $result;
00523         }
00524 
00525         // Store content locally
00526         $this->storeContents( $binaryData, $scope, $datatype, true );
00527 
00528         $this->_freeExclusiveLock( 'storeCache' );
00529 
00530         return $result;
00531     }
00532 
00533     /*!
00534      Provides access to the file contents by downloading the file locally and
00535      calling $callback with the local filename. The callback can then process the
00536      contents and return the data in the same way as in processCache().
00537      Downloading is only done once so the local copy is kept, while updates to the
00538      remote DB entry is synced with the local one.
00539 
00540      The parameters $expiry and $extraData is the same as for processCache().
00541 
00542      \note Unlike processCache() this returns null if the file cannot be accessed.
00543      */
00544     function processFile( $callback, $expiry = false, $extraData = null )
00545     {
00546         $result = $this->processCache( $callback, false, null, $expiry, $extraData );
00547         if ( $result instanceof eZClusterFileFailure )
00548         {
00549             return null;
00550         }
00551         return $result;
00552     }
00553 
00554     /**
00555      * Returns file metadata.
00556      *
00557      * \public
00558      */
00559     function stat()
00560     {
00561         eZDebugSetting::writeDebug( 'kernel-clustering', $this->metaData, "fs::stat( {$this->filePath} )" );
00562         return $this->metaData;
00563     }
00564 
00565     /**
00566      * Returns file size.
00567      *
00568      * \public
00569      */
00570     function size()
00571     {
00572         $size = isset( $this->metaData['size'] ) ? $this->metaData['size'] : null;
00573         eZDebugSetting::writeDebug( 'kernel-clustering', $size, "fs::size( {$this->filePath} )" );
00574         return $size;
00575     }
00576 
00577     /**
00578      * Returns file modification time.
00579      *
00580      * \public
00581      */
00582     function mtime()
00583     {
00584         $mtime = isset( $this->metaData['mtime'] ) ? $this->metaData['mtime'] : null;
00585         eZDebugSetting::writeDebug( 'kernel-clustering', $mtime, "fs::mtime( {$this->filePath} )" );
00586         return $mtime;
00587     }
00588 
00589     /**
00590      * Returns file name.
00591      *
00592      * \public
00593      */
00594     function name()
00595     {
00596         eZDebugSetting::writeDebug( 'kernel-clustering', $this->filePath, "fs::name( {$this->filePath} )" );
00597         return $this->filePath;
00598     }
00599 
00600     /**
00601      * Delete files matching regex $fileRegex under directory $dir.
00602      *
00603      * \public
00604      * \static
00605      * \sa fileDeleteByWildcard()
00606      */
00607     function fileDeleteByRegex( $dir, $fileRegex )
00608     {
00609         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDeleteByRegex( '$dir', '$fileRegex' )", __METHOD__ );
00610 
00611         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00612 
00613         if ( !file_exists( $dir ) )
00614         {
00615             //eZDebugSetting::writeDebug( 'kernel-clustering', "Dir '$dir' does not exist", __METHOD__ );
00616             eZDebug::accumulatorStop( 'dbfile' );
00617             return;
00618         }
00619 
00620         $dirHandle = opendir( $dir );
00621         if ( !$dirHandle )
00622         {
00623             eZDebug::writeError( "opendir( '$dir' ) failed." );
00624             eZDebug::accumulatorStop( 'dbfile' );
00625             return;
00626         }
00627 
00628         while ( ( $file = readdir( $dirHandle ) ) !== false )
00629         {
00630             if ( $file == '.' or
00631                  $file == '..' )
00632                 continue;
00633             if ( preg_match( "/^$fileRegex/", $file ) )
00634             {
00635                 //eZDebugSetting::writeDebug( 'kernel-clustering', "\$file = eZDir::path( array( '$dir', '$file' ) );", __METHOD__ );
00636                 $file = eZDir::path( array( $dir, $file ) );
00637                 eZDebugSetting::writeDebug( 'kernel-clustering', "Removing cache file '$file'", __METHOD__ );
00638                 unlink( $file );
00639 
00640                 // Write log message to storage.log
00641                 eZLog::writeStorageLog( $file );
00642             }
00643         }
00644         closedir( $dirHandle );
00645 
00646         eZDebug::accumulatorStop( 'dbfile' );
00647     }
00648 
00649     /**
00650      * Delete files matching given wildcard.
00651      *
00652      * Note that this method is faster than fileDeleteByRegex().
00653      *
00654      * \public
00655      * \static
00656      * \sa fileDeleteByRegex()
00657      */
00658     function fileDeleteByWildcard( $wildcard )
00659     {
00660         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDeleteByWildcard( '$wildcard' )", __METHOD__ );
00661 
00662         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00663         $unlinkArray = eZSys::globBrace( $wildcard );
00664         if ( $unlinkArray !== false and ( count( $unlinkArray ) > 0 ) )
00665         {
00666             array_map( 'unlink', $unlinkArray );
00667         }
00668         eZDebug::accumulatorStop( 'dbfile' );
00669     }
00670 
00671 
00672     /**
00673      * Delete files located in a directories from dirList, with common prefix specified by
00674      * commonPath, and common suffix with added wildcard at the end
00675      *
00676      * \public
00677      * \static
00678      * \sa fileDeleteByRegex()
00679      */
00680     function fileDeleteByDirList( $dirList, $commonPath, $commonSuffix )
00681     {
00682         $dirs = implode( ',', $dirList );
00683         $wildcard = $commonPath .'/{' . $dirs . '}/' . $commonSuffix . '*';
00684 
00685         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDeleteByDirList( '$dirs', '$commonPath', '$commonSuffix' )", __METHOD__ );
00686 
00687         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00688         $unlinkArray = eZSys::globBrace( $wildcard );
00689         if ( $unlinkArray !== false and ( count( $unlinkArray ) > 0 ) )
00690         {
00691             array_map( 'unlink', $unlinkArray );
00692         }
00693         eZDebug::accumulatorStop( 'dbfile' );
00694     }
00695 
00696     /**
00697      * \public
00698      * \static
00699      */
00700     function fileDelete( $path, $fnamePart = false )
00701     {
00702         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDelete( '$path' )", __METHOD__ );
00703 
00704         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00705 
00706         $list = array();
00707         if ( $fnamePart !== false )
00708         {
00709             $globResult = glob( $path . "/" . $fnamePart . "*" );
00710             if ( is_array( $globResult ) )
00711             {
00712                 $list = $globResult;
00713             }
00714         }
00715         else
00716         {
00717             $list = array( $path );
00718         }
00719 
00720         foreach ( $list as $path )
00721         {
00722             if ( is_file( $path ) )
00723             {
00724                 $handler = eZFileHandler::instance( false );
00725                 $handler->unlink( $path );
00726                 if ( file_exists( $path ) )
00727                     eZDebug::writeError( "File still exists after removal: '$path'", __METHOD__ );
00728             }
00729             else
00730             {
00731                 eZDir::recursiveDelete( $path );
00732             }
00733         }
00734 
00735         eZDebug::accumulatorStop( 'dbfile' );
00736     }
00737 
00738     /**
00739      * Deletes specified file/directory.
00740      *
00741      * If a directory specified it is deleted recursively.
00742      *
00743      * \public
00744      * \static
00745      */
00746     function delete()
00747     {
00748         $path = $this->filePath;
00749         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::delete( '$path' )", __METHOD__ );
00750 
00751         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00752 
00753         if ( is_file( $path ) )
00754         {
00755             $handler = eZFileHandler::instance( false );
00756             $handler->unlink( $path );
00757             if ( file_exists( $path ) )
00758                 eZDebug::writeError( "File still exists after removal: '$path'", __METHOD__ );
00759         }
00760         elseif ( is_dir( $path ) )
00761         {
00762             eZDir::recursiveDelete( $path );
00763         }
00764 
00765         eZClusterFileHandler::cleanupEmptyDirectories( $path );
00766 
00767         $this->loadMetaData( true );
00768 
00769         eZDebug::accumulatorStop( 'dbfile' );
00770     }
00771 
00772 
00773     /**
00774      * Deletes a file that has been fetched before.
00775      *
00776      * @see fetchUnique
00777      *
00778      * In case of fetching from filesystem does nothing.
00779      */
00780     function fileDeleteLocal( $path )
00781     {
00782         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileDeleteLocal( '$path' )", __METHOD__ );
00783     }
00784 
00785     /**
00786      * Deletes a file that has been fetched before.
00787      *
00788      * In case of fetching from filesystem does nothing.
00789      *
00790      * \public
00791      */
00792     function deleteLocal()
00793     {
00794         $path = $this->filePath;
00795         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::deleteLocal( '$path' )", __METHOD__ );
00796     }
00797 
00798     /*!
00799      Purge local and remote file data for current file.
00800      */
00801     function purge( $printCallback = false, $microsleep = false, $max = false, $expiry = false )
00802     {
00803         $file = $this->filePath;
00804         if ( $max === false )
00805             $max = 100;
00806         $count = 0;
00807         $list = array();
00808         if ( is_file( $file ) )
00809         {
00810             $list = array( $file );
00811         }
00812         else
00813         {
00814             $globResult = glob( $file . "/*" );
00815             if ( is_array( $globResult ) )
00816             {
00817                 $list = $globResult;
00818             }
00819         }
00820         do
00821         {
00822             if ( ( $count % $max ) == 0 && $microsleep )
00823                 usleep( $microsleep ); // Sleep a bit to make the filesystem happier
00824 
00825             $count = 0;
00826             $file = array_shift( $list );
00827 
00828             if ( is_file( $file ) )
00829             {
00830                 $mtime = @filemtime( $file );
00831                 if ( $expiry === false ||
00832                      $mtime < $expiry ) // remove it if it is too old
00833                 {
00834                     @unlink( $file );
00835 
00836                     eZClusterFileHandler::cleanupEmptyDirectories( $file );
00837                 }
00838                 ++$count;
00839             }
00840             else if ( is_dir( $file ) )
00841             {
00842                 $globResult = glob( $file . "/*" );
00843                 if ( is_array( $globResult ) )
00844                 {
00845                     $list = array_merge( $list, $globResult );
00846                 }
00847             }
00848 
00849             if ( $printCallback )
00850                 call_user_func_array( $printCallback,
00851                                       array( $file, 1 ) );
00852         } while ( count( $list ) > 0 );
00853     }
00854 
00855     /**
00856      * Check if given file/dir exists.
00857      *
00858      * \public
00859      * \static
00860      */
00861     function fileExists( $path )
00862     {
00863         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileExists( '$path' )", __METHOD__ );
00864 
00865         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00866         $rc = file_exists( $path );
00867         eZDebug::accumulatorStop( 'dbfile' );
00868 
00869         return $rc;
00870     }
00871 
00872     /**
00873      * Check if given file/dir exists.
00874      *
00875      * NOTE: this function does not interact with filesystem.
00876      * Instead, it just returns existance status determined in the constructor.
00877      *
00878      * \public
00879      */
00880     function exists()
00881     {
00882         $path = $this->filePath;
00883         $rc = isset( $this->metaData['mtime'] );
00884         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::exists( '$path' ): " . ( $rc ? 'true' :'false' ), __METHOD__ );
00885         return $rc;
00886     }
00887 
00888     /**
00889      * Outputs file contents to the browser
00890      * Note: does not handle headers. eZFile::downloadHeaders() can be used for this
00891      *
00892      * @param int $offset Transfer start offset
00893      * @param int $length Transfer length, in bytes
00894      */
00895     function passthrough( $offset = 0, $length = false )
00896     {
00897         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::passthrough( '{$this->filePath}' )", __METHOD__ );
00898         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00899 
00900         eZFile::downloadContent( $this->filePath, $offset, $length );
00901 
00902         eZDebug::accumulatorStop( 'dbfile' );
00903     }
00904 
00905     /**
00906      * Copy file.
00907      *
00908      * \public
00909      * \static
00910      */
00911     function fileCopy( $srcPath, $dstPath )
00912     {
00913         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileCopy( '$srcPath', '$dstPath' )", __METHOD__ );
00914 
00915         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00916         eZFileHandler::copy( $srcPath, $dstPath );
00917         eZDebug::accumulatorStop( 'dbfile' );
00918     }
00919 
00920     /**
00921      * Create symbolic or hard link to file.
00922      *
00923      * \public
00924      * \static
00925      */
00926     function fileLinkCopy( $srcPath, $dstPath, $symLink )
00927     {
00928         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileLinkCopy( '$srcPath', '$dstPath' )", __METHOD__ );
00929 
00930         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00931         eZFileHandler::linkCopy( $srcPath, $dstPath, $symLink );
00932         eZDebug::accumulatorStop( 'dbfile' );
00933     }
00934 
00935     /**
00936      * Move file.
00937      *
00938      * \public
00939      * \static
00940      */
00941     function fileMove( $srcPath, $dstPath )
00942     {
00943         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::fileMove( '$srcPath', '$dstPath' )", __METHOD__ );
00944 
00945         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00946         eZFileHandler::move( $srcPath, $dstPath );
00947         eZDebug::accumulatorStop( 'dbfile' );
00948     }
00949 
00950     /**
00951      * Move file.
00952      *
00953      * \public
00954      */
00955     function move( $dstPath )
00956     {
00957         $srcPath = $this->filePath;
00958 
00959         eZDebugSetting::writeDebug( 'kernel-clustering', "fs::move( '$srcPath', '$dstPath' )", __METHOD__ );
00960 
00961         eZDebug::accumulatorStart( 'dbfile', false, 'dbfile' );
00962         eZFileHandler::move( $srcPath, $dstPath );
00963         eZDebug::accumulatorStop( 'dbfile' );
00964     }
00965 
00966     /**
00967      * Starts cache generation for the current file.
00968      *
00969      * This is done by creating a file named by the original file name, prefixed
00970      * with '.generating'.
00971      *
00972      * @todo add timeout handling...
00973      *
00974      * @return mixed true if generation lock was granted, an integer matching the
00975      *               time before the current generation times out
00976      */
00977     public function startCacheGeneration()
00978     {
00979         return true;
00980     }
00981 
00982     /**
00983      * Ends the cache generation started by startCacheGeneration().
00984      */
00985     public function endCacheGeneration()
00986     {
00987         return true;
00988     }
00989 
00990     /**
00991      * Aborts the current cache generation process.
00992      *
00993      * Does so by rolling back the current transaction, which should be the
00994      * .generating file lock
00995      */
00996     public function abortCacheGeneration()
00997     {
00998         return true;
00999     }
01000 
01001     /**
01002      * Checks if the .generating file was changed, which would mean that generation
01003      * timed out. If not timed out, refreshes the timestamp so that storage won't
01004      * be stolen
01005      */
01006     public function checkCacheGenerationTimeout()
01007     {
01008         return true;
01009     }
01010 
01011     /**
01012      * eZFS only stores data to FS and doesn't require/support clusterizing
01013      *
01014      * @return bool false
01015      */
01016     public function requiresClusterizing()
01017     {
01018         return false;
01019     }
01020 
01021     /**
01022      * eZFS does not require binary purge.
01023      * Files are stored on plain FS and removed using FS functions
01024      *
01025      * @since 4.3
01026      * @deprecated Deprecated as of 4.5, use {@link eZFSFileHandler::requiresPurge()} instead.
01027      * @return bool
01028      */
01029     public function requiresBinaryPurge()
01030     {
01031         return false;
01032     }
01033 
01034     /**
01035      * eZFS does not require binary purge.
01036      * Files are stored on plain FS and removed using FS functions
01037      *
01038      * @since 4.5.0
01039      * @return bool
01040      */
01041     public function requiresPurge()
01042     {
01043         return false;
01044     }
01045 
01046     public function hasStaleCacheSupport()
01047     {
01048         return false;
01049     }
01050 
01051     public $metaData = null;
01052     public $filePath;
01053 }
01054 
01055 ?>