MediaWiki
REL1_20
|
00001 <?php 00039 class ForeignAPIRepo extends FileRepo { 00040 /* This version string is used in the user agent for requests and will help 00041 * server maintainers in identify ForeignAPI usage. 00042 * Update the version every time you make breaking or significant changes. */ 00043 const VERSION = "2.1"; 00044 00045 var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' ); 00046 /* Check back with Commons after a day */ 00047 var $apiThumbCacheExpiry = 86400; /* 24*60*60 */ 00048 /* Redownload thumbnail files after a month */ 00049 var $fileCacheExpiry = 2592000; /* 86400*30 */ 00050 00051 protected $mQueryCache = array(); 00052 protected $mFileExists = array(); 00053 00057 function __construct( $info ) { 00058 global $wgLocalFileRepo; 00059 parent::__construct( $info ); 00060 00061 // http://commons.wikimedia.org/w/api.php 00062 $this->mApiBase = isset( $info['apibase'] ) ? $info['apibase'] : null; 00063 00064 if( isset( $info['apiThumbCacheExpiry'] ) ) { 00065 $this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry']; 00066 } 00067 if( isset( $info['fileCacheExpiry'] ) ) { 00068 $this->fileCacheExpiry = $info['fileCacheExpiry']; 00069 } 00070 if( !$this->scriptDirUrl ) { 00071 // hack for description fetches 00072 $this->scriptDirUrl = dirname( $this->mApiBase ); 00073 } 00074 // If we can cache thumbs we can guess sane defaults for these 00075 if( $this->canCacheThumbs() && !$this->url ) { 00076 $this->url = $wgLocalFileRepo['url']; 00077 } 00078 if( $this->canCacheThumbs() && !$this->thumbUrl ) { 00079 $this->thumbUrl = $this->url . '/thumb'; 00080 } 00081 } 00082 00091 function newFile( $title, $time = false ) { 00092 if ( $time ) { 00093 return false; 00094 } 00095 return parent::newFile( $title, $time ); 00096 } 00097 00102 function fileExistsBatch( array $files ) { 00103 $results = array(); 00104 foreach ( $files as $k => $f ) { 00105 if ( isset( $this->mFileExists[$k] ) ) { 00106 $results[$k] = true; 00107 unset( $files[$k] ); 00108 } elseif( self::isVirtualUrl( $f ) ) { 00109 # @todo FIXME: We need to be able to handle virtual 00110 # URLs better, at least when we know they refer to the 00111 # same repo. 00112 $results[$k] = false; 00113 unset( $files[$k] ); 00114 } elseif ( FileBackend::isStoragePath( $f ) ) { 00115 $results[$k] = false; 00116 unset( $files[$k] ); 00117 wfWarn( "Got mwstore:// path '$f'." ); 00118 } 00119 } 00120 00121 $data = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ), 00122 'prop' => 'imageinfo' ) ); 00123 if( isset( $data['query']['pages'] ) ) { 00124 $i = 0; 00125 foreach( $files as $key => $file ) { 00126 $results[$key] = $this->mFileExists[$key] = !isset( $data['query']['pages'][$i]['missing'] ); 00127 $i++; 00128 } 00129 } 00130 return $results; 00131 } 00132 00137 function getFileProps( $virtualUrl ) { 00138 return false; 00139 } 00140 00145 function fetchImageQuery( $query ) { 00146 global $wgMemc; 00147 00148 $query = array_merge( $query, 00149 array( 00150 'format' => 'json', 00151 'action' => 'query', 00152 'redirects' => 'true' 00153 ) ); 00154 if ( $this->mApiBase ) { 00155 $url = wfAppendQuery( $this->mApiBase, $query ); 00156 } else { 00157 $url = $this->makeUrl( $query, 'api' ); 00158 } 00159 00160 if( !isset( $this->mQueryCache[$url] ) ) { 00161 $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) ); 00162 $data = $wgMemc->get( $key ); 00163 if( !$data ) { 00164 $data = self::httpGet( $url ); 00165 if ( !$data ) { 00166 return null; 00167 } 00168 $wgMemc->set( $key, $data, 3600 ); 00169 } 00170 00171 if( count( $this->mQueryCache ) > 100 ) { 00172 // Keep the cache from growing infinitely 00173 $this->mQueryCache = array(); 00174 } 00175 $this->mQueryCache[$url] = $data; 00176 } 00177 return FormatJson::decode( $this->mQueryCache[$url], true ); 00178 } 00179 00184 function getImageInfo( $data ) { 00185 if( $data && isset( $data['query']['pages'] ) ) { 00186 foreach( $data['query']['pages'] as $info ) { 00187 if( isset( $info['imageinfo'][0] ) ) { 00188 return $info['imageinfo'][0]; 00189 } 00190 } 00191 } 00192 return false; 00193 } 00194 00199 function findBySha1( $hash ) { 00200 $results = $this->fetchImageQuery( array( 00201 'aisha1base36' => $hash, 00202 'aiprop' => ForeignAPIFile::getProps(), 00203 'list' => 'allimages', ) ); 00204 $ret = array(); 00205 if ( isset( $results['query']['allimages'] ) ) { 00206 foreach ( $results['query']['allimages'] as $img ) { 00207 // 1.14 was broken, doesn't return name attribute 00208 if( !isset( $img['name'] ) ) { 00209 continue; 00210 } 00211 $ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img ); 00212 } 00213 } 00214 return $ret; 00215 } 00216 00225 function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) { 00226 $data = $this->fetchImageQuery( array( 00227 'titles' => 'File:' . $name, 00228 'iiprop' => 'url|timestamp', 00229 'iiurlwidth' => $width, 00230 'iiurlheight' => $height, 00231 'iiurlparam' => $otherParams, 00232 'prop' => 'imageinfo' ) ); 00233 $info = $this->getImageInfo( $data ); 00234 00235 if( $data && $info && isset( $info['thumburl'] ) ) { 00236 wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" ); 00237 $result = $info; 00238 return $info['thumburl']; 00239 } else { 00240 return false; 00241 } 00242 } 00243 00256 function getThumbUrlFromCache( $name, $width, $height, $params = "" ) { 00257 global $wgMemc; 00258 // We can't check the local cache using FileRepo functions because 00259 // we override fileExistsBatch(). We have to use the FileBackend directly. 00260 $backend = $this->getBackend(); // convenience 00261 00262 if ( !$this->canCacheThumbs() ) { 00263 $result = null; // can't pass "null" by reference, but it's ok as default value 00264 return $this->getThumbUrl( $name, $width, $height, $result, $params ); 00265 } 00266 $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name ); 00267 $sizekey = "$width:$height:$params"; 00268 00269 /* Get the array of urls that we already know */ 00270 $knownThumbUrls = $wgMemc->get($key); 00271 if( !$knownThumbUrls ) { 00272 /* No knownThumbUrls for this file */ 00273 $knownThumbUrls = array(); 00274 } else { 00275 if( isset( $knownThumbUrls[$sizekey] ) ) { 00276 wfDebug( __METHOD__ . ': Got thumburl from local cache: ' . 00277 "{$knownThumbUrls[$sizekey]} \n"); 00278 return $knownThumbUrls[$sizekey]; 00279 } 00280 /* This size is not yet known */ 00281 } 00282 00283 $metadata = null; 00284 $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata, $params ); 00285 00286 if( !$foreignUrl ) { 00287 wfDebug( __METHOD__ . " Could not find thumburl\n" ); 00288 return false; 00289 } 00290 00291 // We need the same filename as the remote one :) 00292 $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) ); 00293 if( !$this->validateFilename( $fileName ) ) { 00294 wfDebug( __METHOD__ . " The deduced filename $fileName is not safe\n" ); 00295 return false; 00296 } 00297 $localPath = $this->getZonePath( 'thumb' ) . "/" . $this->getHashPath( $name ) . $name; 00298 $localFilename = $localPath . "/" . $fileName; 00299 $localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName ); 00300 00301 if( $backend->fileExists( array( 'src' => $localFilename ) ) 00302 && isset( $metadata['timestamp'] ) ) 00303 { 00304 wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" ); 00305 $modified = $backend->getFileTimestamp( array( 'src' => $localFilename ) ); 00306 $remoteModified = strtotime( $metadata['timestamp'] ); 00307 $current = time(); 00308 $diff = abs( $modified - $current ); 00309 if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) { 00310 /* Use our current and already downloaded thumbnail */ 00311 $knownThumbUrls[$sizekey] = $localUrl; 00312 $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); 00313 return $localUrl; 00314 } 00315 /* There is a new Commons file, or existing thumbnail older than a month */ 00316 } 00317 $thumb = self::httpGet( $foreignUrl ); 00318 if( !$thumb ) { 00319 wfDebug( __METHOD__ . " Could not download thumb\n" ); 00320 return false; 00321 } 00322 00323 00324 # @todo FIXME: Delete old thumbs that aren't being used. Maintenance script? 00325 $backend->prepare( array( 'dir' => dirname( $localFilename ) ) ); 00326 $params = array( 'dst' => $localFilename, 'content' => $thumb ); 00327 if( !$backend->quickCreate( $params )->isOK() ) { 00328 wfDebug( __METHOD__ . " could not write to thumb path '$localFilename'\n" ); 00329 return $foreignUrl; 00330 } 00331 $knownThumbUrls[$sizekey] = $localUrl; 00332 $wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry ); 00333 wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" ); 00334 return $localUrl; 00335 } 00336 00342 function getZoneUrl( $zone ) { 00343 switch ( $zone ) { 00344 case 'public': 00345 return $this->url; 00346 case 'thumb': 00347 return $this->thumbUrl; 00348 default: 00349 return parent::getZoneUrl( $zone ); 00350 } 00351 } 00352 00358 function getZonePath( $zone ) { 00359 $supported = array( 'public', 'thumb' ); 00360 if ( in_array( $zone, $supported ) ) { 00361 return parent::getZonePath( $zone ); 00362 } 00363 return false; 00364 } 00365 00370 public function canCacheThumbs() { 00371 return ( $this->apiThumbCacheExpiry > 0 ); 00372 } 00373 00378 public static function getUserAgent() { 00379 return Http::userAgent() . " ForeignAPIRepo/" . self::VERSION; 00380 } 00381 00390 public static function httpGet( $url, $timeout = 'default', $options = array() ) { 00391 $options['timeout'] = $timeout; 00392 /* Http::get */ 00393 $url = wfExpandUrl( $url, PROTO_HTTP ); 00394 wfDebug( "ForeignAPIRepo: HTTP GET: $url\n" ); 00395 $options['method'] = "GET"; 00396 00397 if ( !isset( $options['timeout'] ) ) { 00398 $options['timeout'] = 'default'; 00399 } 00400 00401 $req = MWHttpRequest::factory( $url, $options ); 00402 $req->setUserAgent( ForeignAPIRepo::getUserAgent() ); 00403 $status = $req->execute(); 00404 00405 if ( $status->isOK() ) { 00406 return $req->getContent(); 00407 } else { 00408 return false; 00409 } 00410 } 00411 00416 function enumFiles( $callback ) { 00417 throw new MWException( 'enumFiles is not supported by ' . get_class( $this ) ); 00418 } 00419 00423 protected function assertWritableRepo() { 00424 throw new MWException( get_class( $this ) . ': write operations are not supported.' ); 00425 } 00426 }