MediaWiki
REL1_24
|
00001 <?php 00031 class LocalRepo extends FileRepo { 00033 protected $fileFactory = array( 'LocalFile', 'newFromTitle' ); 00034 00036 protected $fileFactoryKey = array( 'LocalFile', 'newFromKey' ); 00037 00039 protected $fileFromRowFactory = array( 'LocalFile', 'newFromRow' ); 00040 00042 protected $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' ); 00043 00045 protected $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' ); 00046 00048 protected $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' ); 00049 00055 function newFileFromRow( $row ) { 00056 if ( isset( $row->img_name ) ) { 00057 return call_user_func( $this->fileFromRowFactory, $row, $this ); 00058 } elseif ( isset( $row->oi_name ) ) { 00059 return call_user_func( $this->oldFileFromRowFactory, $row, $this ); 00060 } else { 00061 throw new MWException( __METHOD__ . ': invalid row' ); 00062 } 00063 } 00064 00070 function newFromArchiveName( $title, $archiveName ) { 00071 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName ); 00072 } 00073 00084 function cleanupDeletedBatch( array $storageKeys ) { 00085 $backend = $this->backend; // convenience 00086 $root = $this->getZonePath( 'deleted' ); 00087 $dbw = $this->getMasterDB(); 00088 $status = $this->newGood(); 00089 $storageKeys = array_unique( $storageKeys ); 00090 foreach ( $storageKeys as $key ) { 00091 $hashPath = $this->getDeletedHashPath( $key ); 00092 $path = "$root/$hashPath$key"; 00093 $dbw->begin( __METHOD__ ); 00094 // Check for usage in deleted/hidden files and preemptively 00095 // lock the key to avoid any future use until we are finished. 00096 $deleted = $this->deletedFileHasKey( $key, 'lock' ); 00097 $hidden = $this->hiddenFileHasKey( $key, 'lock' ); 00098 if ( !$deleted && !$hidden ) { // not in use now 00099 wfDebug( __METHOD__ . ": deleting $key\n" ); 00100 $op = array( 'op' => 'delete', 'src' => $path ); 00101 if ( !$backend->doOperation( $op )->isOK() ) { 00102 $status->error( 'undelete-cleanup-error', $path ); 00103 $status->failCount++; 00104 } 00105 } else { 00106 wfDebug( __METHOD__ . ": $key still in use\n" ); 00107 $status->successCount++; 00108 } 00109 $dbw->commit( __METHOD__ ); 00110 } 00111 00112 return $status; 00113 } 00114 00122 protected function deletedFileHasKey( $key, $lock = null ) { 00123 $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array(); 00124 00125 $dbw = $this->getMasterDB(); 00126 00127 return (bool)$dbw->selectField( 'filearchive', '1', 00128 array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ), 00129 __METHOD__, $options 00130 ); 00131 } 00132 00140 protected function hiddenFileHasKey( $key, $lock = null ) { 00141 $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array(); 00142 00143 $sha1 = self::getHashFromKey( $key ); 00144 $ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) ); 00145 00146 $dbw = $this->getMasterDB(); 00147 00148 return (bool)$dbw->selectField( 'oldimage', '1', 00149 array( 'oi_sha1' => $sha1, 00150 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ), 00151 $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ), 00152 __METHOD__, $options 00153 ); 00154 } 00155 00162 public static function getHashFromKey( $key ) { 00163 return strtok( $key, '.' ); 00164 } 00165 00172 function checkRedirect( Title $title ) { 00173 global $wgMemc; 00174 00175 $title = File::normalizeTitle( $title, 'exception' ); 00176 00177 $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) ); 00178 if ( $memcKey === false ) { 00179 $memcKey = $this->getLocalCacheKey( 'image_redirect', md5( $title->getDBkey() ) ); 00180 $expiry = 300; // no invalidation, 5 minutes 00181 } else { 00182 $expiry = 86400; // has invalidation, 1 day 00183 } 00184 $cachedValue = $wgMemc->get( $memcKey ); 00185 if ( $cachedValue === ' ' || $cachedValue === '' ) { 00186 // Does not exist 00187 return false; 00188 } elseif ( strval( $cachedValue ) !== '' && $cachedValue !== ' PURGED' ) { 00189 return Title::newFromText( $cachedValue, NS_FILE ); 00190 } // else $cachedValue is false or null: cache miss 00191 00192 $id = $this->getArticleID( $title ); 00193 if ( !$id ) { 00194 $wgMemc->add( $memcKey, " ", $expiry ); 00195 00196 return false; 00197 } 00198 $dbr = $this->getSlaveDB(); 00199 $row = $dbr->selectRow( 00200 'redirect', 00201 array( 'rd_title', 'rd_namespace' ), 00202 array( 'rd_from' => $id ), 00203 __METHOD__ 00204 ); 00205 00206 if ( $row && $row->rd_namespace == NS_FILE ) { 00207 $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title ); 00208 $wgMemc->add( $memcKey, $targetTitle->getDBkey(), $expiry ); 00209 00210 return $targetTitle; 00211 } else { 00212 $wgMemc->add( $memcKey, '', $expiry ); 00213 00214 return false; 00215 } 00216 } 00217 00225 protected function getArticleID( $title ) { 00226 if ( !$title instanceof Title ) { 00227 return 0; 00228 } 00229 $dbr = $this->getSlaveDB(); 00230 $id = $dbr->selectField( 00231 'page', // Table 00232 'page_id', //Field 00233 array( //Conditions 00234 'page_namespace' => $title->getNamespace(), 00235 'page_title' => $title->getDBkey(), 00236 ), 00237 __METHOD__ //Function name 00238 ); 00239 00240 return $id; 00241 } 00242 00243 public function findFiles( array $items, $flags = 0 ) { 00244 $finalFiles = array(); // map of (DB key => corresponding File) for matches 00245 00246 $searchSet = array(); // map of (normalized DB key => search params) 00247 foreach ( $items as $item ) { 00248 if ( is_array( $item ) ) { 00249 $title = File::normalizeTitle( $item['title'] ); 00250 if ( $title ) { 00251 $searchSet[$title->getDBkey()] = $item; 00252 } 00253 } else { 00254 $title = File::normalizeTitle( $item ); 00255 if ( $title ) { 00256 $searchSet[$title->getDBkey()] = array(); 00257 } 00258 } 00259 } 00260 00261 $fileMatchesSearch = function ( File $file, array $search ) { 00262 // Note: file name comparison done elsewhere (to handle redirects) 00263 $user = ( !empty( $search['private'] ) && $search['private'] instanceof User ) 00264 ? $search['private'] 00265 : null; 00266 00267 return ( 00268 $file->exists() && 00269 ( 00270 ( empty( $search['time'] ) && !$file->isOld() ) || 00271 ( !empty( $search['time'] ) && $search['time'] === $file->getTimestamp() ) 00272 ) && 00273 ( !empty( $search['private'] ) || !$file->isDeleted( File::DELETED_FILE ) ) && 00274 $file->userCan( File::DELETED_FILE, $user ) 00275 ); 00276 }; 00277 00278 $repo = $this; 00279 $applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles ) 00280 use ( $repo, $fileMatchesSearch, $flags ) 00281 { 00282 global $wgContLang; 00283 $info = $repo->getInfo(); 00284 foreach ( $res as $row ) { 00285 $file = $repo->newFileFromRow( $row ); 00286 // There must have been a search for this DB key, but this has to handle the 00287 // cases were title capitalization is different on the client and repo wikis. 00288 $dbKeysLook = array( str_replace( ' ', '_', $file->getName() ) ); 00289 if ( !empty( $info['initialCapital'] ) ) { 00290 // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file" 00291 $dbKeysLook[] = $wgContLang->lcfirst( $file->getName() ); 00292 } 00293 foreach ( $dbKeysLook as $dbKey ) { 00294 if ( isset( $searchSet[$dbKey] ) 00295 && $fileMatchesSearch( $file, $searchSet[$dbKey] ) 00296 ) { 00297 $finalFiles[$dbKey] = ( $flags & FileRepo::NAME_AND_TIME_ONLY ) 00298 ? array( 'title' => $dbKey, 'timestamp' => $file->getTimestamp() ) 00299 : $file; 00300 unset( $searchSet[$dbKey] ); 00301 } 00302 } 00303 } 00304 }; 00305 00306 $dbr = $this->getSlaveDB(); 00307 00308 // Query image table 00309 $imgNames = array(); 00310 foreach ( array_keys( $searchSet ) as $dbKey ) { 00311 $imgNames[] = $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ); 00312 } 00313 00314 if ( count( $imgNames ) ) { 00315 $res = $dbr->select( 'image', 00316 LocalFile::selectFields(), array( 'img_name' => $imgNames ), __METHOD__ ); 00317 $applyMatchingFiles( $res, $searchSet, $finalFiles ); 00318 } 00319 00320 // Query old image table 00321 $oiConds = array(); // WHERE clause array for each file 00322 foreach ( $searchSet as $dbKey => $search ) { 00323 if ( isset( $search['time'] ) ) { 00324 $oiConds[] = $dbr->makeList( 00325 array( 00326 'oi_name' => $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ), 00327 'oi_timestamp' => $dbr->timestamp( $search['time'] ) 00328 ), 00329 LIST_AND 00330 ); 00331 } 00332 } 00333 00334 if ( count( $oiConds ) ) { 00335 $res = $dbr->select( 'oldimage', 00336 OldLocalFile::selectFields(), $dbr->makeList( $oiConds, LIST_OR ), __METHOD__ ); 00337 $applyMatchingFiles( $res, $searchSet, $finalFiles ); 00338 } 00339 00340 // Check for redirects... 00341 foreach ( $searchSet as $dbKey => $search ) { 00342 if ( !empty( $search['ignoreRedirect'] ) ) { 00343 continue; 00344 } 00345 00346 $title = File::normalizeTitle( $dbKey ); 00347 $redir = $this->checkRedirect( $title ); // hopefully hits memcached 00348 00349 if ( $redir && $redir->getNamespace() == NS_FILE ) { 00350 $file = $this->newFile( $redir ); 00351 if ( $file && $fileMatchesSearch( $file, $search ) ) { 00352 $file->redirectedFrom( $title->getDBkey() ); 00353 if ( $flags & FileRepo::NAME_AND_TIME_ONLY ) { 00354 $finalFiles[$dbKey] = array( 00355 'title' => $file->getTitle()->getDBkey(), 00356 'timestamp' => $file->getTimestamp() 00357 ); 00358 } else { 00359 $finalFiles[$dbKey] = $file; 00360 } 00361 } 00362 } 00363 } 00364 00365 return $finalFiles; 00366 } 00367 00375 function findBySha1( $hash ) { 00376 $dbr = $this->getSlaveDB(); 00377 $res = $dbr->select( 00378 'image', 00379 LocalFile::selectFields(), 00380 array( 'img_sha1' => $hash ), 00381 __METHOD__, 00382 array( 'ORDER BY' => 'img_name' ) 00383 ); 00384 00385 $result = array(); 00386 foreach ( $res as $row ) { 00387 $result[] = $this->newFileFromRow( $row ); 00388 } 00389 $res->free(); 00390 00391 return $result; 00392 } 00393 00403 function findBySha1s( array $hashes ) { 00404 if ( !count( $hashes ) ) { 00405 return array(); //empty parameter 00406 } 00407 00408 $dbr = $this->getSlaveDB(); 00409 $res = $dbr->select( 00410 'image', 00411 LocalFile::selectFields(), 00412 array( 'img_sha1' => $hashes ), 00413 __METHOD__, 00414 array( 'ORDER BY' => 'img_name' ) 00415 ); 00416 00417 $result = array(); 00418 foreach ( $res as $row ) { 00419 $file = $this->newFileFromRow( $row ); 00420 $result[$file->getSha1()][] = $file; 00421 } 00422 $res->free(); 00423 00424 return $result; 00425 } 00426 00434 public function findFilesByPrefix( $prefix, $limit ) { 00435 $selectOptions = array( 'ORDER BY' => 'img_name', 'LIMIT' => intval( $limit ) ); 00436 00437 // Query database 00438 $dbr = $this->getSlaveDB(); 00439 $res = $dbr->select( 00440 'image', 00441 LocalFile::selectFields(), 00442 'img_name ' . $dbr->buildLike( $prefix, $dbr->anyString() ), 00443 __METHOD__, 00444 $selectOptions 00445 ); 00446 00447 // Build file objects 00448 $files = array(); 00449 foreach ( $res as $row ) { 00450 $files[] = $this->newFileFromRow( $row ); 00451 } 00452 00453 return $files; 00454 } 00455 00460 function getSlaveDB() { 00461 return wfGetDB( DB_SLAVE ); 00462 } 00463 00468 function getMasterDB() { 00469 return wfGetDB( DB_MASTER ); 00470 } 00471 00479 function getSharedCacheKey( /*...*/ ) { 00480 $args = func_get_args(); 00481 00482 return call_user_func_array( 'wfMemcKey', $args ); 00483 } 00484 00491 function invalidateImageRedirect( Title $title ) { 00492 global $wgMemc; 00493 $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) ); 00494 if ( $memcKey ) { 00495 // Set a temporary value for the cache key, to ensure 00496 // that this value stays purged long enough so that 00497 // it isn't refreshed with a stale value due to a 00498 // lagged slave. 00499 $wgMemc->set( $memcKey, ' PURGED', 12 ); 00500 } 00501 } 00502 00509 function getInfo() { 00510 global $wgFavicon; 00511 00512 return array_merge( parent::getInfo(), array( 00513 'favicon' => wfExpandUrl( $wgFavicon ), 00514 ) ); 00515 } 00516 }