MediaWiki  REL1_21
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 
00051         const CACHE_TTL = 10; // integer; TTL in seconds for process cache entries
00052 
00058         public function __construct( array $config ) {
00059                 parent::__construct( $config );
00060                 $this->memCache = new EmptyBagOStuff(); // disabled by default
00061                 $this->cheapCache = new ProcessCacheLRU( 300 );
00062                 $this->expensiveCache = new ProcessCacheLRU( 5 );
00063         }
00064 
00072         final public function maxFileSizeInternal() {
00073                 return $this->maxFileSize;
00074         }
00075 
00085         abstract public function isPathUsableInternal( $storagePath );
00086 
00106         final public function createInternal( array $params ) {
00107                 wfProfileIn( __METHOD__ );
00108                 wfProfileIn( __METHOD__ . '-' . $this->name );
00109                 if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
00110                         $status = Status::newFatal( 'backend-fail-maxsize',
00111                                 $params['dst'], $this->maxFileSizeInternal() );
00112                 } else {
00113                         $status = $this->doCreateInternal( $params );
00114                         $this->clearCache( array( $params['dst'] ) );
00115                         if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
00116                                 $this->deleteFileCache( $params['dst'] ); // persistent cache
00117                         }
00118                 }
00119                 wfProfileOut( __METHOD__ . '-' . $this->name );
00120                 wfProfileOut( __METHOD__ );
00121                 return $status;
00122         }
00123 
00128         abstract protected function doCreateInternal( array $params );
00129 
00149         final public function storeInternal( array $params ) {
00150                 wfProfileIn( __METHOD__ );
00151                 wfProfileIn( __METHOD__ . '-' . $this->name );
00152                 if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
00153                         $status = Status::newFatal( 'backend-fail-maxsize',
00154                                 $params['dst'], $this->maxFileSizeInternal() );
00155                 } else {
00156                         $status = $this->doStoreInternal( $params );
00157                         $this->clearCache( array( $params['dst'] ) );
00158                         if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
00159                                 $this->deleteFileCache( $params['dst'] ); // persistent cache
00160                         }
00161                 }
00162                 wfProfileOut( __METHOD__ . '-' . $this->name );
00163                 wfProfileOut( __METHOD__ );
00164                 return $status;
00165         }
00166 
00171         abstract protected function doStoreInternal( array $params );
00172 
00192         final public function copyInternal( array $params ) {
00193                 wfProfileIn( __METHOD__ );
00194                 wfProfileIn( __METHOD__ . '-' . $this->name );
00195                 $status = $this->doCopyInternal( $params );
00196                 $this->clearCache( array( $params['dst'] ) );
00197                 if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
00198                         $this->deleteFileCache( $params['dst'] ); // persistent cache
00199                 }
00200                 wfProfileOut( __METHOD__ . '-' . $this->name );
00201                 wfProfileOut( __METHOD__ );
00202                 return $status;
00203         }
00204 
00209         abstract protected function doCopyInternal( array $params );
00210 
00225         final public function deleteInternal( array $params ) {
00226                 wfProfileIn( __METHOD__ );
00227                 wfProfileIn( __METHOD__ . '-' . $this->name );
00228                 $status = $this->doDeleteInternal( $params );
00229                 $this->clearCache( array( $params['src'] ) );
00230                 $this->deleteFileCache( $params['src'] ); // persistent cache
00231                 wfProfileOut( __METHOD__ . '-' . $this->name );
00232                 wfProfileOut( __METHOD__ );
00233                 return $status;
00234         }
00235 
00240         abstract protected function doDeleteInternal( array $params );
00241 
00261         final public function moveInternal( array $params ) {
00262                 wfProfileIn( __METHOD__ );
00263                 wfProfileIn( __METHOD__ . '-' . $this->name );
00264                 $status = $this->doMoveInternal( $params );
00265                 $this->clearCache( array( $params['src'], $params['dst'] ) );
00266                 $this->deleteFileCache( $params['src'] ); // persistent cache
00267                 if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
00268                         $this->deleteFileCache( $params['dst'] ); // persistent cache
00269                 }
00270                 wfProfileOut( __METHOD__ . '-' . $this->name );
00271                 wfProfileOut( __METHOD__ );
00272                 return $status;
00273         }
00274 
00279         protected function doMoveInternal( array $params ) {
00280                 unset( $params['async'] ); // two steps, won't work here :)
00281                 // Copy source to dest
00282                 $status = $this->copyInternal( $params );
00283                 if ( $status->isOK() ) {
00284                         // Delete source (only fails due to races or medium going down)
00285                         $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
00286                         $status->setResult( true, $status->value ); // ignore delete() errors
00287                 }
00288                 return $status;
00289         }
00290 
00306         final public function describeInternal( array $params ) {
00307                 wfProfileIn( __METHOD__ );
00308                 wfProfileIn( __METHOD__ . '-' . $this->name );
00309                 $status = $this->doDescribeInternal( $params );
00310                 $this->clearCache( array( $params['src'] ) );
00311                 $this->deleteFileCache( $params['src'] ); // persistent cache
00312                 wfProfileOut( __METHOD__ . '-' . $this->name );
00313                 wfProfileOut( __METHOD__ );
00314                 return $status;
00315         }
00316 
00321         protected function doDescribeInternal( array $params ) {
00322                 return Status::newGood();
00323         }
00324 
00332         final public function nullInternal( array $params ) {
00333                 return Status::newGood();
00334         }
00335 
00340         final public function concatenate( array $params ) {
00341                 wfProfileIn( __METHOD__ );
00342                 wfProfileIn( __METHOD__ . '-' . $this->name );
00343                 $status = Status::newGood();
00344 
00345                 // Try to lock the source files for the scope of this function
00346                 $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
00347                 if ( $status->isOK() ) {
00348                         // Actually do the file concatenation...
00349                         $start_time = microtime( true );
00350                         $status->merge( $this->doConcatenate( $params ) );
00351                         $sec = microtime( true ) - $start_time;
00352                         if ( !$status->isOK() ) {
00353                                 wfDebugLog( 'FileOperation', get_class( $this ) . " failed to concatenate " .
00354                                         count( $params['srcs'] ) . " file(s) [$sec sec]" );
00355                         }
00356                 }
00357 
00358                 wfProfileOut( __METHOD__ . '-' . $this->name );
00359                 wfProfileOut( __METHOD__ );
00360                 return $status;
00361         }
00362 
00367         protected function doConcatenate( array $params ) {
00368                 $status = Status::newGood();
00369                 $tmpPath = $params['dst']; // convenience
00370                 unset( $params['latest'] ); // sanity
00371 
00372                 // Check that the specified temp file is valid...
00373                 wfSuppressWarnings();
00374                 $ok = ( is_file( $tmpPath ) && filesize( $tmpPath ) == 0 );
00375                 wfRestoreWarnings();
00376                 if ( !$ok ) { // not present or not empty
00377                         $status->fatal( 'backend-fail-opentemp', $tmpPath );
00378                         return $status;
00379                 }
00380 
00381                 // Get local FS versions of the chunks needed for the concatenation...
00382                 $fsFiles = $this->getLocalReferenceMulti( $params );
00383                 foreach ( $fsFiles as $path => &$fsFile ) {
00384                         if ( !$fsFile ) { // chunk failed to download?
00385                                 $fsFile = $this->getLocalReference( array( 'src' => $path ) );
00386                                 if ( !$fsFile ) { // retry failed?
00387                                         $status->fatal( 'backend-fail-read', $path );
00388                                         return $status;
00389                                 }
00390                         }
00391                 }
00392                 unset( $fsFile ); // unset reference so we can reuse $fsFile
00393 
00394                 // Get a handle for the destination temp file
00395                 $tmpHandle = fopen( $tmpPath, 'ab' );
00396                 if ( $tmpHandle === false ) {
00397                         $status->fatal( 'backend-fail-opentemp', $tmpPath );
00398                         return $status;
00399                 }
00400 
00401                 // Build up the temp file using the source chunks (in order)...
00402                 foreach ( $fsFiles as $virtualSource => $fsFile ) {
00403                         // Get a handle to the local FS version
00404                         $sourceHandle = fopen( $fsFile->getPath(), 'rb' );
00405                         if ( $sourceHandle === false ) {
00406                                 fclose( $tmpHandle );
00407                                 $status->fatal( 'backend-fail-read', $virtualSource );
00408                                 return $status;
00409                         }
00410                         // Append chunk to file (pass chunk size to avoid magic quotes)
00411                         if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
00412                                 fclose( $sourceHandle );
00413                                 fclose( $tmpHandle );
00414                                 $status->fatal( 'backend-fail-writetemp', $tmpPath );
00415                                 return $status;
00416                         }
00417                         fclose( $sourceHandle );
00418                 }
00419                 if ( !fclose( $tmpHandle ) ) {
00420                         $status->fatal( 'backend-fail-closetemp', $tmpPath );
00421                         return $status;
00422                 }
00423 
00424                 clearstatcache(); // temp file changed
00425 
00426                 return $status;
00427         }
00428 
00433         final protected function doPrepare( array $params ) {
00434                 wfProfileIn( __METHOD__ );
00435                 wfProfileIn( __METHOD__ . '-' . $this->name );
00436 
00437                 $status = Status::newGood();
00438                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00439                 if ( $dir === null ) {
00440                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00441                         wfProfileOut( __METHOD__ . '-' . $this->name );
00442                         wfProfileOut( __METHOD__ );
00443                         return $status; // invalid storage path
00444                 }
00445 
00446                 if ( $shard !== null ) { // confined to a single container/shard
00447                         $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) );
00448                 } else { // directory is on several shards
00449                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00450                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
00451                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00452                                 $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00453                         }
00454                 }
00455 
00456                 wfProfileOut( __METHOD__ . '-' . $this->name );
00457                 wfProfileOut( __METHOD__ );
00458                 return $status;
00459         }
00460 
00465         protected function doPrepareInternal( $container, $dir, array $params ) {
00466                 return Status::newGood();
00467         }
00468 
00473         final protected function doSecure( array $params ) {
00474                 wfProfileIn( __METHOD__ );
00475                 wfProfileIn( __METHOD__ . '-' . $this->name );
00476                 $status = Status::newGood();
00477 
00478                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00479                 if ( $dir === null ) {
00480                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00481                         wfProfileOut( __METHOD__ . '-' . $this->name );
00482                         wfProfileOut( __METHOD__ );
00483                         return $status; // invalid storage path
00484                 }
00485 
00486                 if ( $shard !== null ) { // confined to a single container/shard
00487                         $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
00488                 } else { // directory is on several shards
00489                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00490                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
00491                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00492                                 $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00493                         }
00494                 }
00495 
00496                 wfProfileOut( __METHOD__ . '-' . $this->name );
00497                 wfProfileOut( __METHOD__ );
00498                 return $status;
00499         }
00500 
00505         protected function doSecureInternal( $container, $dir, array $params ) {
00506                 return Status::newGood();
00507         }
00508 
00513         final protected function doPublish( array $params ) {
00514                 wfProfileIn( __METHOD__ );
00515                 wfProfileIn( __METHOD__ . '-' . $this->name );
00516                 $status = Status::newGood();
00517 
00518                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00519                 if ( $dir === null ) {
00520                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00521                         wfProfileOut( __METHOD__ . '-' . $this->name );
00522                         wfProfileOut( __METHOD__ );
00523                         return $status; // invalid storage path
00524                 }
00525 
00526                 if ( $shard !== null ) { // confined to a single container/shard
00527                         $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) );
00528                 } else { // directory is on several shards
00529                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00530                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
00531                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00532                                 $status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00533                         }
00534                 }
00535 
00536                 wfProfileOut( __METHOD__ . '-' . $this->name );
00537                 wfProfileOut( __METHOD__ );
00538                 return $status;
00539         }
00540 
00545         protected function doPublishInternal( $container, $dir, array $params ) {
00546                 return Status::newGood();
00547         }
00548 
00553         final protected function doClean( array $params ) {
00554                 wfProfileIn( __METHOD__ );
00555                 wfProfileIn( __METHOD__ . '-' . $this->name );
00556                 $status = Status::newGood();
00557 
00558                 // Recursive: first delete all empty subdirs recursively
00559                 if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) {
00560                         $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) );
00561                         if ( $subDirsRel !== null ) { // no errors
00562                                 foreach ( $subDirsRel as $subDirRel ) {
00563                                         $subDir = $params['dir'] . "/{$subDirRel}"; // full path
00564                                         $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) );
00565                                 }
00566                                 unset( $subDirsRel ); // free directory for rmdir() on Windows (for FS backends)
00567                         }
00568                 }
00569 
00570                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00571                 if ( $dir === null ) {
00572                         $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
00573                         wfProfileOut( __METHOD__ . '-' . $this->name );
00574                         wfProfileOut( __METHOD__ );
00575                         return $status; // invalid storage path
00576                 }
00577 
00578                 // Attempt to lock this directory...
00579                 $filesLockEx = array( $params['dir'] );
00580                 $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
00581                 if ( !$status->isOK() ) {
00582                         wfProfileOut( __METHOD__ . '-' . $this->name );
00583                         wfProfileOut( __METHOD__ );
00584                         return $status; // abort
00585                 }
00586 
00587                 if ( $shard !== null ) { // confined to a single container/shard
00588                         $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
00589                         $this->deleteContainerCache( $fullCont ); // purge cache
00590                 } else { // directory is on several shards
00591                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00592                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
00593                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00594                                 $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
00595                                 $this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache
00596                         }
00597                 }
00598 
00599                 wfProfileOut( __METHOD__ . '-' . $this->name );
00600                 wfProfileOut( __METHOD__ );
00601                 return $status;
00602         }
00603 
00608         protected function doCleanInternal( $container, $dir, array $params ) {
00609                 return Status::newGood();
00610         }
00611 
00616         final public function fileExists( array $params ) {
00617                 wfProfileIn( __METHOD__ );
00618                 wfProfileIn( __METHOD__ . '-' . $this->name );
00619                 $stat = $this->getFileStat( $params );
00620                 wfProfileOut( __METHOD__ . '-' . $this->name );
00621                 wfProfileOut( __METHOD__ );
00622                 return ( $stat === null ) ? null : (bool)$stat; // null => failure
00623         }
00624 
00629         final public function getFileTimestamp( array $params ) {
00630                 wfProfileIn( __METHOD__ );
00631                 wfProfileIn( __METHOD__ . '-' . $this->name );
00632                 $stat = $this->getFileStat( $params );
00633                 wfProfileOut( __METHOD__ . '-' . $this->name );
00634                 wfProfileOut( __METHOD__ );
00635                 return $stat ? $stat['mtime'] : false;
00636         }
00637 
00642         final public function getFileSize( array $params ) {
00643                 wfProfileIn( __METHOD__ );
00644                 wfProfileIn( __METHOD__ . '-' . $this->name );
00645                 $stat = $this->getFileStat( $params );
00646                 wfProfileOut( __METHOD__ . '-' . $this->name );
00647                 wfProfileOut( __METHOD__ );
00648                 return $stat ? $stat['size'] : false;
00649         }
00650 
00655         final public function getFileStat( array $params ) {
00656                 $path = self::normalizeStoragePath( $params['src'] );
00657                 if ( $path === null ) {
00658                         return false; // invalid storage path
00659                 }
00660                 wfProfileIn( __METHOD__ );
00661                 wfProfileIn( __METHOD__ . '-' . $this->name );
00662                 $latest = !empty( $params['latest'] ); // use latest data?
00663                 if ( !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
00664                         $this->primeFileCache( array( $path ) ); // check persistent cache
00665                 }
00666                 if ( $this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
00667                         $stat = $this->cheapCache->get( $path, 'stat' );
00668                         // If we want the latest data, check that this cached
00669                         // value was in fact fetched with the latest available data.
00670                         if ( is_array( $stat ) ) {
00671                                 if ( !$latest || $stat['latest'] ) {
00672                                         wfProfileOut( __METHOD__ . '-' . $this->name );
00673                                         wfProfileOut( __METHOD__ );
00674                                         return $stat;
00675                                 }
00676                         } elseif ( in_array( $stat, array( 'NOT_EXIST', 'NOT_EXIST_LATEST' ) ) ) {
00677                                 if ( !$latest || $stat === 'NOT_EXIST_LATEST' ) {
00678                                         wfProfileOut( __METHOD__ . '-' . $this->name );
00679                                         wfProfileOut( __METHOD__ );
00680                                         return false;
00681                                 }
00682                         }
00683                 }
00684                 wfProfileIn( __METHOD__ . '-miss' );
00685                 wfProfileIn( __METHOD__ . '-miss-' . $this->name );
00686                 $stat = $this->doGetFileStat( $params );
00687                 wfProfileOut( __METHOD__ . '-miss-' . $this->name );
00688                 wfProfileOut( __METHOD__ . '-miss' );
00689                 if ( is_array( $stat ) ) { // file exists
00690                         $stat['latest'] = $latest;
00691                         $this->cheapCache->set( $path, 'stat', $stat );
00692                         $this->setFileCache( $path, $stat ); // update persistent cache
00693                         if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata
00694                                 $this->cheapCache->set( $path, 'sha1',
00695                                         array( 'hash' => $stat['sha1'], 'latest' => $latest ) );
00696                         }
00697                 } elseif ( $stat === false ) { // file does not exist
00698                         $this->cheapCache->set( $path, 'stat', $latest ? 'NOT_EXIST_LATEST' : 'NOT_EXIST' );
00699                         wfDebug( __METHOD__ . ": File $path does not exist.\n" );
00700                 } else { // an error occurred
00701                         wfDebug( __METHOD__ . ": Could not stat file $path.\n" );
00702                 }
00703                 wfProfileOut( __METHOD__ . '-' . $this->name );
00704                 wfProfileOut( __METHOD__ );
00705                 return $stat;
00706         }
00707 
00711         abstract protected function doGetFileStat( array $params );
00712 
00717         public function getFileContentsMulti( array $params ) {
00718                 wfProfileIn( __METHOD__ );
00719                 wfProfileIn( __METHOD__ . '-' . $this->name );
00720 
00721                 $params = $this->setConcurrencyFlags( $params );
00722                 $contents = $this->doGetFileContentsMulti( $params );
00723 
00724                 wfProfileOut( __METHOD__ . '-' . $this->name );
00725                 wfProfileOut( __METHOD__ );
00726                 return $contents;
00727         }
00728 
00733         protected function doGetFileContentsMulti( array $params ) {
00734                 $contents = array();
00735                 foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
00736                         wfSuppressWarnings();
00737                         $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false;
00738                         wfRestoreWarnings();
00739                 }
00740                 return $contents;
00741         }
00742 
00747         final public function getFileSha1Base36( array $params ) {
00748                 $path = self::normalizeStoragePath( $params['src'] );
00749                 if ( $path === null ) {
00750                         return false; // invalid storage path
00751                 }
00752                 wfProfileIn( __METHOD__ );
00753                 wfProfileIn( __METHOD__ . '-' . $this->name );
00754                 $latest = !empty( $params['latest'] ); // use latest data?
00755                 if ( $this->cheapCache->has( $path, 'sha1', self::CACHE_TTL ) ) {
00756                         $stat = $this->cheapCache->get( $path, 'sha1' );
00757                         // If we want the latest data, check that this cached
00758                         // value was in fact fetched with the latest available data.
00759                         if ( !$latest || $stat['latest'] ) {
00760                                 wfProfileOut( __METHOD__ . '-' . $this->name );
00761                                 wfProfileOut( __METHOD__ );
00762                                 return $stat['hash'];
00763                         }
00764                 }
00765                 wfProfileIn( __METHOD__ . '-miss' );
00766                 wfProfileIn( __METHOD__ . '-miss-' . $this->name );
00767                 $hash = $this->doGetFileSha1Base36( $params );
00768                 wfProfileOut( __METHOD__ . '-miss-' . $this->name );
00769                 wfProfileOut( __METHOD__ . '-miss' );
00770                 $this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) );
00771                 wfProfileOut( __METHOD__ . '-' . $this->name );
00772                 wfProfileOut( __METHOD__ );
00773                 return $hash;
00774         }
00775 
00780         protected function doGetFileSha1Base36( array $params ) {
00781                 $fsFile = $this->getLocalReference( $params );
00782                 if ( !$fsFile ) {
00783                         return false;
00784                 } else {
00785                         return $fsFile->getSha1Base36();
00786                 }
00787         }
00788 
00793         final public function getFileProps( array $params ) {
00794                 wfProfileIn( __METHOD__ );
00795                 wfProfileIn( __METHOD__ . '-' . $this->name );
00796                 $fsFile = $this->getLocalReference( $params );
00797                 $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
00798                 wfProfileOut( __METHOD__ . '-' . $this->name );
00799                 wfProfileOut( __METHOD__ );
00800                 return $props;
00801         }
00802 
00807         final public function getLocalReferenceMulti( array $params ) {
00808                 wfProfileIn( __METHOD__ );
00809                 wfProfileIn( __METHOD__ . '-' . $this->name );
00810 
00811                 $params = $this->setConcurrencyFlags( $params );
00812 
00813                 $fsFiles = array(); // (path => FSFile)
00814                 $latest = !empty( $params['latest'] ); // use latest data?
00815                 // Reuse any files already in process cache...
00816                 foreach ( $params['srcs'] as $src ) {
00817                         $path = self::normalizeStoragePath( $src );
00818                         if ( $path === null ) {
00819                                 $fsFiles[$src] = null; // invalid storage path
00820                         } elseif ( $this->expensiveCache->has( $path, 'localRef' ) ) {
00821                                 $val = $this->expensiveCache->get( $path, 'localRef' );
00822                                 // If we want the latest data, check that this cached
00823                                 // value was in fact fetched with the latest available data.
00824                                 if ( !$latest || $val['latest'] ) {
00825                                         $fsFiles[$src] = $val['object'];
00826                                 }
00827                         }
00828                 }
00829                 // Fetch local references of any remaning files...
00830                 $params['srcs'] = array_diff( $params['srcs'], array_keys( $fsFiles ) );
00831                 foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) {
00832                         $fsFiles[$path] = $fsFile;
00833                         if ( $fsFile ) { // update the process cache...
00834                                 $this->expensiveCache->set( $path, 'localRef',
00835                                         array( 'object' => $fsFile, 'latest' => $latest ) );
00836                         }
00837                 }
00838 
00839                 wfProfileOut( __METHOD__ . '-' . $this->name );
00840                 wfProfileOut( __METHOD__ );
00841                 return $fsFiles;
00842         }
00843 
00848         protected function doGetLocalReferenceMulti( array $params ) {
00849                 return $this->doGetLocalCopyMulti( $params );
00850         }
00851 
00856         final public function getLocalCopyMulti( array $params ) {
00857                 wfProfileIn( __METHOD__ );
00858                 wfProfileIn( __METHOD__ . '-' . $this->name );
00859 
00860                 $params = $this->setConcurrencyFlags( $params );
00861                 $tmpFiles = $this->doGetLocalCopyMulti( $params );
00862 
00863                 wfProfileOut( __METHOD__ . '-' . $this->name );
00864                 wfProfileOut( __METHOD__ );
00865                 return $tmpFiles;
00866         }
00867 
00872         abstract protected function doGetLocalCopyMulti( array $params );
00873 
00878         public function getFileHttpUrl( array $params ) {
00879                 return null; // not supported
00880         }
00881 
00886         final public function streamFile( array $params ) {
00887                 wfProfileIn( __METHOD__ );
00888                 wfProfileIn( __METHOD__ . '-' . $this->name );
00889                 $status = Status::newGood();
00890 
00891                 $info = $this->getFileStat( $params );
00892                 if ( !$info ) { // let StreamFile handle the 404
00893                         $status->fatal( 'backend-fail-notexists', $params['src'] );
00894                 }
00895 
00896                 // Set output buffer and HTTP headers for stream
00897                 $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array();
00898                 $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders );
00899                 if ( $res == StreamFile::NOT_MODIFIED ) {
00900                         // do nothing; client cache is up to date
00901                 } elseif ( $res == StreamFile::READY_STREAM ) {
00902                         wfProfileIn( __METHOD__ . '-send' );
00903                         wfProfileIn( __METHOD__ . '-send-' . $this->name );
00904                         $status = $this->doStreamFile( $params );
00905                         wfProfileOut( __METHOD__ . '-send-' . $this->name );
00906                         wfProfileOut( __METHOD__ . '-send' );
00907                         if ( !$status->isOK() ) {
00908                                 // Per bug 41113, nasty things can happen if bad cache entries get
00909                                 // stuck in cache. It's also possible that this error can come up
00910                                 // with simple race conditions. Clear out the stat cache to be safe.
00911                                 $this->clearCache( array( $params['src'] ) );
00912                                 $this->deleteFileCache( $params['src'] );
00913                                 trigger_error( "Bad stat cache or race condition for file {$params['src']}." );
00914                         }
00915                 } else {
00916                         $status->fatal( 'backend-fail-stream', $params['src'] );
00917                 }
00918 
00919                 wfProfileOut( __METHOD__ . '-' . $this->name );
00920                 wfProfileOut( __METHOD__ );
00921                 return $status;
00922         }
00923 
00928         protected function doStreamFile( array $params ) {
00929                 $status = Status::newGood();
00930 
00931                 $fsFile = $this->getLocalReference( $params );
00932                 if ( !$fsFile ) {
00933                         $status->fatal( 'backend-fail-stream', $params['src'] );
00934                 } elseif ( !readfile( $fsFile->getPath() ) ) {
00935                         $status->fatal( 'backend-fail-stream', $params['src'] );
00936                 }
00937 
00938                 return $status;
00939         }
00940 
00945         final public function directoryExists( array $params ) {
00946                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00947                 if ( $dir === null ) {
00948                         return false; // invalid storage path
00949                 }
00950                 if ( $shard !== null ) { // confined to a single container/shard
00951                         return $this->doDirectoryExists( $fullCont, $dir, $params );
00952                 } else { // directory is on several shards
00953                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00954                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
00955                         $res = false; // response
00956                         foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
00957                                 $exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params );
00958                                 if ( $exists ) {
00959                                         $res = true;
00960                                         break; // found one!
00961                                 } elseif ( $exists === null ) { // error?
00962                                         $res = null; // if we don't find anything, it is indeterminate
00963                                 }
00964                         }
00965                         return $res;
00966                 }
00967         }
00968 
00977         abstract protected function doDirectoryExists( $container, $dir, array $params );
00978 
00983         final public function getDirectoryList( array $params ) {
00984                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
00985                 if ( $dir === null ) { // invalid storage path
00986                         return null;
00987                 }
00988                 if ( $shard !== null ) {
00989                         // File listing is confined to a single container/shard
00990                         return $this->getDirectoryListInternal( $fullCont, $dir, $params );
00991                 } else {
00992                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
00993                         // File listing spans multiple containers/shards
00994                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
00995                         return new FileBackendStoreShardDirIterator( $this,
00996                                 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
00997                 }
00998         }
00999 
01010         abstract public function getDirectoryListInternal( $container, $dir, array $params );
01011 
01016         final public function getFileList( array $params ) {
01017                 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
01018                 if ( $dir === null ) { // invalid storage path
01019                         return null;
01020                 }
01021                 if ( $shard !== null ) {
01022                         // File listing is confined to a single container/shard
01023                         return $this->getFileListInternal( $fullCont, $dir, $params );
01024                 } else {
01025                         wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
01026                         // File listing spans multiple containers/shards
01027                         list( , $shortCont, ) = self::splitStoragePath( $params['dir'] );
01028                         return new FileBackendStoreShardFileIterator( $this,
01029                                 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
01030                 }
01031         }
01032 
01043         abstract public function getFileListInternal( $container, $dir, array $params );
01044 
01056         final public function getOperationsInternal( array $ops ) {
01057                 $supportedOps = array(
01058                         'store'    => 'StoreFileOp',
01059                         'copy'     => 'CopyFileOp',
01060                         'move'     => 'MoveFileOp',
01061                         'delete'   => 'DeleteFileOp',
01062                         'create'   => 'CreateFileOp',
01063                         'describe' => 'DescribeFileOp',
01064                         'null'     => 'NullFileOp'
01065                 );
01066 
01067                 $performOps = array(); // array of FileOp objects
01068                 // Build up ordered array of FileOps...
01069                 foreach ( $ops as $operation ) {
01070                         $opName = $operation['op'];
01071                         if ( isset( $supportedOps[$opName] ) ) {
01072                                 $class = $supportedOps[$opName];
01073                                 // Get params for this operation
01074                                 $params = $operation;
01075                                 // Append the FileOp class
01076                                 $performOps[] = new $class( $this, $params );
01077                         } else {
01078                                 throw new MWException( "Operation '$opName' is not supported." );
01079                         }
01080                 }
01081 
01082                 return $performOps;
01083         }
01084 
01094         final public function getPathsToLockForOpsInternal( array $performOps ) {
01095                 // Build up a list of files to lock...
01096                 $paths = array( 'sh' => array(), 'ex' => array() );
01097                 foreach ( $performOps as $fileOp ) {
01098                         $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() );
01099                         $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() );
01100                 }
01101                 // Optimization: if doing an EX lock anyway, don't also set an SH one
01102                 $paths['sh'] = array_diff( $paths['sh'], $paths['ex'] );
01103                 // Get a shared lock on the parent directory of each path changed
01104                 $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
01105 
01106                 return $paths;
01107         }
01108 
01113         public function getScopedLocksForOps( array $ops, Status $status ) {
01114                 $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
01115                 return array(
01116                         $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
01117                         $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
01118                 );
01119         }
01120 
01125         final protected function doOperationsInternal( array $ops, array $opts ) {
01126                 wfProfileIn( __METHOD__ );
01127                 wfProfileIn( __METHOD__ . '-' . $this->name );
01128                 $status = Status::newGood();
01129 
01130                 // Fix up custom header name/value pairs...
01131                 $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops );
01132 
01133                 // Build up a list of FileOps...
01134                 $performOps = $this->getOperationsInternal( $ops );
01135 
01136                 // Acquire any locks as needed...
01137                 if ( empty( $opts['nonLocking'] ) ) {
01138                         // Build up a list of files to lock...
01139                         $paths = $this->getPathsToLockForOpsInternal( $performOps );
01140                         // Try to lock those files for the scope of this function...
01141                         $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
01142                         $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
01143                         if ( !$status->isOK() ) {
01144                                 wfProfileOut( __METHOD__ . '-' . $this->name );
01145                                 wfProfileOut( __METHOD__ );
01146                                 return $status; // abort
01147                         }
01148                 }
01149 
01150                 // Clear any file cache entries (after locks acquired)
01151                 if ( empty( $opts['preserveCache'] ) ) {
01152                         $this->clearCache();
01153                 }
01154 
01155                 // Load from the persistent file and container caches
01156                 $this->primeFileCache( $performOps );
01157                 $this->primeContainerCache( $performOps );
01158 
01159                 // Actually attempt the operation batch...
01160                 $opts = $this->setConcurrencyFlags( $opts );
01161                 $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
01162 
01163                 // Merge errors into status fields
01164                 $status->merge( $subStatus );
01165                 $status->success = $subStatus->success; // not done in merge()
01166 
01167                 wfProfileOut( __METHOD__ . '-' . $this->name );
01168                 wfProfileOut( __METHOD__ );
01169                 return $status;
01170         }
01171 
01177         final protected function doQuickOperationsInternal( array $ops ) {
01178                 wfProfileIn( __METHOD__ );
01179                 wfProfileIn( __METHOD__ . '-' . $this->name );
01180                 $status = Status::newGood();
01181 
01182                 // Fix up custom header name/value pairs...
01183                 $ops = array_map( array( $this, 'stripInvalidHeadersFromOp' ), $ops );
01184 
01185                 // Clear any file cache entries
01186                 $this->clearCache();
01187 
01188                 $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
01189                 $async = ( $this->parallelize === 'implicit' );
01190                 $maxConcurrency = $this->concurrency; // throttle
01191 
01192                 $statuses = array(); // array of (index => Status)
01193                 $fileOpHandles = array(); // list of (index => handle) arrays
01194                 $curFileOpHandles = array(); // current handle batch
01195                 // Perform the sync-only ops and build up op handles for the async ops...
01196                 foreach ( $ops as $index => $params ) {
01197                         if ( !in_array( $params['op'], $supportedOps ) ) {
01198                                 wfProfileOut( __METHOD__ . '-' . $this->name );
01199                                 wfProfileOut( __METHOD__ );
01200                                 throw new MWException( "Operation '{$params['op']}' is not supported." );
01201                         }
01202                         $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
01203                         $subStatus = $this->$method( array( 'async' => $async ) + $params );
01204                         if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async
01205                                 if ( count( $curFileOpHandles ) >= $maxConcurrency ) {
01206                                         $fileOpHandles[] = $curFileOpHandles; // push this batch
01207                                         $curFileOpHandles = array();
01208                                 }
01209                                 $curFileOpHandles[$index] = $subStatus->value; // keep index
01210                         } else { // error or completed
01211                                 $statuses[$index] = $subStatus; // keep index
01212                         }
01213                 }
01214                 if ( count( $curFileOpHandles ) ) {
01215                         $fileOpHandles[] = $curFileOpHandles; // last batch
01216                 }
01217                 // Do all the async ops that can be done concurrently...
01218                 foreach ( $fileOpHandles as $fileHandleBatch ) {
01219                         $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch );
01220                 }
01221                 // Marshall and merge all the responses...
01222                 foreach ( $statuses as $index => $subStatus ) {
01223                         $status->merge( $subStatus );
01224                         if ( $subStatus->isOK() ) {
01225                                 $status->success[$index] = true;
01226                                 ++$status->successCount;
01227                         } else {
01228                                 $status->success[$index] = false;
01229                                 ++$status->failCount;
01230                         }
01231                 }
01232 
01233                 wfProfileOut( __METHOD__ . '-' . $this->name );
01234                 wfProfileOut( __METHOD__ );
01235                 return $status;
01236         }
01237 
01247         final public function executeOpHandlesInternal( array $fileOpHandles ) {
01248                 wfProfileIn( __METHOD__ );
01249                 wfProfileIn( __METHOD__ . '-' . $this->name );
01250                 foreach ( $fileOpHandles as $fileOpHandle ) {
01251                         if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
01252                                 throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
01253                         } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
01254                                 throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." );
01255                         }
01256                 }
01257                 $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
01258                 foreach ( $fileOpHandles as $fileOpHandle ) {
01259                         $fileOpHandle->closeResources();
01260                 }
01261                 wfProfileOut( __METHOD__ . '-' . $this->name );
01262                 wfProfileOut( __METHOD__ );
01263                 return $res;
01264         }
01265 
01272         protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
01273                 foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
01274                         throw new MWException( "This backend supports no asynchronous operations." );
01275                 }
01276                 return array();
01277         }
01278 
01285         protected function stripInvalidHeadersFromOp( array $op ) {
01286                 if ( isset( $op['headers'] ) ) {
01287                         foreach ( $op['headers'] as $name => $value ) {
01288                                 if ( strlen( $name ) > 255 || strlen( $value ) > 255 ) {
01289                                         trigger_error( "Header '$name: $value' is too long." );
01290                                         unset( $op['headers'][$name] );
01291                                 } elseif ( !strlen( $value ) ) {
01292                                         $op['headers'][$name] = ''; // null/false => ""
01293                                 }
01294                         }
01295                 }
01296                 return $op;
01297         }
01298 
01302         final public function preloadCache( array $paths ) {
01303                 $fullConts = array(); // full container names
01304                 foreach ( $paths as $path ) {
01305                         list( $fullCont, , ) = $this->resolveStoragePath( $path );
01306                         $fullConts[] = $fullCont;
01307                 }
01308                 // Load from the persistent file and container caches
01309                 $this->primeContainerCache( $fullConts );
01310                 $this->primeFileCache( $paths );
01311         }
01312 
01316         final public function clearCache( array $paths = null ) {
01317                 if ( is_array( $paths ) ) {
01318                         $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
01319                         $paths = array_filter( $paths, 'strlen' ); // remove nulls
01320                 }
01321                 if ( $paths === null ) {
01322                         $this->cheapCache->clear();
01323                         $this->expensiveCache->clear();
01324                 } else {
01325                         foreach ( $paths as $path ) {
01326                                 $this->cheapCache->clear( $path );
01327                                 $this->expensiveCache->clear( $path );
01328                         }
01329                 }
01330                 $this->doClearCache( $paths );
01331         }
01332 
01341         protected function doClearCache( array $paths = null ) {}
01342 
01350         abstract protected function directoriesAreVirtual();
01351 
01359         final protected static function isValidContainerName( $container ) {
01360                 // This accounts for Swift and S3 restrictions while leaving room
01361                 // for things like '.xxx' (hex shard chars) or '.seg' (segments).
01362                 // This disallows directory separators or traversal characters.
01363                 // Note that matching strings URL encode to the same string;
01364                 // in Swift, the length restriction is *after* URL encoding.
01365                 return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container );
01366         }
01367 
01381         final protected function resolveStoragePath( $storagePath ) {
01382                 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
01383                 if ( $backend === $this->name ) { // must be for this backend
01384                         $relPath = self::normalizeContainerPath( $relPath );
01385                         if ( $relPath !== null ) {
01386                                 // Get shard for the normalized path if this container is sharded
01387                                 $cShard = $this->getContainerShard( $container, $relPath );
01388                                 // Validate and sanitize the relative path (backend-specific)
01389                                 $relPath = $this->resolveContainerPath( $container, $relPath );
01390                                 if ( $relPath !== null ) {
01391                                         // Prepend any wiki ID prefix to the container name
01392                                         $container = $this->fullContainerName( $container );
01393                                         if ( self::isValidContainerName( $container ) ) {
01394                                                 // Validate and sanitize the container name (backend-specific)
01395                                                 $container = $this->resolveContainerName( "{$container}{$cShard}" );
01396                                                 if ( $container !== null ) {
01397                                                         return array( $container, $relPath, $cShard );
01398                                                 }
01399                                         }
01400                                 }
01401                         }
01402                 }
01403                 return array( null, null, null );
01404         }
01405 
01415         final protected function resolveStoragePathReal( $storagePath ) {
01416                 list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
01417                 if ( $cShard !== null ) {
01418                         return array( $container, $relPath );
01419                 }
01420                 return array( null, null );
01421         }
01422 
01431         final protected function getContainerShard( $container, $relPath ) {
01432                 list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container );
01433                 if ( $levels == 1 || $levels == 2 ) {
01434                         // Hash characters are either base 16 or 36
01435                         $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]';
01436                         // Get a regex that represents the shard portion of paths.
01437                         // The concatenation of the captures gives us the shard.
01438                         if ( $levels === 1 ) { // 16 or 36 shards per container
01439                                 $hashDirRegex = '(' . $char . ')';
01440                         } else { // 256 or 1296 shards per container
01441                                 if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc")
01442                                         $hashDirRegex = $char . '/(' . $char . '{2})';
01443                                 } else { // short hash dir format (e.g. "a/b/c")
01444                                         $hashDirRegex = '(' . $char . ')/(' . $char . ')';
01445                                 }
01446                         }
01447                         // Allow certain directories to be above the hash dirs so as
01448                         // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab").
01449                         // They must be 2+ chars to avoid any hash directory ambiguity.
01450                         $m = array();
01451                         if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
01452                                 return '.' . implode( '', array_slice( $m, 1 ) );
01453                         }
01454                         return null; // failed to match
01455                 }
01456                 return ''; // no sharding
01457         }
01458 
01467         final public function isSingleShardPathInternal( $storagePath ) {
01468                 list( , , $shard ) = $this->resolveStoragePath( $storagePath );
01469                 return ( $shard !== null );
01470         }
01471 
01480         final protected function getContainerHashLevels( $container ) {
01481                 if ( isset( $this->shardViaHashLevels[$container] ) ) {
01482                         $config = $this->shardViaHashLevels[$container];
01483                         $hashLevels = (int)$config['levels'];
01484                         if ( $hashLevels == 1 || $hashLevels == 2 ) {
01485                                 $hashBase = (int)$config['base'];
01486                                 if ( $hashBase == 16 || $hashBase == 36 ) {
01487                                         return array( $hashLevels, $hashBase, $config['repeat'] );
01488                                 }
01489                         }
01490                 }
01491                 return array( 0, 0, false ); // no sharding
01492         }
01493 
01500         final protected function getContainerSuffixes( $container ) {
01501                 $shards = array();
01502                 list( $digits, $base ) = $this->getContainerHashLevels( $container );
01503                 if ( $digits > 0 ) {
01504                         $numShards = pow( $base, $digits );
01505                         for ( $index = 0; $index < $numShards; $index++ ) {
01506                                 $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits );
01507                         }
01508                 }
01509                 return $shards;
01510         }
01511 
01518         final protected function fullContainerName( $container ) {
01519                 if ( $this->wikiId != '' ) {
01520                         return "{$this->wikiId}-$container";
01521                 } else {
01522                         return $container;
01523                 }
01524         }
01525 
01534         protected function resolveContainerName( $container ) {
01535                 return $container;
01536         }
01537 
01548         protected function resolveContainerPath( $container, $relStoragePath ) {
01549                 return $relStoragePath;
01550         }
01551 
01558         private function containerCacheKey( $container ) {
01559                 return wfMemcKey( 'backend', $this->getName(), 'container', $container );
01560         }
01561 
01568         final protected function setContainerCache( $container, $val ) {
01569                 $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 );
01570         }
01571 
01578         final protected function deleteContainerCache( $container ) {
01579                 if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) {
01580                         trigger_error( "Unable to delete stat cache for container $container." );
01581                 }
01582         }
01583 
01592         final protected function primeContainerCache( array $items ) {
01593                 wfProfileIn( __METHOD__ );
01594                 wfProfileIn( __METHOD__ . '-' . $this->name );
01595 
01596                 $paths = array(); // list of storage paths
01597                 $contNames = array(); // (cache key => resolved container name)
01598                 // Get all the paths/containers from the items...
01599                 foreach ( $items as $item ) {
01600                         if ( $item instanceof FileOp ) {
01601                                 $paths = array_merge( $paths, $item->storagePathsRead() );
01602                                 $paths = array_merge( $paths, $item->storagePathsChanged() );
01603                         } elseif ( self::isStoragePath( $item ) ) {
01604                                 $paths[] = $item;
01605                         } elseif ( is_string( $item ) ) { // full container name
01606                                 $contNames[$this->containerCacheKey( $item )] = $item;
01607                         }
01608                 }
01609                 // Get all the corresponding cache keys for paths...
01610                 foreach ( $paths as $path ) {
01611                         list( $fullCont, , ) = $this->resolveStoragePath( $path );
01612                         if ( $fullCont !== null ) { // valid path for this backend
01613                                 $contNames[$this->containerCacheKey( $fullCont )] = $fullCont;
01614                         }
01615                 }
01616 
01617                 $contInfo = array(); // (resolved container name => cache value)
01618                 // Get all cache entries for these container cache keys...
01619                 $values = $this->memCache->getMulti( array_keys( $contNames ) );
01620                 foreach ( $values as $cacheKey => $val ) {
01621                         $contInfo[$contNames[$cacheKey]] = $val;
01622                 }
01623 
01624                 // Populate the container process cache for the backend...
01625                 $this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
01626 
01627                 wfProfileOut( __METHOD__ . '-' . $this->name );
01628                 wfProfileOut( __METHOD__ );
01629         }
01630 
01639         protected function doPrimeContainerCache( array $containerInfo ) {}
01640 
01647         private function fileCacheKey( $path ) {
01648                 return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) );
01649         }
01650 
01659         final protected function setFileCache( $path, $val ) {
01660                 $path = FileBackend::normalizeStoragePath( $path );
01661                 if ( $path === null ) {
01662                         return; // invalid storage path
01663                 }
01664                 $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 );
01665         }
01666 
01675         final protected function deleteFileCache( $path ) {
01676                 $path = FileBackend::normalizeStoragePath( $path );
01677                 if ( $path === null ) {
01678                         return; // invalid storage path
01679                 }
01680                 if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) {
01681                         trigger_error( "Unable to delete stat cache for file $path." );
01682                 }
01683         }
01684 
01693         final protected function primeFileCache( array $items ) {
01694                 wfProfileIn( __METHOD__ );
01695                 wfProfileIn( __METHOD__ . '-' . $this->name );
01696 
01697                 $paths = array(); // list of storage paths
01698                 $pathNames = array(); // (cache key => storage path)
01699                 // Get all the paths/containers from the items...
01700                 foreach ( $items as $item ) {
01701                         if ( $item instanceof FileOp ) {
01702                                 $paths = array_merge( $paths, $item->storagePathsRead() );
01703                                 $paths = array_merge( $paths, $item->storagePathsChanged() );
01704                         } elseif ( self::isStoragePath( $item ) ) {
01705                                 $paths[] = FileBackend::normalizeStoragePath( $item );
01706                         }
01707                 }
01708                 // Get rid of any paths that failed normalization...
01709                 $paths = array_filter( $paths, 'strlen' ); // remove nulls
01710                 // Get all the corresponding cache keys for paths...
01711                 foreach ( $paths as $path ) {
01712                         list( , $rel, ) = $this->resolveStoragePath( $path );
01713                         if ( $rel !== null ) { // valid path for this backend
01714                                 $pathNames[$this->fileCacheKey( $path )] = $path;
01715                         }
01716                 }
01717                 // Get all cache entries for these container cache keys...
01718                 $values = $this->memCache->getMulti( array_keys( $pathNames ) );
01719                 foreach ( $values as $cacheKey => $val ) {
01720                         if ( is_array( $val ) ) {
01721                                 $path = $pathNames[$cacheKey];
01722                                 $this->cheapCache->set( $path, 'stat', $val );
01723                                 if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
01724                                         $this->cheapCache->set( $path, 'sha1',
01725                                                 array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) );
01726                                 }
01727                         }
01728                 }
01729 
01730                 wfProfileOut( __METHOD__ . '-' . $this->name );
01731                 wfProfileOut( __METHOD__ );
01732         }
01733 
01740         final protected function setConcurrencyFlags( array $opts ) {
01741                 $opts['concurrency'] = 1; // off
01742                 if ( $this->parallelize === 'implicit' ) {
01743                         if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
01744                                 $opts['concurrency'] = $this->concurrency;
01745                         }
01746                 } elseif ( $this->parallelize === 'explicit' ) {
01747                         if ( !empty( $opts['parallelize'] ) ) {
01748                                 $opts['concurrency'] = $this->concurrency;
01749                         }
01750                 }
01751                 return $opts;
01752         }
01753 }
01754 
01763 abstract class FileBackendStoreOpHandle {
01765         public $params = array(); // params to caller functions
01767         public $backend;
01769         public $resourcesToClose = array();
01770 
01771         public $call; // string; name that identifies the function called
01772 
01778         public function closeResources() {
01779                 array_map( 'fclose', $this->resourcesToClose );
01780         }
01781 }
01782 
01789 abstract class FileBackendStoreShardListIterator implements Iterator {
01791         protected $backend;
01793         protected $params;
01795         protected $shardSuffixes;
01796         protected $container; // string; full container name
01797         protected $directory; // string; resolved relative path
01798 
01800         protected $iter;
01801         protected $curShard = 0; // integer
01802         protected $pos = 0; // integer
01803 
01805         protected $multiShardPaths = array(); // (rel path => 1)
01806 
01814         public function __construct(
01815                 FileBackendStore $backend, $container, $dir, array $suffixes, array $params
01816         ) {
01817                 $this->backend = $backend;
01818                 $this->container = $container;
01819                 $this->directory = $dir;
01820                 $this->shardSuffixes = $suffixes;
01821                 $this->params = $params;
01822         }
01823 
01828         public function key() {
01829                 return $this->pos;
01830         }
01831 
01836         public function valid() {
01837                 if ( $this->iter instanceof Iterator ) {
01838                         return $this->iter->valid();
01839                 } elseif ( is_array( $this->iter ) ) {
01840                         return ( current( $this->iter ) !== false ); // no paths can have this value
01841                 }
01842                 return false; // some failure?
01843         }
01844 
01849         public function current() {
01850                 return ( $this->iter instanceof Iterator )
01851                         ? $this->iter->current()
01852                         : current( $this->iter );
01853         }
01854 
01859         public function next() {
01860                 ++$this->pos;
01861                 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
01862                 do {
01863                         $continue = false; // keep scanning shards?
01864                         $this->filterViaNext(); // filter out duplicates
01865                         // Find the next non-empty shard if no elements are left
01866                         if ( !$this->valid() ) {
01867                                 $this->nextShardIteratorIfNotValid();
01868                                 $continue = $this->valid(); // re-filter unless we ran out of shards
01869                         }
01870                 } while ( $continue );
01871         }
01872 
01877         public function rewind() {
01878                 $this->pos = 0;
01879                 $this->curShard = 0;
01880                 $this->setIteratorFromCurrentShard();
01881                 do {
01882                         $continue = false; // keep scanning shards?
01883                         $this->filterViaNext(); // filter out duplicates
01884                         // Find the next non-empty shard if no elements are left
01885                         if ( !$this->valid() ) {
01886                                 $this->nextShardIteratorIfNotValid();
01887                                 $continue = $this->valid(); // re-filter unless we ran out of shards
01888                         }
01889                 } while ( $continue );
01890         }
01891 
01895         protected function filterViaNext() {
01896                 while ( $this->valid() ) {
01897                         $rel = $this->iter->current(); // path relative to given directory
01898                         $path = $this->params['dir'] . "/{$rel}"; // full storage path
01899                         if ( $this->backend->isSingleShardPathInternal( $path ) ) {
01900                                 break; // path is only on one shard; no issue with duplicates
01901                         } elseif ( isset( $this->multiShardPaths[$rel] ) ) {
01902                                 // Don't keep listing paths that are on multiple shards
01903                                 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter );
01904                         } else {
01905                                 $this->multiShardPaths[$rel] = 1;
01906                                 break;
01907                         }
01908                 }
01909         }
01910 
01916         protected function nextShardIteratorIfNotValid() {
01917                 while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) {
01918                         $this->setIteratorFromCurrentShard();
01919                 }
01920         }
01921 
01925         protected function setIteratorFromCurrentShard() {
01926                 $this->iter = $this->listFromShard(
01927                         $this->container . $this->shardSuffixes[$this->curShard],
01928                         $this->directory, $this->params );
01929                 // Start loading results so that current() works
01930                 if ( $this->iter ) {
01931                         ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter );
01932                 }
01933         }
01934 
01943         abstract protected function listFromShard( $container, $dir, array $params );
01944 }
01945 
01949 class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
01954         protected function listFromShard( $container, $dir, array $params ) {
01955                 return $this->backend->getDirectoryListInternal( $container, $dir, $params );
01956         }
01957 }
01958 
01962 class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
01967         protected function listFromShard( $container, $dir, array $params ) {
01968                 return $this->backend->getFileListInternal( $container, $dir, $params );
01969         }
01970 }