MediaWiki  REL1_24
LocalRepo.php
Go to the documentation of this file.
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 }