MediaWiki
master
|
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 }