MediaWiki  REL1_19
FileCacheBase.php
Go to the documentation of this file.
00001 <?php
00007 abstract class FileCacheBase {
00008         protected $mKey;
00009         protected $mType = 'object';
00010         protected $mExt = 'cache';
00011         protected $mFilePath;
00012         protected $mUseGzip;
00013         /* lazy loaded */
00014         protected $mCached;
00015 
00016         /* @TODO: configurable? */
00017         const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
00018         const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
00019 
00020         protected function __construct() {
00021                 global $wgUseGzip;
00022 
00023                 $this->mUseGzip = (bool)$wgUseGzip;
00024         }
00025 
00030         final protected function baseCacheDirectory() {
00031                 global $wgFileCacheDirectory;
00032                 return $wgFileCacheDirectory;
00033         }
00034 
00039         abstract protected function cacheDirectory();
00040 
00045         protected function cachePath() {
00046                 if ( $this->mFilePath !== null ) {
00047                         return $this->mFilePath;
00048                 }
00049 
00050                 $dir = $this->cacheDirectory();
00051                 # Build directories (methods include the trailing "/")
00052                 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
00053                 # Avoid extension confusion
00054                 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
00055                 # Build the full file path
00056                 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
00057                 if ( $this->useGzip() ) {
00058                         $this->mFilePath .= '.gz';
00059                 }
00060 
00061                 return $this->mFilePath;
00062         }
00063 
00068         public function isCached() {
00069                 if ( $this->mCached === null ) {
00070                         $this->mCached = file_exists( $this->cachePath() );
00071                 }
00072                 return $this->mCached;
00073         }
00074 
00079         public function cacheTimestamp() {
00080                 $timestamp = filemtime( $this->cachePath() );
00081                 return ( $timestamp !== false )
00082                         ? wfTimestamp( TS_MW, $timestamp )
00083                         : false;
00084         }
00085 
00092         public function isCacheGood( $timestamp = '' ) {
00093                 global $wgCacheEpoch;
00094 
00095                 if ( !$this->isCached() ) {
00096                         return false;
00097                 }
00098 
00099                 $cachetime = $this->cacheTimestamp();
00100                 $good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime );
00101                 wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" );
00102 
00103                 return $good;
00104         }
00105 
00110         protected function useGzip() {
00111                 return $this->mUseGzip;
00112         }
00113 
00118         public function fetchText() {
00119                 if( $this->useGzip() ) {
00120                         $fh = gzopen( $this->cachePath(), 'rb' );
00121                         return stream_get_contents( $fh );
00122                 } else {
00123                         return file_get_contents( $this->cachePath() );
00124                 }
00125         }
00126 
00131         public function saveText( $text ) {
00132                 global $wgUseFileCache;
00133 
00134                 if ( !$wgUseFileCache ) {
00135                         return false;
00136                 }
00137 
00138                 if ( $this->useGzip() ) {
00139                         $text = gzencode( $text );
00140                 }
00141 
00142                 $this->checkCacheDirs(); // build parent dir
00143                 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
00144                         wfDebug( __METHOD__ . "() failed saving ". $this->cachePath() . "\n");
00145                         $this->mCached = null;
00146                         return false;
00147                 }
00148 
00149                 $this->mCached = true;
00150                 return $text;
00151         }
00152 
00157         public function clearCache() {
00158                 wfSuppressWarnings();
00159                 unlink( $this->cachePath() );
00160                 wfRestoreWarnings();
00161                 $this->mCached = false;
00162         }
00163 
00168         protected function checkCacheDirs() {
00169                 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
00170         }
00171 
00179         protected function typeSubdirectory() {
00180                 return $this->mType . '/';
00181         }
00182 
00188         protected function hashSubdirectory() {
00189                 global $wgFileCacheDepth;
00190 
00191                 $subdir = '';
00192                 if ( $wgFileCacheDepth > 0 ) {
00193                         $hash = md5( $this->mKey );
00194                         for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
00195                                 $subdir .= substr( $hash, 0, $i ) . '/';
00196                         }
00197                 }
00198 
00199                 return $subdir;
00200         }
00201 
00207         public function incrMissesRecent( WebRequest $request ) {
00208                 global $wgMemc;
00209                 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
00210                         # Get a large IP range that should include the user  even if that 
00211                         # person's IP address changes
00212                         $ip = $request->getIP();
00213                         if ( !IP::isValid( $ip ) ) {
00214                                 return;
00215                         }
00216                         $ip = IP::isIPv6( $ip )
00217                                 ? IP::sanitizeRange( "$ip/32" )
00218                                 : IP::sanitizeRange( "$ip/16" );
00219 
00220                         # Bail out if a request already came from this range...
00221                         $key = wfMemcKey( get_class( $this ), 'attempt', $this->mType, $this->mKey, $ip );
00222                         if ( $wgMemc->get( $key ) ) {
00223                                 return; // possibly the same user
00224                         }
00225                         $wgMemc->set( $key, 1, self::MISS_TTL_SEC );
00226 
00227                         # Increment the number of cache misses...
00228                         $key = $this->cacheMissKey();
00229                         if ( $wgMemc->get( $key ) === false ) {
00230                                 $wgMemc->set( $key, 1, self::MISS_TTL_SEC );
00231                         } else {
00232                                 $wgMemc->incr( $key );
00233                         }
00234                 }
00235         }
00236 
00241         public function getMissesRecent() {
00242                 global $wgMemc;
00243                 return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() );
00244         }
00245 
00249         protected function cacheMissKey() {
00250                 return wfMemcKey( get_class( $this ), 'misses', $this->mType, $this->mKey );
00251         }
00252 }