]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/cache/FileCacheBase.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / cache / FileCacheBase.php
1 <?php
2 /**
3  * Data storage in the file system.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup Cache
22  */
23
24 /**
25  * Base class for data storage in the file system.
26  *
27  * @ingroup Cache
28  */
29 abstract class FileCacheBase {
30         protected $mKey;
31         protected $mType = 'object';
32         protected $mExt = 'cache';
33         protected $mFilePath;
34         protected $mUseGzip;
35         /* lazy loaded */
36         protected $mCached;
37
38         /* @todo configurable? */
39         const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
40         const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
41
42         protected function __construct() {
43                 global $wgUseGzip;
44
45                 $this->mUseGzip = (bool)$wgUseGzip;
46         }
47
48         /**
49          * Get the base file cache directory
50          * @return string
51          */
52         final protected function baseCacheDirectory() {
53                 global $wgFileCacheDirectory;
54
55                 return $wgFileCacheDirectory;
56         }
57
58         /**
59          * Get the base cache directory (not specific to this file)
60          * @return string
61          */
62         abstract protected function cacheDirectory();
63
64         /**
65          * Get the path to the cache file
66          * @return string
67          */
68         protected function cachePath() {
69                 if ( $this->mFilePath !== null ) {
70                         return $this->mFilePath;
71                 }
72
73                 $dir = $this->cacheDirectory();
74                 # Build directories (methods include the trailing "/")
75                 $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
76                 # Avoid extension confusion
77                 $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
78                 # Build the full file path
79                 $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
80                 if ( $this->useGzip() ) {
81                         $this->mFilePath .= '.gz';
82                 }
83
84                 return $this->mFilePath;
85         }
86
87         /**
88          * Check if the cache file exists
89          * @return bool
90          */
91         public function isCached() {
92                 if ( $this->mCached === null ) {
93                         $this->mCached = file_exists( $this->cachePath() );
94                 }
95
96                 return $this->mCached;
97         }
98
99         /**
100          * Get the last-modified timestamp of the cache file
101          * @return string|bool TS_MW timestamp
102          */
103         public function cacheTimestamp() {
104                 $timestamp = filemtime( $this->cachePath() );
105
106                 return ( $timestamp !== false )
107                         ? wfTimestamp( TS_MW, $timestamp )
108                         : false;
109         }
110
111         /**
112          * Check if up to date cache file exists
113          * @param string $timestamp MW_TS timestamp
114          *
115          * @return bool
116          */
117         public function isCacheGood( $timestamp = '' ) {
118                 global $wgCacheEpoch;
119
120                 if ( !$this->isCached() ) {
121                         return false;
122                 }
123
124                 $cachetime = $this->cacheTimestamp();
125                 $good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime );
126                 wfDebug( __METHOD__ .
127                         ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" );
128
129                 return $good;
130         }
131
132         /**
133          * Check if the cache is gzipped
134          * @return bool
135          */
136         protected function useGzip() {
137                 return $this->mUseGzip;
138         }
139
140         /**
141          * Get the uncompressed text from the cache
142          * @return string
143          */
144         public function fetchText() {
145                 if ( $this->useGzip() ) {
146                         $fh = gzopen( $this->cachePath(), 'rb' );
147
148                         return stream_get_contents( $fh );
149                 } else {
150                         return file_get_contents( $this->cachePath() );
151                 }
152         }
153
154         /**
155          * Save and compress text to the cache
156          * @param string $text
157          * @return string|false Compressed text
158          */
159         public function saveText( $text ) {
160                 if ( $this->useGzip() ) {
161                         $text = gzencode( $text );
162                 }
163
164                 $this->checkCacheDirs(); // build parent dir
165                 if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
166                         wfDebug( __METHOD__ . "() failed saving " . $this->cachePath() . "\n" );
167                         $this->mCached = null;
168
169                         return false;
170                 }
171
172                 $this->mCached = true;
173
174                 return $text;
175         }
176
177         /**
178          * Clear the cache for this page
179          * @return void
180          */
181         public function clearCache() {
182                 MediaWiki\suppressWarnings();
183                 unlink( $this->cachePath() );
184                 MediaWiki\restoreWarnings();
185                 $this->mCached = false;
186         }
187
188         /**
189          * Create parent directors of $this->cachePath()
190          * @return void
191          */
192         protected function checkCacheDirs() {
193                 wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
194         }
195
196         /**
197          * Get the cache type subdirectory (with trailing slash)
198          * An extending class could use that method to alter the type -> directory
199          * mapping. @see HTMLFileCache::typeSubdirectory() for an example.
200          *
201          * @return string
202          */
203         protected function typeSubdirectory() {
204                 return $this->mType . '/';
205         }
206
207         /**
208          * Return relative multi-level hash subdirectory (with trailing slash)
209          * or the empty string if not $wgFileCacheDepth
210          * @return string
211          */
212         protected function hashSubdirectory() {
213                 global $wgFileCacheDepth;
214
215                 $subdir = '';
216                 if ( $wgFileCacheDepth > 0 ) {
217                         $hash = md5( $this->mKey );
218                         for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
219                                 $subdir .= substr( $hash, 0, $i ) . '/';
220                         }
221                 }
222
223                 return $subdir;
224         }
225
226         /**
227          * Roughly increments the cache misses in the last hour by unique visitors
228          * @param WebRequest $request
229          * @return void
230          */
231         public function incrMissesRecent( WebRequest $request ) {
232                 if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
233                         $cache = ObjectCache::getLocalClusterInstance();
234                         # Get a large IP range that should include the user  even if that
235                         # person's IP address changes
236                         $ip = $request->getIP();
237                         if ( !IP::isValid( $ip ) ) {
238                                 return;
239                         }
240                         $ip = IP::isIPv6( $ip )
241                                 ? IP::sanitizeRange( "$ip/32" )
242                                 : IP::sanitizeRange( "$ip/16" );
243
244                         # Bail out if a request already came from this range...
245                         $key = $cache->makeKey( static::class, 'attempt', $this->mType, $this->mKey, $ip );
246                         if ( $cache->get( $key ) ) {
247                                 return; // possibly the same user
248                         }
249                         $cache->set( $key, 1, self::MISS_TTL_SEC );
250
251                         # Increment the number of cache misses...
252                         $key = $this->cacheMissKey( $cache );
253                         if ( $cache->get( $key ) === false ) {
254                                 $cache->set( $key, 1, self::MISS_TTL_SEC );
255                         } else {
256                                 $cache->incr( $key );
257                         }
258                 }
259         }
260
261         /**
262          * Roughly gets the cache misses in the last hour by unique visitors
263          * @return int
264          */
265         public function getMissesRecent() {
266                 $cache = ObjectCache::getLocalClusterInstance();
267
268                 return self::MISS_FACTOR * $cache->get( $this->cacheMissKey( $cache ) );
269         }
270
271         /**
272          * @param BagOStuff $cache Instance that the key will be used with
273          * @return string
274          */
275         protected function cacheMissKey( BagOStuff $cache ) {
276                 return $cache->makeKey( static::class, 'misses', $this->mType, $this->mKey );
277         }
278 }