|
eZ Publish
[trunk]
|
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 ?>