MediaWiki  REL1_20
FileCacheBase.php
Go to the documentation of this file.
00001 <?php
00029 abstract class FileCacheBase {
00030         protected $mKey;
00031         protected $mType = 'object';
00032         protected $mExt = 'cache';
00033         protected $mFilePath;
00034         protected $mUseGzip;
00035         /* lazy loaded */
00036         protected $mCached;
00037 
00038         /* @TODO: configurable? */
00039         const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
00040         const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
00041 
00042         protected function __construct() {
00043                 global $wgUseGzip;
00044 
00045                 $this->mUseGzip = (bool)$wgUseGzip;
00046         }
00047 
00052         final protected function baseCacheDirectory() {
00053                 global $wgFileCacheDirectory;
00054                 return $wgFileCacheDirectory;
00055         }
00056 
00061         abstract protected function cacheDirectory();
00062 
00067         protected function cachePath() {
00068                 if ( $this->mFilePath !== null ) {
00069                         return $this->mFilePath;
00070                 }
00071 
00072                 $dir = $this->cacheDirectory();
00073                 # Build directories (methods include the trailing "/")
00074                 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
00075                 # Avoid extension confusion
00076                 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
00077                 # Build the full file path
00078                 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
00079                 if ( $this->useGzip() ) {
00080                         $this->mFilePath .= '.gz';
00081                 }
00082 
00083                 return $this->mFilePath;
00084         }
00085 
00090         public function isCached() {
00091                 if ( $this->mCached === null ) {
00092                         $this->mCached = file_exists( $this->cachePath() );
00093                 }
00094                 return $this->mCached;
00095         }
00096 
00101         public function cacheTimestamp() {
00102                 $timestamp = filemtime( $this->cachePath() );
00103                 return ( $timestamp !== false )
00104                         ? wfTimestamp( TS_MW, $timestamp )
00105                         : false;
00106         }
00107 
00114         public function isCacheGood( $timestamp = '' ) {
00115                 global $wgCacheEpoch;
00116 
00117                 if ( !$this->isCached() ) {
00118                         return false;
00119                 }
00120 
00121                 $cachetime = $this->cacheTimestamp();
00122                 $good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime );
00123                 wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" );
00124 
00125                 return $good;
00126         }
00127 
00132         protected function useGzip() {
00133                 return $this->mUseGzip;
00134         }
00135 
00140         public function fetchText() {
00141                 if( $this->useGzip() ) {
00142                         $fh = gzopen( $this->cachePath(), 'rb' );
00143                         return stream_get_contents( $fh );
00144                 } else {
00145                         return file_get_contents( $this->cachePath() );
00146                 }
00147         }
00148 
00153         public function saveText( $text ) {
00154                 global $wgUseFileCache;
00155 
00156                 if ( !$wgUseFileCache ) {
00157                         return false;
00158                 }
00159 
00160                 if ( $this->useGzip() ) {
00161                         $text = gzencode( $text );
00162                 }
00163 
00164                 $this->checkCacheDirs(); // build parent dir
00165                 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
00166                         wfDebug( __METHOD__ . "() failed saving ". $this->cachePath() . "\n");
00167                         $this->mCached = null;
00168                         return false;
00169                 }
00170 
00171                 $this->mCached = true;
00172                 return $text;
00173         }
00174 
00179         public function clearCache() {
00180                 wfSuppressWarnings();
00181                 unlink( $this->cachePath() );
00182                 wfRestoreWarnings();
00183                 $this->mCached = false;
00184         }
00185 
00190         protected function checkCacheDirs() {
00191                 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
00192         }
00193 
00201         protected function typeSubdirectory() {
00202                 return $this->mType . '/';
00203         }
00204 
00210         protected function hashSubdirectory() {
00211                 global $wgFileCacheDepth;
00212 
00213                 $subdir = '';
00214                 if ( $wgFileCacheDepth > 0 ) {
00215                         $hash = md5( $this->mKey );
00216                         for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
00217                                 $subdir .= substr( $hash, 0, $i ) . '/';
00218                         }
00219                 }
00220 
00221                 return $subdir;
00222         }
00223 
00229         public function incrMissesRecent( WebRequest $request ) {
00230                 global $wgMemc;
00231                 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
00232                         # Get a large IP range that should include the user  even if that 
00233                         # person's IP address changes
00234                         $ip = $request->getIP();
00235                         if ( !IP::isValid( $ip ) ) {
00236                                 return;
00237                         }
00238                         $ip = IP::isIPv6( $ip )
00239                                 ? IP::sanitizeRange( "$ip/32" )
00240                                 : IP::sanitizeRange( "$ip/16" );
00241 
00242                         # Bail out if a request already came from this range...
00243                         $key = wfMemcKey( get_class( $this ), 'attempt', $this->mType, $this->mKey, $ip );
00244                         if ( $wgMemc->get( $key ) ) {
00245                                 return; // possibly the same user
00246                         }
00247                         $wgMemc->set( $key, 1, self::MISS_TTL_SEC );
00248 
00249                         # Increment the number of cache misses...
00250                         $key = $this->cacheMissKey();
00251                         if ( $wgMemc->get( $key ) === false ) {
00252                                 $wgMemc->set( $key, 1, self::MISS_TTL_SEC );
00253                         } else {
00254                                 $wgMemc->incr( $key );
00255                         }
00256                 }
00257         }
00258 
00263         public function getMissesRecent() {
00264                 global $wgMemc;
00265                 return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() );
00266         }
00267 
00271         protected function cacheMissKey() {
00272                 return wfMemcKey( get_class( $this ), 'misses', $this->mType, $this->mKey );
00273         }
00274 }