MediaWiki  master
FileBackendStore.php
Go to the documentation of this file.
00001 <?php
00038 abstract class FileBackendStore extends FileBackend {
00040         protected $memCache;
00042         protected $cheapCache; // Map of paths to small (RAM/disk) cache items
00044         protected $expensiveCache; // Map of paths to large (RAM/disk) cache items
00045 
00047         protected $shardViaHashLevels = array(); // (container name => config array)
00048 
00049         protected $maxFileSize = 4294967296; // integer bytes (4GiB)
00050 
00056         public function __construct( array $config ) {
00057                 parent::__construct( $config );
00058                 $this->memCache       = new EmptyBagOStuff(); // disabled by default
00059                 $this->cheapCache     = new ProcessCacheLRU( 300 );
00060                 $this->expensiveCache = new ProcessCacheLRU( 5 );
00061         }
00062 
00070         final public function maxFileSizeInternal() {
00071                 return $this->maxFileSize;
00072         }
00073 
00083         abstract public function isPathUsableInternal( $storagePath );
00084 
00101         final public function createInternal( array $params ) {
00102                 wfProfileIn( __METHOD__ );
00103                 wfProfileIn( __METHOD__ . '-' . $this->name );
00104                 if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
00105                         $status = Status::newFatal( 'backend-fail-maxsize',
00106                                 $params['dst'], $this->maxFileSizeInternal() );
00107                 } else {
00108                         $status = $this->doCreateInternal( $params );
00109                         $this->clearCache( array( $params['dst'] ) );
00110                         $this->deleteFileCache( $params['dst'] ); // persistent cache
00111                 }
00112                 wfProfileOut( __METHOD__ . '-' . $this->name );
00113                 wfProfileOut( __METHOD__ );
00114                 return $status;
00115         }
00116 
00120         abstract protected function doCreateInternal( array $params );
00121 
00138         final public function storeInternal( array $params ) {
00139                 wfProfileIn( __METHOD__ );
00140                 wfProfileIn( __METHOD__ . '-' . $this->name );
00141                 if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
00142                         $status = Status::newFatal( 'backend-fail-maxsize',
00143                                 $params['dst'], $this->maxFileSizeInternal() );
00144                 } else {
00145                         $status = $this->doStoreInternal( $params );
00146                         $this->clearCache( array( $params['dst'] ) );
00147                         $this->deleteFileCache( $params['dst'] ); // persistent cache
00148                 }
00149                 wfProfileOut( __METHOD__ . '-' . $this->name );
00150                 wfProfileOut( __METHOD__ );
00151                 return $status;
00152         }
00153 
00157         abstract protected function doStoreInternal( array $params );
00158 
00176         final public function copyInternal( array $params ) {
00177                 wfProfileIn( __METHOD__ );
00178                 wfProfileIn( __METHOD__ . '-' . $this->name );
00179                 $status = $this->doCopyInternal( $params );
00180                 $this->clearCache( array( $params['dst'] ) );
00181                 $this->deleteFileCache( $params['dst'] ); // persistent cache
00182                 wfProfileOut( __METHOD__ . '-' . $this->name );
00183                 wfProfileOut( __METHOD__ );
00184                 return $status;
00185         }
00186 
00190         abstract protected function doCopyInternal( array $params );
00191 
00206         final public function deleteInternal( array $params ) {
00207                 wfProfileIn( __METHOD__ );
00208                 wfProfileIn( __METHOD__ . '-' . $this->name );
00209                 $status = $this->doDeleteInternal( $params );
00210                 $this->clearCache( array( $params['src'] ) );
00211                 $this->deleteFileCache( $params['src'] ); // persistent cache
00212                 wfProfileOut( __METHOD__ . '-' . $this->name );
00213                 wfProfileOut( __METHOD__ );
00214                 return $status;
00215         }
00216 
00220         abstract protected function doDeleteInternal( array $params );
00221 
00239         final public function moveInternal( array $params ) {
00240                 wfProfileIn( __METHOD__ );
00241                 wfProfileIn( __METHOD__ . '-' . $this->name );
00242                 $status = $this->doMoveInternal( $params );
00243                 $this->clearCache( array( $params['src'], $params['dst'] ) );
00244                 $this->deleteFileCache( $params['src'] ); // persistent cache
00245                 $this->deleteFileCache( $params['dst'] ); // persistent cache
00246                 wfProfileOut( __METHOD__ . '-' . $this->name );
00247                 wfProfileOut( __METHOD__ );
00248                 return $status;
00249         }
00250 
00255         protected function doMoveInternal( array $params ) {
00256                 unset( $params['async'] ); // two steps, won't work here :)
00257                 // Copy source to dest
00258                 $status = $this->copyInternal( $params );
00259                 if ( $status->isOK() ) {
00260                         // Delete source (only fails due to races or medium going down)
00261                         $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
00262                         $status->setResult( true, $status->value ); // ignore delete() errors
00263                 }
00264                 return $status;
00265         }
00266 
00274         final public function nullInternal( array $params ) {
00275                 return Status::newGood();
00276         }
00277 
00282         final public function concatenate( array $params ) {
00283                 wfProfileIn( __METHOD__ );
00284                 wfProfileIn( __METHOD__ . '-' . $this->name );
00285                 $status = Status::newGood();
00286 
00287                 // Try to lock the source files for the scope of this function
00288                 $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
00289                 if ( $status->isOK() ) {
00290                         // Actually do the file concatenation...
00291                         $start_time = microtime( true );
00292                         $status->merge( $this->doConcatenate( $params ) );
00293                         $sec = microtime( true ) - $start_time;
00294                         if ( !$status->isOK() ) {
00295                                 wfDebugLog( 'FileOperation', get_class( $this ) . " failed to concatenate " .
00296                                         count( $params['srcs'] ) . " file(s) [$sec sec]" );
00297                         }
00298                 }
00299 
00300                 wfProfileOut( __METHOD__ . '-' . $this->name );
00301                 wfProfileOut( __METHOD__ );
00302                 return $status;
00303         }
00304 
00309         protected function doConcatenate( array $params ) {
00310                 $status = Status::newGood();
00311                 $tmpPath = $params['dst']; // convenience
00312                 unset( $params['latest'] ); // sanity
00313 
00314                 // Check that the specified temp file is valid...
00315                 wfSuppressWarnings();
00316                 $ok = ( is_file( $tmpPath ) && filesize( $tmpPath ) == 0 );
00317                 wfRestoreWarnings();
00318                 if ( !$ok ) { // not present or not empty
00319                         $status->fatal( 'backend-fail-opentemp', $tmpPath );
00320                         return $status;
00321                 }
00322 
00323                 // Get local FS versions of the chunks needed for the concatenation...
00324                 $fsFiles = $this->getLocalReferenceMulti( $params );
00325                 foreach ( $fsFiles as $path => &$fsFile ) {
00326                         if ( !$fsFile ) { // chunk failed to download?
00327                                 $fsFile = $this->getLocalReference( array( 'src' => $path ) );
00328                                 if ( !$fsFile ) { // retry failed?
00329                                         $status->fatal( 'backend-fail-read', $path );
00330                                         return $status;
00331                                 }
00332                         }
00333                 }
00334                 unset( $fsFile ); // unset reference so we can reuse $fsFile
00335 
00336                 // Get a handle for the destination temp file
00337                 $tmpHandle = fopen( $tmpPath, 'ab' );
00338                 if ( $tmpHandle === false ) {
00339                         $status->fatal( 'backend-fail-opentemp', $tmpPath );
00340                         return $status;
00341                 }
00342 
00343                 // Build up the temp file using the source chunks (in order)...
00344                 foreach ( $fsFiles as $virtualSource => $fsFile ) {
00345                         // Get a handle to the local FS version
00346                         $sourceHandle = fopen( $fsFile->getPath(), 'rb' );
00347                         if ( $sourceHandle === false ) {
00348                                 fclose( $tmpHandle );
00349                                 $status->fatal( 'backend-fail-read', $virtualSource );
00350                                 return $status;
00351                         }
00352                         // Append chunk to file (pass chunk size to avoid magic quotes)
00353                         if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
00354                                 fclose( $sourceHandle );
00355                                 fclose( $tmpHandle );
00356                                 $status->fatal( 'backend-fail-writetemp', $tmpPath );
00357                                 return $status;
00358                         }
00359                         fclose( $sourceHandle );
00360                 }
00361                 if ( !fclose( $tmpHandle ) ) {
00362                         $status->fatal( 'backend-fail-closetemp', $tmpPath );
00363                         return $status;
00364                 }
00365 
00366                 clearstatcache(); // temp file changed
00367 
00368                 return $status;
00369         }
00370 
00375         final protected function doPrepare( array $params ) {
00376                 wfProfileIn( __METHOD__ );
00377                 wfProfileIn( __METHOD__ . '-' . $this->name );
00378 
00379                 $status = Status::newGood();
00380                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00381                 if ( $dir === null ) {
00382                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00383                         wfProfileOut( __METHOD__ . '-' . $this->name );
00384                         wfProfileOut( __METHOD__ );
00385                         return $status; // invalid storage path
00386                 }
00387 
00388                 if ( $shard !== null ) { // confined to a single container/shard
00389                         $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) );
00390                 } else { // directory is on several shards
00391                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00392                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00393                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00394                                 $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00395                         }
00396                 }
00397 
00398                 wfProfileOut( __METHOD__ . '-' . $this->name );
00399                 wfProfileOut( __METHOD__ );
00400                 return $status;
00401         }
00402 
00407         protected function doPrepareInternal( $container, $dir, array $params ) {
00408                 return Status::newGood();
00409         }
00410 
00415         final protected function doSecure( array $params ) {
00416                 wfProfileIn( __METHOD__ );
00417                 wfProfileIn( __METHOD__ . '-' . $this->name );
00418                 $status = Status::newGood();
00419 
00420                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00421                 if ( $dir === null ) {
00422                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00423                         wfProfileOut( __METHOD__ . '-' . $this->name );
00424                         wfProfileOut( __METHOD__ );
00425                         return $status; // invalid storage path
00426                 }
00427 
00428                 if ( $shard !== null ) { // confined to a single container/shard
00429                         $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
00430                 } else { // directory is on several shards
00431                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00432                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00433                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00434                                 $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00435                         }
00436                 }
00437 
00438                 wfProfileOut( __METHOD__ . '-' . $this->name );
00439                 wfProfileOut( __METHOD__ );
00440                 return $status;
00441         }
00442 
00447         protected function doSecureInternal( $container, $dir, array $params ) {
00448                 return Status::newGood();
00449         }
00450 
00455         final protected function doPublish( array $params ) {
00456                 wfProfileIn( __METHOD__ );
00457                 wfProfileIn( __METHOD__ . '-' . $this->name );
00458                 $status = Status::newGood();
00459 
00460                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00461                 if ( $dir === null ) {
00462                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00463                         wfProfileOut( __METHOD__ . '-' . $this->name );
00464                         wfProfileOut( __METHOD__ );
00465                         return $status; // invalid storage path
00466                 }
00467 
00468                 if ( $shard !== null ) { // confined to a single container/shard
00469                         $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
00470                 } else { // directory is on several shards
00471                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00472                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00473                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00474                                 $status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00475                         }
00476                 }
00477 
00478                 wfProfileOut( __METHOD__ . '-' . $this->name );
00479                 wfProfileOut( __METHOD__ );
00480                 return $status;
00481         }
00482 
00487         protected function doPublishInternal( $container, $dir, array $params ) {
00488                 return Status::newGood();
00489         }
00490 
00495         final protected function doClean( array $params ) {
00496                 wfProfileIn( __METHOD__ );
00497                 wfProfileIn( __METHOD__ . '-' . $this->name );
00498                 $status = Status::newGood();
00499 
00500                 // Recursive: first delete all empty subdirs recursively
00501                 if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
00502                         $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) );
00503                         if ( $subDirsRel !== null ) { // no errors
00504                                 foreach ( $subDirsRel as $subDirRel ) {
00505                                         $subDir = $params['dir'] . "/{$subDirRel}"; // full path
00506                                         $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
00507                                 }
00508                         }
00509                 }
00510 
00511                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00512                 if ( $dir === null ) {
00513                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00514                         wfProfileOut( __METHOD__ . '-' . $this->name );
00515                         wfProfileOut( __METHOD__ );
00516                         return $status; // invalid storage path
00517                 }
00518 
00519                 // Attempt to lock this directory...
00520                 $filesLockEx = array( $params['dir'] );
00521                 $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
00522                 if ( !$status->isOK() ) {
00523                         wfProfileOut( __METHOD__ . '-' . $this->name );
00524                         wfProfileOut( __METHOD__ );
00525                         return $status; // abort
00526                 }
00527 
00528                 if ( $shard !== null ) { // confined to a single container/shard
00529                         $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
00530                         $this->deleteContainerCache( $fullCont ); // purge cache
00531                 } else { // directory is on several shards
00532                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00533                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00534                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00535                                 $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00536                                 $this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache
00537                         }
00538                 }
00539 
00540                 wfProfileOut( __METHOD__ . '-' . $this->name );
00541                 wfProfileOut( __METHOD__ );
00542                 return $status;
00543         }
00544 
00549         protected function doCleanInternal( $container, $dir, array $params ) {
00550                 return Status::newGood();
00551         }
00552 
00557         final public function fileExists( array $params ) {
00558                 wfProfileIn( __METHOD__ );
00559                 wfProfileIn( __METHOD__ . '-' . $this->name );
00560                 $stat = $this->getFileStat( $params );
00561                 wfProfileOut( __METHOD__ . '-' . $this->name );
00562                 wfProfileOut( __METHOD__ );
00563                 return ( $stat === null ) ? null : (bool)$stat; // null => failure
00564         }
00565 
00570         final public function getFileTimestamp( array $params ) {
00571                 wfProfileIn( __METHOD__ );
00572                 wfProfileIn( __METHOD__ . '-' . $this->name );
00573                 $stat = $this->getFileStat( $params );
00574                 wfProfileOut( __METHOD__ . '-' . $this->name );
00575                 wfProfileOut( __METHOD__ );
00576                 return $stat ? $stat['mtime'] : false;
00577         }
00578 
00583         final public function getFileSize( array $params ) {
00584                 wfProfileIn( __METHOD__ );
00585                 wfProfileIn( __METHOD__ . '-' . $this->name );
00586                 $stat = $this->getFileStat( $params );
00587                 wfProfileOut( __METHOD__ . '-' . $this->name );
00588                 wfProfileOut( __METHOD__ );
00589                 return $stat ? $stat['size'] : false;
00590         }
00591 
00596         final public function getFileStat( array $params ) {
00597                 $path = self::normalizeStoragePath( $params['src'] );
00598                 if ( $path === null ) {
00599                         return false; // invalid storage path
00600                 }
00601                 wfProfileIn( __METHOD__ );
00602                 wfProfileIn( __METHOD__ . '-' . $this->name );
00603                 $latest = !empty( $params['latest'] ); // use latest data?
00604                 if ( !$this->cheapCache->has( $path, 'stat' ) ) {
00605                         $this->primeFileCache( array( $path ) ); // check persistent cache
00606                 }
00607                 if ( $this->cheapCache->has( $path, 'stat' ) ) {
00608                         $stat = $this->cheapCache->get( $path, 'stat' );
00609                         // If we want the latest data, check that this cached
00610                         // value was in fact fetched with the latest available data.
00611                         if ( !$latest || $stat['latest'] ) {
00612                                 wfProfileOut( __METHOD__ . '-' . $this->name );
00613                                 wfProfileOut( __METHOD__ );
00614                                 return $stat;
00615                         }
00616                 }
00617                 wfProfileIn( __METHOD__ . '-miss' );
00618                 wfProfileIn( __METHOD__ . '-miss-' . $this->name );
00619                 $stat = $this->doGetFileStat( $params );
00620                 wfProfileOut( __METHOD__ . '-miss-' . $this->name );
00621                 wfProfileOut( __METHOD__ . '-miss' );
00622                 if ( is_array( $stat ) ) { // don't cache negatives
00623                         $stat['latest'] = $latest;
00624                         $this->cheapCache->set( $path, 'stat', $stat );
00625                         $this->setFileCache( $path, $stat ); // update persistent cache
00626                         if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
00627                                 $this->cheapCache->set( $path, 'sha1',
00628                                         array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
00629                         }
00630                 } else {
00631                         wfDebug( __METHOD__ . ": File $path does not exist.\n" );
00632                 }
00633                 wfProfileOut( __METHOD__ . '-' . $this->name );
00634                 wfProfileOut( __METHOD__ );
00635                 return $stat;
00636         }
00637 
00641         abstract protected function doGetFileStat( array $params );
00642 
00647         public function getFileContentsMulti( array $params ) {
00648                 wfProfileIn( __METHOD__ );
00649                 wfProfileIn( __METHOD__ . '-' . $this->name );
00650 
00651                 $params = $this->setConcurrencyFlags( $params );
00652                 $contents = $this->doGetFileContentsMulti( $params );
00653 
00654                 wfProfileOut( __METHOD__ . '-' . $this->name );
00655                 wfProfileOut( __METHOD__ );
00656                 return $contents;
00657         }
00658 
00663         protected function doGetFileContentsMulti( array $params ) {
00664                 $contents = array();
00665                 foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
00666                         wfSuppressWarnings();
00667                         $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
00668                         wfRestoreWarnings();
00669                 }
00670                 return $contents;
00671         }
00672 
00677         final public function getFileSha1Base36( array $params ) {
00678                 $path = self::normalizeStoragePath( $params['src'] );
00679                 if ( $path === null ) {
00680                         return false; // invalid storage path
00681                 }
00682                 wfProfileIn( __METHOD__ );
00683                 wfProfileIn( __METHOD__ . '-' . $this->name );
00684                 $latest = !empty( $params['latest'] ); // use latest data?
00685                 if ( $this->cheapCache->has( $path, 'sha1' ) ) {
00686                         $stat = $this->cheapCache->get( $path, 'sha1' );
00687                         // If we want the latest data, check that this cached
00688                         // value was in fact fetched with the latest available data.
00689                         if ( !$latest || $stat['latest'] ) {
00690                                 wfProfileOut( __METHOD__ . '-' . $this->name );
00691                                 wfProfileOut( __METHOD__ );
00692                                 return $stat['hash'];
00693                         }
00694                 }
00695                 wfProfileIn( __METHOD__ . '-miss' );
00696                 wfProfileIn( __METHOD__ . '-miss-' . $this->name );
00697                 $hash = $this->doGetFileSha1Base36( $params );
00698                 wfProfileOut( __METHOD__ . '-miss-' . $this->name );
00699                 wfProfileOut( __METHOD__ . '-miss' );
00700                 if ( $hash ) { // don't cache negatives
00701                         $this->cheapCache->set( $path, 'sha1',
00702                                 array( 'hash' => $hash, 'latest' => $latest ) );
00703                 }
00704                 wfProfileOut( __METHOD__ . '-' . $this->name );
00705                 wfProfileOut( __METHOD__ );
00706                 return $hash;
00707         }
00708 
00713         protected function doGetFileSha1Base36( array $params ) {
00714                 $fsFile = $this->getLocalReference( $params );
00715                 if ( !$fsFile ) {
00716                         return false;
00717                 } else {
00718                         return $fsFile->getSha1Base36();
00719                 }
00720         }
00721 
00726         final public function getFileProps( array $params ) {
00727                 wfProfileIn( __METHOD__ );
00728                 wfProfileIn( __METHOD__ . '-' . $this->name );
00729                 $fsFile = $this->getLocalReference( $params );
00730                 $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
00731                 wfProfileOut( __METHOD__ . '-' . $this->name );
00732                 wfProfileOut( __METHOD__ );
00733                 return $props;
00734         }
00735 
00740         final public function getLocalReferenceMulti( array $params ) {
00741                 wfProfileIn( __METHOD__ );
00742                 wfProfileIn( __METHOD__ . '-' . $this->name );
00743 
00744                 $params = $this->setConcurrencyFlags( $params );
00745 
00746                 $fsFiles = array(); // (path => FSFile)
00747                 $latest = !empty( $params['latest'] ); // use latest data?
00748                 // Reuse any files already in process cache...
00749                 foreach ( $params['srcs'] as $src ) {
00750                         $path = self::normalizeStoragePath( $src );
00751                         if ( $path === null ) {
00752                                 $fsFiles[$src] = null; // invalid storage path
00753                         } elseif ( $this->expensiveCache->has( $path, 'localRef' ) ) {
00754                                 $val = $this->expensiveCache->get( $path, 'localRef' );
00755                                 // If we want the latest data, check that this cached
00756                                 // value was in fact fetched with the latest available data.
00757                                 if ( !$latest || $val['latest'] ) {
00758                                         $fsFiles[$src] = $val['object'];
00759                                 }
00760                         }
00761                 }
00762                 // Fetch local references of any remaning files...
00763                 $params['srcs'] = array_diff( $params['srcs'], array_keys( $fsFiles ) );
00764                 foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
00765                         $fsFiles[$path] = $fsFile;
00766                         if ( $fsFile ) { // update the process cache...
00767                                 $this->expensiveCache->set( $path, 'localRef',
00768                                         array( 'object' => $fsFile, 'latest' => $latest ) );
00769                         }
00770                 }
00771 
00772                 wfProfileOut( __METHOD__ . '-' . $this->name );
00773                 wfProfileOut( __METHOD__ );
00774                 return $fsFiles;
00775         }
00776 
00781         protected function doGetLocalReferenceMulti( array $params ) {
00782                 return $this->doGetLocalCopyMulti( $params );
00783         }
00784 
00789         final public function getLocalCopyMulti( array $params ) {
00790                 wfProfileIn( __METHOD__ );
00791                 wfProfileIn( __METHOD__ . '-' . $this->name );
00792 
00793                 $params = $this->setConcurrencyFlags( $params );
00794                 $tmpFiles = $this->doGetLocalCopyMulti( $params );
00795 
00796                 wfProfileOut( __METHOD__ . '-' . $this->name );
00797                 wfProfileOut( __METHOD__ );
00798                 return $tmpFiles;
00799         }
00800 
00805         abstract protected function doGetLocalCopyMulti( array $params );
00806 
00811         public function getFileHttpUrl( array $params ) {
00812                 return null; // not supported
00813         }
00814 
00819         final public function streamFile( array $params ) {
00820                 wfProfileIn( __METHOD__ );
00821                 wfProfileIn( __METHOD__ . '-' . $this->name );
00822                 $status = Status::newGood();
00823 
00824                 $info = $this->getFileStat( $params );
00825                 if ( !$info ) { // let StreamFile handle the 404
00826                         $status->fatal( 'backend-fail-notexists', $params['src'] );
00827                 }
00828 
00829                 // Set output buffer and HTTP headers for stream
00830                 $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array();
00831                 $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders );
00832                 if ( $res == StreamFile::NOT_MODIFIED ) {
00833                         // do nothing; client cache is up to date
00834                 } elseif ( $res == StreamFile::READY_STREAM ) {
00835                         wfProfileIn( __METHOD__ . '-send' );
00836                         wfProfileIn( __METHOD__ . '-send-' . $this->name );
00837                         $status = $this->doStreamFile( $params );
00838                         wfProfileOut( __METHOD__ . '-send-' . $this->name );
00839                         wfProfileOut( __METHOD__ . '-send' );
00840                         if ( !$status->isOK() ) {
00841                                 // Per bug 41113, nasty things can happen if bad cache entries get
00842                                 // stuck in cache. It's also possible that this error can come up
00843                                 // with simple race conditions. Clear out the stat cache to be safe.
00844                                 $this->clearCache( array( $params['src'] ) );
00845                                 $this->deleteFileCache( $params['src'] );
00846                                 trigger_error( "Bad stat cache or race condition for file {$params['src']}." );
00847                         }
00848                 } else {
00849                         $status->fatal( 'backend-fail-stream', $params['src'] );
00850                 }
00851 
00852                 wfProfileOut( __METHOD__ . '-' . $this->name );
00853                 wfProfileOut( __METHOD__ );
00854                 return $status;
00855         }
00856 
00861         protected function doStreamFile( array $params ) {
00862                 $status = Status::newGood();
00863 
00864                 $fsFile = $this->getLocalReference( $params );
00865                 if ( !$fsFile ) {
00866                         $status->fatal( 'backend-fail-stream', $params['src'] );
00867                 } elseif ( !readfile( $fsFile->getPath() ) ) {
00868                         $status->fatal( 'backend-fail-stream', $params['src'] );
00869                 }
00870 
00871                 return $status;
00872         }
00873 
00878         final public function directoryExists( array $params ) {
00879                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00880                 if ( $dir === null ) {
00881                         return false; // invalid storage path
00882                 }
00883                 if ( $shard !== null ) { // confined to a single container/shard
00884                         return $this->doDirectoryExists( $fullCont, $dir, $params );
00885                 } else { // directory is on several shards
00886                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00887                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00888                         $res = false; // response
00889                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00890                                 $exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
00891                                 if ( $exists ) {
00892                                         $res = true;
00893                                         break; // found one!
00894                                 } elseif ( $exists === null ) { // error?
00895                                         $res = null; // if we don't find anything, it is indeterminate
00896                                 }
00897                         }
00898                         return $res;
00899                 }
00900         }
00901 
00910         abstract protected function doDirectoryExists( $container, $dir, array $params );
00911 
00916         final public function getDirectoryList( array $params ) {
00917                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00918                 if ( $dir === null ) { // invalid storage path
00919                         return null;
00920                 }
00921                 if ( $shard !== null ) {
00922                         // File listing is confined to a single container/shard
00923                         return $this->getDirectoryListInternal( $fullCont, $dir, $params );
00924                 } else {
00925                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00926                         // File listing spans multiple containers/shards
00927                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00928                         return new FileBackendStoreShardDirIterator( $this,
00929                                 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
00930                 }
00931         }
00932 
00943         abstract public function getDirectoryListInternal( $container, $dir, array $params );
00944 
00949         final public function getFileList( array $params ) {
00950                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00951                 if ( $dir === null ) { // invalid storage path
00952                         return null;
00953                 }
00954                 if ( $shard !== null ) {
00955                         // File listing is confined to a single container/shard
00956                         return $this->getFileListInternal( $fullCont, $dir, $params );
00957                 } else {
00958                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00959                         // File listing spans multiple containers/shards
00960                         list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
00961                         return new FileBackendStoreShardFileIterator( $this,
00962                                 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
00963                 }
00964         }
00965 
00976         abstract public function getFileListInternal( $container, $dir, array $params );
00977 
00989         final public function getOperationsInternal( array $ops ) {
00990                 $supportedOps = array(
00991                         'store'       => 'StoreFileOp',
00992                         'copy'        => 'CopyFileOp',
00993                         'move'        => 'MoveFileOp',
00994                         'delete'      => 'DeleteFileOp',
00995                         'create'      => 'CreateFileOp',
00996                         'null'        => 'NullFileOp'
00997                 );
00998 
00999                 $performOps = array(); // array of FileOp objects
01000                 // Build up ordered array of FileOps...
01001                 foreach ( $ops as $operation ) {
01002                         $opName = $operation['op'];
01003                         if ( isset( $supportedOps[$opName] ) ) {
01004                                 $class = $supportedOps[$opName];
01005                                 // Get params for this operation
01006                                 $params = $operation;
01007                                 // Append the FileOp class
01008                                 $performOps[] = new $class( $this, $params );
01009                         } else {
01010                                 throw new MWException( "Operation '$opName' is not supported." );
01011                         }
01012                 }
01013 
01014                 return $performOps;
01015         }
01016 
01026         final public function getPathsToLockForOpsInternal( array $performOps ) {
01027                 // Build up a list of files to lock...
01028                 $paths = array( 'sh' => array(), 'ex' => array() );
01029                 foreach ( $performOps as $fileOp ) {
01030                         $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() );
01031                         $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() );
01032                 }
01033                 // Optimization: if doing an EX lock anyway, don't also set an SH one
01034                 $paths['sh'] = array_diff( $paths['sh'], $paths['ex'] );
01035                 // Get a shared lock on the parent directory of each path changed
01036                 $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
01037 
01038                 return $paths;
01039         }
01040 
01045         public function getScopedLocksForOps( array $ops, Status $status ) {
01046                 $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
01047                 return array(
01048                         $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
01049                         $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
01050                 );
01051         }
01052 
01057         final protected function doOperationsInternal( array $ops, array $opts ) {
01058                 wfProfileIn( __METHOD__ );
01059                 wfProfileIn( __METHOD__ . '-' . $this->name );
01060                 $status = Status::newGood();
01061 
01062                 // Build up a list of FileOps...
01063                 $performOps = $this->getOperationsInternal( $ops );
01064 
01065                 // Acquire any locks as needed...
01066                 if ( empty( $opts['nonLocking'] ) ) {
01067                         // Build up a list of files to lock...
01068                         $paths = $this->getPathsToLockForOpsInternal( $performOps );
01069                         // Try to lock those files for the scope of this function...
01070                         $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
01071                         $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
01072                         if ( !$status->isOK() ) {
01073                                 wfProfileOut( __METHOD__ . '-' . $this->name );
01074                                 wfProfileOut( __METHOD__ );
01075                                 return $status; // abort
01076                         }
01077                 }
01078 
01079                 // Clear any file cache entries (after locks acquired)
01080                 if ( empty( $opts['preserveCache'] ) ) {
01081                         $this->clearCache();
01082                 }
01083 
01084                 // Load from the persistent file and container caches
01085                 $this->primeFileCache( $performOps );
01086                 $this->primeContainerCache( $performOps );
01087 
01088                 // Actually attempt the operation batch...
01089                 $opts = $this->setConcurrencyFlags( $opts );
01090                 $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
01091 
01092                 // Merge errors into status fields
01093                 $status->merge( $subStatus );
01094                 $status->success = $subStatus->success; // not done in merge()
01095 
01096                 wfProfileOut( __METHOD__ . '-' . $this->name );
01097                 wfProfileOut( __METHOD__ );
01098                 return $status;
01099         }
01100 
01106         final protected function doQuickOperationsInternal( array $ops ) {
01107                 wfProfileIn( __METHOD__ );
01108                 wfProfileIn( __METHOD__ . '-' . $this->name );
01109                 $status = Status::newGood();
01110 
01111                 $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
01112                 $async = ( $this->parallelize === 'implicit' );
01113                 $maxConcurrency = $this->concurrency; // throttle
01114 
01115                 $statuses = array(); // array of (index => Status)
01116                 $fileOpHandles = array(); // list of (index => handle) arrays
01117                 $curFileOpHandles = array(); // current handle batch
01118                 // Perform the sync-only ops and build up op handles for the async ops...
01119                 foreach ( $ops as $index => $params ) {
01120                         if ( !in_array( $params['op'], $supportedOps ) ) {
01121                                 wfProfileOut( __METHOD__ . '-' . $this->name );
01122                                 wfProfileOut( __METHOD__ );
01123                                 throw new MWException( "Operation '{$params['op']}' is not supported." );
01124                         }
01125                         $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
01126                         $subStatus = $this->$method( array( 'async' => $async ) + $params );
01127                         if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async
01128                                 if ( count( $curFileOpHandles ) >= $maxConcurrency ) {
01129                                         $fileOpHandles[] = $curFileOpHandles; // push this batch
01130                                         $curFileOpHandles = array();
01131                                 }
01132                                 $curFileOpHandles[$index] = $subStatus->value; // keep index
01133                         } else { // error or completed
01134                                 $statuses[$index] = $subStatus; // keep index
01135                         }
01136                 }
01137                 if ( count( $curFileOpHandles ) ) {
01138                         $fileOpHandles[] = $curFileOpHandles; // last batch
01139                 }
01140                 // Do all the async ops that can be done concurrently...
01141                 foreach ( $fileOpHandles as $fileHandleBatch ) {
01142                         $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch );
01143                 }
01144                 // Marshall and merge all the responses...
01145                 foreach ( $statuses as $index => $subStatus ) {
01146                         $status->merge( $subStatus );
01147                         if ( $subStatus->isOK() ) {
01148                                 $status->success[$index] = true;
01149                                 ++$status->successCount;
01150                         } else {
01151                                 $status->success[$index] = false;
01152                                 ++$status->failCount;
01153                         }
01154                 }
01155 
01156                 wfProfileOut( __METHOD__ . '-' . $this->name );
01157                 wfProfileOut( __METHOD__ );
01158                 return $status;
01159         }
01160 
01170         final public function executeOpHandlesInternal( array $fileOpHandles ) {
01171                 wfProfileIn( __METHOD__ );
01172                 wfProfileIn( __METHOD__ . '-' . $this->name );
01173                 foreach ( $fileOpHandles as $fileOpHandle ) {
01174                         if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
01175                                 throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
01176                         } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
01177                                 throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." );
01178                         }
01179                 }
01180                 $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
01181                 foreach ( $fileOpHandles as $fileOpHandle ) {
01182                         $fileOpHandle->closeResources();
01183                 }
01184                 wfProfileOut( __METHOD__ . '-' . $this->name );
01185                 wfProfileOut( __METHOD__ );
01186                 return $res;
01187         }
01188 
01195         protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
01196                 foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
01197                         throw new MWException( "This backend supports no asynchronous operations." );
01198                 }
01199                 return array();
01200         }
01201 
01205         final public function preloadCache( array $paths ) {
01206                 $fullConts = array(); // full container names
01207                 foreach ( $paths as $path ) {
01208                         list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
01209                         $fullConts[] = $fullCont;
01210                 }
01211                 // Load from the persistent file and container caches
01212                 $this->primeContainerCache( $fullConts );
01213                 $this->primeFileCache( $paths );
01214         }
01215 
01219         final public function clearCache( array $paths = null ) {
01220                 if ( is_array( $paths ) ) {
01221                         $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
01222                         $paths = array_filter( $paths, 'strlen' ); // remove nulls
01223                 }
01224                 if ( $paths === null ) {
01225                         $this->cheapCache->clear();
01226                         $this->expensiveCache->clear();
01227                 } else {
01228                         foreach ( $paths as $path ) {
01229                                 $this->cheapCache->clear( $path );
01230                                 $this->expensiveCache->clear( $path );
01231                         }
01232                 }
01233                 $this->doClearCache( $paths );
01234         }
01235 
01244         protected function doClearCache( array $paths = null ) {}
01245 
01253         abstract protected function directoriesAreVirtual();
01254 
01262         final protected static function isValidContainerName( $container ) {
01263                 // This accounts for Swift and S3 restrictions while leaving room
01264                 // for things like '.xxx' (hex shard chars) or '.seg' (segments).
01265                 // This disallows directory separators or traversal characters.
01266                 // Note that matching strings URL encode to the same string;
01267                 // in Swift, the length restriction is *after* URL encoding.
01268                 return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container );
01269         }
01270 
01284         final protected function resolveStoragePath( $storagePath ) {
01285                 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
01286                 if ( $backend === $this->name ) { // must be for this backend
01287                         $relPath = self::normalizeContainerPath( $relPath );
01288                         if ( $relPath !== null ) {
01289                                 // Get shard for the normalized path if this container is sharded
01290                                 $cShard = $this->getContainerShard( $container, $relPath );
01291                                 // Validate and sanitize the relative path (backend-specific)
01292                                 $relPath = $this->resolveContainerPath( $container, $relPath );
01293                                 if ( $relPath !== null ) {
01294                                         // Prepend any wiki ID prefix to the container name
01295                                         $container = $this->fullContainerName( $container );
01296                                         if ( self::isValidContainerName( $container ) ) {
01297                                                 // Validate and sanitize the container name (backend-specific)
01298                                                 $container = $this->resolveContainerName( "{$container}{$cShard}" );
01299                                                 if ( $container !== null ) {
01300                                                         return array( $container, $relPath, $cShard );
01301                                                 }
01302                                         }
01303                                 }
01304                         }
01305                 }
01306                 return array( null, null, null );
01307         }
01308 
01318         final protected function resolveStoragePathReal( $storagePath ) {
01319                 list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
01320                 if ( $cShard !== null ) {
01321                         return array( $container, $relPath );
01322                 }
01323                 return array( null, null );
01324         }
01325 
01334         final protected function getContainerShard( $container, $relPath ) {
01335                 list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container );
01336                 if ( $levels == 1 || $levels == 2 ) {
01337                         // Hash characters are either base 16 or 36
01338                         $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]';
01339                         // Get a regex that represents the shard portion of paths.
01340                         // The concatenation of the captures gives us the shard.
01341                         if ( $levels === 1 ) { // 16 or 36 shards per container
01342                                 $hashDirRegex = '(' . $char . ')';
01343                         } else { // 256 or 1296 shards per container
01344                                 if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc")
01345                                         $hashDirRegex = $char . '/(' . $char . '{2})';
01346                                 } else { // short hash dir format (e.g. "a/b/c")
01347                                         $hashDirRegex = '(' . $char . ')/(' . $char . ')';
01348                                 }
01349                         }
01350                         // Allow certain directories to be above the hash dirs so as
01351                         // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab").
01352                         // They must be 2+ chars to avoid any hash directory ambiguity.
01353                         $m = array();
01354                         if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
01355                                 return '.' . implode( '', array_slice( $m, 1 ) );
01356                         }
01357                         return null; // failed to match
01358                 }
01359                 return ''; // no sharding
01360         }
01361 
01370         final public function isSingleShardPathInternal( $storagePath ) {
01371                 list( $c, $r, $shard ) = $this->resolveStoragePath( $storagePath );
01372                 return ( $shard !== null );
01373         }
01374 
01383         final protected function getContainerHashLevels( $container ) {
01384                 if ( isset( $this->shardViaHashLevels[$container] ) ) {
01385                         $config = $this->shardViaHashLevels[$container];
01386                         $hashLevels = (int)$config['levels'];
01387                         if ( $hashLevels == 1 || $hashLevels == 2 ) {
01388                                 $hashBase = (int)$config['base'];
01389                                 if ( $hashBase == 16 || $hashBase == 36 ) {
01390                                         return array( $hashLevels, $hashBase, $config['repeat'] );
01391                                 }
01392                         }
01393                 }
01394                 return array( 0, 0, false ); // no sharding
01395         }
01396 
01403         final protected function getContainerSuffixes( $container ) {
01404                 $shards = array();
01405                 list( $digits, $base ) = $this->getContainerHashLevels( $container );
01406                 if ( $digits > 0 ) {
01407                         $numShards = pow( $base, $digits );
01408                         for ( $index = 0; $index < $numShards; $index++ ) {
01409                                 $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits );
01410                         }
01411                 }
01412                 return $shards;
01413         }
01414 
01421         final protected function fullContainerName( $container ) {
01422                 if ( $this->wikiId != '' ) {
01423                         return "{$this->wikiId}-$container";
01424                 } else {
01425                         return $container;
01426                 }
01427         }
01428 
01437         protected function resolveContainerName( $container ) {
01438                 return $container;
01439         }
01440 
01451         protected function resolveContainerPath( $container, $relStoragePath ) {
01452                 return $relStoragePath;
01453         }
01454 
01461         private function containerCacheKey( $container ) {
01462                 return wfMemcKey( 'backend', $this->getName(), 'container', $container );
01463         }
01464 
01471         final protected function setContainerCache( $container, $val ) {
01472                 $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 );
01473         }
01474 
01481         final protected function deleteContainerCache( $container ) {
01482                 if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
01483                         trigger_error( "Unable to delete stat cache for container $container." );
01484                 }
01485         }
01486 
01495         final protected function primeContainerCache( array $items ) {
01496                 wfProfileIn( __METHOD__ );
01497                 wfProfileIn( __METHOD__ . '-' . $this->name );
01498 
01499                 $paths = array(); // list of storage paths
01500                 $contNames = array(); // (cache key => resolved container name)
01501                 // Get all the paths/containers from the items...
01502                 foreach ( $items as $item ) {
01503                         if ( $item instanceof FileOp ) {
01504                                 $paths = array_merge( $paths, $item->storagePathsRead() );
01505                                 $paths = array_merge( $paths, $item->storagePathsChanged() );
01506                         } elseif ( self::isStoragePath( $item ) ) {
01507                                 $paths[] = $item;
01508                         } elseif ( is_string( $item ) ) { // full container name
01509                                 $contNames[$this->containerCacheKey( $item )] = $item;
01510                         }
01511                 }
01512                 // Get all the corresponding cache keys for paths...
01513                 foreach ( $paths as $path ) {
01514                         list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path );
01515                         if ( $fullCont !== null ) { // valid path for this backend
01516                                 $contNames[$this->containerCacheKey( $fullCont )] = $fullCont;
01517                         }
01518                 }
01519 
01520                 $contInfo = array(); // (resolved container name => cache value)
01521                 // Get all cache entries for these container cache keys...
01522                 $values = $this->memCache->getMulti( array_keys( $contNames ) );
01523                 foreach ( $values as $cacheKey => $val ) {
01524                         $contInfo[$contNames[$cacheKey]] = $val;
01525                 }
01526 
01527                 // Populate the container process cache for the backend...
01528                 $this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
01529 
01530                 wfProfileOut( __METHOD__ . '-' . $this->name );
01531                 wfProfileOut( __METHOD__ );
01532         }
01533 
01542         protected function doPrimeContainerCache( array $containerInfo ) {}
01543 
01550         private function fileCacheKey( $path ) {
01551                 return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) );
01552         }
01553 
01562         final protected function setFileCache( $path, $val ) {
01563                 $path = FileBackend::normalizeStoragePath( $path );
01564                 if ( $path === null ) {
01565                         return; // invalid storage path
01566                 }
01567                 $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 );
01568         }
01569 
01576         final protected function deleteFileCache( $path ) {
01577                 $path = FileBackend::normalizeStoragePath( $path );
01578                 if ( $path === null ) {
01579                         return; // invalid storage path
01580                 }
01581                 if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) {
01582                         trigger_error( "Unable to delete stat cache for file $path." );
01583                 }
01584         }
01585 
01594         final protected function primeFileCache( array $items ) {
01595                 wfProfileIn( __METHOD__ );
01596                 wfProfileIn( __METHOD__ . '-' . $this->name );
01597 
01598                 $paths = array(); // list of storage paths
01599                 $pathNames = array(); // (cache key => storage path)
01600                 // Get all the paths/containers from the items...
01601                 foreach ( $items as $item ) {
01602                         if ( $item instanceof FileOp ) {
01603                                 $paths = array_merge( $paths, $item->storagePathsRead() );
01604                                 $paths = array_merge( $paths, $item->storagePathsChanged() );
01605                         } elseif ( self::isStoragePath( $item ) ) {
01606                                 $paths[] = FileBackend::normalizeStoragePath( $item );
01607                         }
01608                 }
01609                 // Get rid of any paths that failed normalization...
01610                 $paths = array_filter( $paths, 'strlen' ); // remove nulls
01611                 // Get all the corresponding cache keys for paths...
01612                 foreach ( $paths as $path ) {
01613                         list( $cont, $rel, $s ) = $this->resolveStoragePath( $path );
01614                         if ( $rel !== null ) { // valid path for this backend
01615                                 $pathNames[$this->fileCacheKey( $path )] = $path;
01616                         }
01617                 }
01618                 // Get all cache entries for these container cache keys...
01619                 $values = $this->memCache->getMulti( array_keys( $pathNames ) );
01620                 foreach ( $values as $cacheKey => $val ) {
01621                         if ( is_array( $val ) ) {
01622                                 $path = $pathNames[$cacheKey];
01623                                 $this->cheapCache->set( $path, 'stat', $val );
01624                                 if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
01625                                         $this->cheapCache->set( $path, 'sha1',
01626                                                 array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) );
01627                                 }
01628                         }
01629                 }
01630 
01631                 wfProfileOut( __METHOD__ . '-' . $this->name );
01632                 wfProfileOut( __METHOD__ );
01633         }
01634 
01641         final protected function setConcurrencyFlags( array $opts ) {
01642                 $opts['concurrency'] = 1; // off
01643                 if ( $this->parallelize === 'implicit' ) {
01644                         if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
01645                                 $opts['concurrency'] = $this->concurrency;
01646                         }
01647                 } elseif ( $this->parallelize === 'explicit' ) {
01648                         if ( !empty( $opts['parallelize'] ) ) {
01649                                 $opts['concurrency'] = $this->concurrency;
01650                         }
01651                 }
01652                 return $opts;
01653         }
01654 }
01655 
01664 abstract class FileBackendStoreOpHandle {
01666         public $params = array(); // params to caller functions
01668         public $backend;
01670         public $resourcesToClose = array();
01671 
01672         public $call; // string; name that identifies the function called
01673 
01679         public function closeResources() {
01680                 array_map( 'fclose', $this->resourcesToClose );
01681         }
01682 }
01683 
01690 abstract class FileBackendStoreShardListIterator implements Iterator {
01692         protected $backend;
01694         protected $params;
01696         protected $shardSuffixes;
01697         protected $container; // string; full container name
01698         protected $directory; // string; resolved relative path
01699 
01701         protected $iter;
01702         protected $curShard = 0; // integer
01703         protected $pos = 0; // integer
01704 
01706         protected $multiShardPaths = array(); // (rel path => 1)
01707 
01715         public function __construct(
01716                 FileBackendStore $backend, $container, $dir, array $suffixes, array $params
01717         ) {
01718                 $this->backend = $backend;
01719                 $this->container = $container;
01720                 $this->directory = $dir;
01721                 $this->shardSuffixes = $suffixes;
01722                 $this->params = $params;
01723         }
01724 
01729         public function key() {
01730                 return $this->pos;
01731         }
01732 
01737         public function valid() {
01738                 if ( $this->iter instanceof Iterator ) {
01739                         return $this->iter->valid();
01740                 } elseif ( is_array( $this->iter ) ) {
01741                         return ( current( $this->iter ) !== false ); // no paths can have this value
01742                 }
01743                 return false; // some failure?
01744         }
01745 
01750         public function current() {
01751                 return ( $this->iter instanceof Iterator )
01752                         ? $this->iter->current()
01753                         : current( $this->iter );
01754         }
01755 
01760         public function next() {
01761                 ++$this->pos;
01762                 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
01763                 do {
01764                         $continue = false; // keep scanning shards?
01765                         $this->filterViaNext(); // filter out duplicates
01766                         // Find the next non-empty shard if no elements are left
01767                         if ( !$this->valid() ) {
01768                                 $this->nextShardIteratorIfNotValid();
01769                                 $continue = $this->valid(); // re-filter unless we ran out of shards
01770                         }
01771                 } while ( $continue );
01772         }
01773 
01778         public function rewind() {
01779                 $this->pos = 0;
01780                 $this->curShard = 0;
01781                 $this->setIteratorFromCurrentShard();
01782                 do {
01783                         $continue = false; // keep scanning shards?
01784                         $this->filterViaNext(); // filter out duplicates
01785                         // Find the next non-empty shard if no elements are left
01786                         if ( !$this->valid() ) {
01787                                 $this->nextShardIteratorIfNotValid();
01788                                 $continue = $this->valid(); // re-filter unless we ran out of shards
01789                         }
01790                 } while ( $continue );
01791         }
01792 
01796         protected function filterViaNext() {
01797                 while ( $this->valid() ) {
01798                         $rel = $this->iter->current(); // path relative to given directory
01799                         $path = $this->params['dir'] . "/{$rel}"; // full storage path
01800                         if ( $this->backend->isSingleShardPathInternal( $path ) ) {
01801                                 break; // path is only on one shard; no issue with duplicates
01802                         } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
01803                                 // Don't keep listing paths that are on multiple shards
01804                                 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
01805                         } else {
01806                                 $this->multiShardPaths[$rel] = 1;
01807                                 break;
01808                         }
01809                 }
01810         }
01811 
01817         protected function nextShardIteratorIfNotValid() {
01818                 while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) {
01819                         $this->setIteratorFromCurrentShard();
01820                 }
01821         }
01822 
01826         protected function setIteratorFromCurrentShard() {
01827                 $this->iter = $this->listFromShard(
01828                         $this->container . $this->shardSuffixes[$this->curShard],
01829                         $this->directory, $this->params );
01830                 // Start loading results so that current() works
01831                 if ( $this->iter ) {
01832                         ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter );
01833                 }
01834         }
01835 
01844         abstract protected function listFromShard( $container, $dir, array $params );
01845 }
01846 
01850 class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
01855         protected function listFromShard( $container, $dir, array $params ) {
01856                 return $this->backend->getDirectoryListInternal( $container, $dir, $params );
01857         }
01858 }
01859 
01863 class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
01868         protected function listFromShard( $container, $dir, array $params ) {
01869                 return $this->backend->getFileListInternal( $container, $dir, $params );
01870         }
01871 }