MediaWiki
REL1_24
|
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 00055 return $wgFileCacheDirectory; 00056 } 00057 00062 abstract protected function cacheDirectory(); 00063 00068 protected function cachePath() { 00069 if ( $this->mFilePath !== null ) { 00070 return $this->mFilePath; 00071 } 00072 00073 $dir = $this->cacheDirectory(); 00074 # Build directories (methods include the trailing "/") 00075 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory(); 00076 # Avoid extension confusion 00077 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) ); 00078 # Build the full file path 00079 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}"; 00080 if ( $this->useGzip() ) { 00081 $this->mFilePath .= '.gz'; 00082 } 00083 00084 return $this->mFilePath; 00085 } 00086 00091 public function isCached() { 00092 if ( $this->mCached === null ) { 00093 $this->mCached = file_exists( $this->cachePath() ); 00094 } 00095 00096 return $this->mCached; 00097 } 00098 00103 public function cacheTimestamp() { 00104 $timestamp = filemtime( $this->cachePath() ); 00105 00106 return ( $timestamp !== false ) 00107 ? wfTimestamp( TS_MW, $timestamp ) 00108 : false; 00109 } 00110 00117 public function isCacheGood( $timestamp = '' ) { 00118 global $wgCacheEpoch; 00119 00120 if ( !$this->isCached() ) { 00121 return false; 00122 } 00123 00124 $cachetime = $this->cacheTimestamp(); 00125 $good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime ); 00126 wfDebug( __METHOD__ . 00127 ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" ); 00128 00129 return $good; 00130 } 00131 00136 protected function useGzip() { 00137 return $this->mUseGzip; 00138 } 00139 00144 public function fetchText() { 00145 if ( $this->useGzip() ) { 00146 $fh = gzopen( $this->cachePath(), 'rb' ); 00147 00148 return stream_get_contents( $fh ); 00149 } else { 00150 return file_get_contents( $this->cachePath() ); 00151 } 00152 } 00153 00159 public function saveText( $text ) { 00160 global $wgUseFileCache; 00161 00162 if ( !$wgUseFileCache ) { 00163 return false; 00164 } 00165 00166 if ( $this->useGzip() ) { 00167 $text = gzencode( $text ); 00168 } 00169 00170 $this->checkCacheDirs(); // build parent dir 00171 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) { 00172 wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() . "\n" ); 00173 $this->mCached = null; 00174 00175 return false; 00176 } 00177 00178 $this->mCached = true; 00179 00180 return $text; 00181 } 00182 00187 public function clearCache() { 00188 wfSuppressWarnings(); 00189 unlink( $this->cachePath() ); 00190 wfRestoreWarnings(); 00191 $this->mCached = false; 00192 } 00193 00198 protected function checkCacheDirs() { 00199 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ ); 00200 } 00201 00209 protected function typeSubdirectory() { 00210 return $this->mType . '/'; 00211 } 00212 00218 protected function hashSubdirectory() { 00219 global $wgFileCacheDepth; 00220 00221 $subdir = ''; 00222 if ( $wgFileCacheDepth > 0 ) { 00223 $hash = md5( $this->mKey ); 00224 for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) { 00225 $subdir .= substr( $hash, 0, $i ) . '/'; 00226 } 00227 } 00228 00229 return $subdir; 00230 } 00231 00237 public function incrMissesRecent( WebRequest $request ) { 00238 global $wgMemc; 00239 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) { 00240 # Get a large IP range that should include the user even if that 00241 # person's IP address changes 00242 $ip = $request->getIP(); 00243 if ( !IP::isValid( $ip ) ) { 00244 return; 00245 } 00246 $ip = IP::isIPv6( $ip ) 00247 ? IP::sanitizeRange( "$ip/32" ) 00248 : IP::sanitizeRange( "$ip/16" ); 00249 00250 # Bail out if a request already came from this range... 00251 $key = wfMemcKey( get_class( $this ), 'attempt', $this->mType, $this->mKey, $ip ); 00252 if ( $wgMemc->get( $key ) ) { 00253 return; // possibly the same user 00254 } 00255 $wgMemc->set( $key, 1, self::MISS_TTL_SEC ); 00256 00257 # Increment the number of cache misses... 00258 $key = $this->cacheMissKey(); 00259 if ( $wgMemc->get( $key ) === false ) { 00260 $wgMemc->set( $key, 1, self::MISS_TTL_SEC ); 00261 } else { 00262 $wgMemc->incr( $key ); 00263 } 00264 } 00265 } 00266 00271 public function getMissesRecent() { 00272 global $wgMemc; 00273 00274 return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() ); 00275 } 00276 00280 protected function cacheMissKey() { 00281 return wfMemcKey( get_class( $this ), 'misses', $this->mType, $this->mKey ); 00282 } 00283 }