MediaWiki
REL1_20
|
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 00082 abstract public function isPathUsableInternal( $storagePath ); 00083 00100 final public function createInternal( array $params ) { 00101 wfProfileIn( __METHOD__ ); 00102 wfProfileIn( __METHOD__ . '-' . $this->name ); 00103 if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) { 00104 $status = Status::newFatal( 'backend-fail-maxsize', 00105 $params['dst'], $this->maxFileSizeInternal() ); 00106 } else { 00107 $status = $this->doCreateInternal( $params ); 00108 $this->clearCache( array( $params['dst'] ) ); 00109 if ( !empty( $params['overwrite'] ) ) { // file possibly mutated 00110 $this->deleteFileCache( $params['dst'] ); // persistent cache 00111 } 00112 } 00113 wfProfileOut( __METHOD__ . '-' . $this->name ); 00114 wfProfileOut( __METHOD__ ); 00115 return $status; 00116 } 00117 00121 abstract protected function doCreateInternal( array $params ); 00122 00139 final public function storeInternal( array $params ) { 00140 wfProfileIn( __METHOD__ ); 00141 wfProfileIn( __METHOD__ . '-' . $this->name ); 00142 if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) { 00143 $status = Status::newFatal( 'backend-fail-maxsize', 00144 $params['dst'], $this->maxFileSizeInternal() ); 00145 } else { 00146 $status = $this->doStoreInternal( $params ); 00147 $this->clearCache( array( $params['dst'] ) ); 00148 if ( !empty( $params['overwrite'] ) ) { // file possibly mutated 00149 $this->deleteFileCache( $params['dst'] ); // persistent cache 00150 } 00151 } 00152 wfProfileOut( __METHOD__ . '-' . $this->name ); 00153 wfProfileOut( __METHOD__ ); 00154 return $status; 00155 } 00156 00160 abstract protected function doStoreInternal( array $params ); 00161 00178 final public function copyInternal( array $params ) { 00179 wfProfileIn( __METHOD__ ); 00180 wfProfileIn( __METHOD__ . '-' . $this->name ); 00181 $status = $this->doCopyInternal( $params ); 00182 $this->clearCache( array( $params['dst'] ) ); 00183 if ( !empty( $params['overwrite'] ) ) { // file possibly mutated 00184 $this->deleteFileCache( $params['dst'] ); // persistent cache 00185 } 00186 wfProfileOut( __METHOD__ . '-' . $this->name ); 00187 wfProfileOut( __METHOD__ ); 00188 return $status; 00189 } 00190 00194 abstract protected function doCopyInternal( array $params ); 00195 00210 final public function deleteInternal( array $params ) { 00211 wfProfileIn( __METHOD__ ); 00212 wfProfileIn( __METHOD__ . '-' . $this->name ); 00213 $status = $this->doDeleteInternal( $params ); 00214 $this->clearCache( array( $params['src'] ) ); 00215 $this->deleteFileCache( $params['src'] ); // persistent cache 00216 wfProfileOut( __METHOD__ . '-' . $this->name ); 00217 wfProfileOut( __METHOD__ ); 00218 return $status; 00219 } 00220 00224 abstract protected function doDeleteInternal( array $params ); 00225 00242 final public function moveInternal( array $params ) { 00243 wfProfileIn( __METHOD__ ); 00244 wfProfileIn( __METHOD__ . '-' . $this->name ); 00245 $status = $this->doMoveInternal( $params ); 00246 $this->clearCache( array( $params['src'], $params['dst'] ) ); 00247 $this->deleteFileCache( $params['src'] ); // persistent cache 00248 if ( !empty( $params['overwrite'] ) ) { // file possibly mutated 00249 $this->deleteFileCache( $params['dst'] ); // persistent cache 00250 } 00251 wfProfileOut( __METHOD__ . '-' . $this->name ); 00252 wfProfileOut( __METHOD__ ); 00253 return $status; 00254 } 00255 00260 protected function doMoveInternal( array $params ) { 00261 unset( $params['async'] ); // two steps, won't work here :) 00262 // Copy source to dest 00263 $status = $this->copyInternal( $params ); 00264 if ( $status->isOK() ) { 00265 // Delete source (only fails due to races or medium going down) 00266 $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) ); 00267 $status->setResult( true, $status->value ); // ignore delete() errors 00268 } 00269 return $status; 00270 } 00271 00279 final public function nullInternal( array $params ) { 00280 return Status::newGood(); 00281 } 00282 00287 final public function concatenate( array $params ) { 00288 wfProfileIn( __METHOD__ ); 00289 wfProfileIn( __METHOD__ . '-' . $this->name ); 00290 $status = Status::newGood(); 00291 00292 // Try to lock the source files for the scope of this function 00293 $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status ); 00294 if ( $status->isOK() ) { 00295 // Actually do the file concatenation... 00296 $start_time = microtime( true ); 00297 $status->merge( $this->doConcatenate( $params ) ); 00298 $sec = microtime( true ) - $start_time; 00299 if ( !$status->isOK() ) { 00300 wfDebugLog( 'FileOperation', get_class( $this ) . " failed to concatenate " . 00301 count( $params['srcs'] ) . " file(s) [$sec sec]" ); 00302 } 00303 } 00304 00305 wfProfileOut( __METHOD__ . '-' . $this->name ); 00306 wfProfileOut( __METHOD__ ); 00307 return $status; 00308 } 00309 00314 protected function doConcatenate( array $params ) { 00315 $status = Status::newGood(); 00316 $tmpPath = $params['dst']; // convenience 00317 00318 // Check that the specified temp file is valid... 00319 wfSuppressWarnings(); 00320 $ok = ( is_file( $tmpPath ) && !filesize( $tmpPath ) ); 00321 wfRestoreWarnings(); 00322 if ( !$ok ) { // not present or not empty 00323 $status->fatal( 'backend-fail-opentemp', $tmpPath ); 00324 return $status; 00325 } 00326 00327 // Build up the temp file using the source chunks (in order)... 00328 $tmpHandle = fopen( $tmpPath, 'ab' ); 00329 if ( $tmpHandle === false ) { 00330 $status->fatal( 'backend-fail-opentemp', $tmpPath ); 00331 return $status; 00332 } 00333 foreach ( $params['srcs'] as $virtualSource ) { 00334 // Get a local FS version of the chunk 00335 $tmpFile = $this->getLocalReference( array( 'src' => $virtualSource ) ); 00336 if ( !$tmpFile ) { 00337 $status->fatal( 'backend-fail-read', $virtualSource ); 00338 return $status; 00339 } 00340 // Get a handle to the local FS version 00341 $sourceHandle = fopen( $tmpFile->getPath(), 'r' ); 00342 if ( $sourceHandle === false ) { 00343 fclose( $tmpHandle ); 00344 $status->fatal( 'backend-fail-read', $virtualSource ); 00345 return $status; 00346 } 00347 // Append chunk to file (pass chunk size to avoid magic quotes) 00348 if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) { 00349 fclose( $sourceHandle ); 00350 fclose( $tmpHandle ); 00351 $status->fatal( 'backend-fail-writetemp', $tmpPath ); 00352 return $status; 00353 } 00354 fclose( $sourceHandle ); 00355 } 00356 if ( !fclose( $tmpHandle ) ) { 00357 $status->fatal( 'backend-fail-closetemp', $tmpPath ); 00358 return $status; 00359 } 00360 00361 clearstatcache(); // temp file changed 00362 00363 return $status; 00364 } 00365 00370 final protected function doPrepare( array $params ) { 00371 wfProfileIn( __METHOD__ ); 00372 wfProfileIn( __METHOD__ . '-' . $this->name ); 00373 00374 $status = Status::newGood(); 00375 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00376 if ( $dir === null ) { 00377 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00378 wfProfileOut( __METHOD__ . '-' . $this->name ); 00379 wfProfileOut( __METHOD__ ); 00380 return $status; // invalid storage path 00381 } 00382 00383 if ( $shard !== null ) { // confined to a single container/shard 00384 $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) ); 00385 } else { // directory is on several shards 00386 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00387 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00388 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00389 $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00390 } 00391 } 00392 00393 wfProfileOut( __METHOD__ . '-' . $this->name ); 00394 wfProfileOut( __METHOD__ ); 00395 return $status; 00396 } 00397 00402 protected function doPrepareInternal( $container, $dir, array $params ) { 00403 return Status::newGood(); 00404 } 00405 00410 final protected function doSecure( array $params ) { 00411 wfProfileIn( __METHOD__ ); 00412 wfProfileIn( __METHOD__ . '-' . $this->name ); 00413 $status = Status::newGood(); 00414 00415 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00416 if ( $dir === null ) { 00417 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00418 wfProfileOut( __METHOD__ . '-' . $this->name ); 00419 wfProfileOut( __METHOD__ ); 00420 return $status; // invalid storage path 00421 } 00422 00423 if ( $shard !== null ) { // confined to a single container/shard 00424 $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) ); 00425 } else { // directory is on several shards 00426 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00427 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00428 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00429 $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00430 } 00431 } 00432 00433 wfProfileOut( __METHOD__ . '-' . $this->name ); 00434 wfProfileOut( __METHOD__ ); 00435 return $status; 00436 } 00437 00442 protected function doSecureInternal( $container, $dir, array $params ) { 00443 return Status::newGood(); 00444 } 00445 00450 final protected function doPublish( array $params ) { 00451 wfProfileIn( __METHOD__ ); 00452 wfProfileIn( __METHOD__ . '-' . $this->name ); 00453 $status = Status::newGood(); 00454 00455 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00456 if ( $dir === null ) { 00457 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00458 wfProfileOut( __METHOD__ . '-' . $this->name ); 00459 wfProfileOut( __METHOD__ ); 00460 return $status; // invalid storage path 00461 } 00462 00463 if ( $shard !== null ) { // confined to a single container/shard 00464 $status->merge( $this->doPublishInternal( $fullCont, $dir, $params ) ); 00465 } else { // directory is on several shards 00466 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00467 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00468 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00469 $status->merge( $this->doPublishInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00470 } 00471 } 00472 00473 wfProfileOut( __METHOD__ . '-' . $this->name ); 00474 wfProfileOut( __METHOD__ ); 00475 return $status; 00476 } 00477 00482 protected function doPublishInternal( $container, $dir, array $params ) { 00483 return Status::newGood(); 00484 } 00485 00490 final protected function doClean( array $params ) { 00491 wfProfileIn( __METHOD__ ); 00492 wfProfileIn( __METHOD__ . '-' . $this->name ); 00493 $status = Status::newGood(); 00494 00495 // Recursive: first delete all empty subdirs recursively 00496 if ( !empty( $params['recursive'] ) && !$this->directoriesAreVirtual() ) { 00497 $subDirsRel = $this->getTopDirectoryList( array( 'dir' => $params['dir'] ) ); 00498 if ( $subDirsRel !== null ) { // no errors 00499 foreach ( $subDirsRel as $subDirRel ) { 00500 $subDir = $params['dir'] . "/{$subDirRel}"; // full path 00501 $status->merge( $this->doClean( array( 'dir' => $subDir ) + $params ) ); 00502 } 00503 } 00504 } 00505 00506 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00507 if ( $dir === null ) { 00508 $status->fatal( 'backend-fail-invalidpath', $params['dir'] ); 00509 wfProfileOut( __METHOD__ . '-' . $this->name ); 00510 wfProfileOut( __METHOD__ ); 00511 return $status; // invalid storage path 00512 } 00513 00514 // Attempt to lock this directory... 00515 $filesLockEx = array( $params['dir'] ); 00516 $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status ); 00517 if ( !$status->isOK() ) { 00518 wfProfileOut( __METHOD__ . '-' . $this->name ); 00519 wfProfileOut( __METHOD__ ); 00520 return $status; // abort 00521 } 00522 00523 if ( $shard !== null ) { // confined to a single container/shard 00524 $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) ); 00525 $this->deleteContainerCache( $fullCont ); // purge cache 00526 } else { // directory is on several shards 00527 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00528 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00529 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00530 $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) ); 00531 $this->deleteContainerCache( "{$fullCont}{$suffix}" ); // purge cache 00532 } 00533 } 00534 00535 wfProfileOut( __METHOD__ . '-' . $this->name ); 00536 wfProfileOut( __METHOD__ ); 00537 return $status; 00538 } 00539 00544 protected function doCleanInternal( $container, $dir, array $params ) { 00545 return Status::newGood(); 00546 } 00547 00552 final public function fileExists( array $params ) { 00553 wfProfileIn( __METHOD__ ); 00554 wfProfileIn( __METHOD__ . '-' . $this->name ); 00555 $stat = $this->getFileStat( $params ); 00556 wfProfileOut( __METHOD__ . '-' . $this->name ); 00557 wfProfileOut( __METHOD__ ); 00558 return ( $stat === null ) ? null : (bool)$stat; // null => failure 00559 } 00560 00565 final public function getFileTimestamp( array $params ) { 00566 wfProfileIn( __METHOD__ ); 00567 wfProfileIn( __METHOD__ . '-' . $this->name ); 00568 $stat = $this->getFileStat( $params ); 00569 wfProfileOut( __METHOD__ . '-' . $this->name ); 00570 wfProfileOut( __METHOD__ ); 00571 return $stat ? $stat['mtime'] : false; 00572 } 00573 00578 final public function getFileSize( array $params ) { 00579 wfProfileIn( __METHOD__ ); 00580 wfProfileIn( __METHOD__ . '-' . $this->name ); 00581 $stat = $this->getFileStat( $params ); 00582 wfProfileOut( __METHOD__ . '-' . $this->name ); 00583 wfProfileOut( __METHOD__ ); 00584 return $stat ? $stat['size'] : false; 00585 } 00586 00591 final public function getFileStat( array $params ) { 00592 $path = self::normalizeStoragePath( $params['src'] ); 00593 if ( $path === null ) { 00594 return false; // invalid storage path 00595 } 00596 wfProfileIn( __METHOD__ ); 00597 wfProfileIn( __METHOD__ . '-' . $this->name ); 00598 $latest = !empty( $params['latest'] ); // use latest data? 00599 if ( !$this->cheapCache->has( $path, 'stat' ) ) { 00600 $this->primeFileCache( array( $path ) ); // check persistent cache 00601 } 00602 if ( $this->cheapCache->has( $path, 'stat' ) ) { 00603 $stat = $this->cheapCache->get( $path, 'stat' ); 00604 // If we want the latest data, check that this cached 00605 // value was in fact fetched with the latest available data. 00606 if ( !$latest || $stat['latest'] ) { 00607 wfProfileOut( __METHOD__ . '-' . $this->name ); 00608 wfProfileOut( __METHOD__ ); 00609 return $stat; 00610 } 00611 } 00612 wfProfileIn( __METHOD__ . '-miss' ); 00613 wfProfileIn( __METHOD__ . '-miss-' . $this->name ); 00614 $stat = $this->doGetFileStat( $params ); 00615 wfProfileOut( __METHOD__ . '-miss-' . $this->name ); 00616 wfProfileOut( __METHOD__ . '-miss' ); 00617 if ( is_array( $stat ) ) { // don't cache negatives 00618 $stat['latest'] = $latest; 00619 $this->cheapCache->set( $path, 'stat', $stat ); 00620 $this->setFileCache( $path, $stat ); // update persistent cache 00621 if ( isset( $stat['sha1'] ) ) { // some backends store SHA-1 as metadata 00622 $this->cheapCache->set( $path, 'sha1', 00623 array( 'hash' => $stat['sha1'], 'latest' => $latest ) ); 00624 } 00625 } else { 00626 wfDebug( __METHOD__ . ": File $path does not exist.\n" ); 00627 } 00628 wfProfileOut( __METHOD__ . '-' . $this->name ); 00629 wfProfileOut( __METHOD__ ); 00630 return $stat; 00631 } 00632 00636 abstract protected function doGetFileStat( array $params ); 00637 00642 public function getFileContents( array $params ) { 00643 wfProfileIn( __METHOD__ ); 00644 wfProfileIn( __METHOD__ . '-' . $this->name ); 00645 $tmpFile = $this->getLocalReference( $params ); 00646 if ( !$tmpFile ) { 00647 wfProfileOut( __METHOD__ . '-' . $this->name ); 00648 wfProfileOut( __METHOD__ ); 00649 return false; 00650 } 00651 wfSuppressWarnings(); 00652 $data = file_get_contents( $tmpFile->getPath() ); 00653 wfRestoreWarnings(); 00654 wfProfileOut( __METHOD__ . '-' . $this->name ); 00655 wfProfileOut( __METHOD__ ); 00656 return $data; 00657 } 00658 00663 final public function getFileSha1Base36( array $params ) { 00664 $path = self::normalizeStoragePath( $params['src'] ); 00665 if ( $path === null ) { 00666 return false; // invalid storage path 00667 } 00668 wfProfileIn( __METHOD__ ); 00669 wfProfileIn( __METHOD__ . '-' . $this->name ); 00670 $latest = !empty( $params['latest'] ); // use latest data? 00671 if ( $this->cheapCache->has( $path, 'sha1' ) ) { 00672 $stat = $this->cheapCache->get( $path, 'sha1' ); 00673 // If we want the latest data, check that this cached 00674 // value was in fact fetched with the latest available data. 00675 if ( !$latest || $stat['latest'] ) { 00676 wfProfileOut( __METHOD__ . '-' . $this->name ); 00677 wfProfileOut( __METHOD__ ); 00678 return $stat['hash']; 00679 } 00680 } 00681 wfProfileIn( __METHOD__ . '-miss' ); 00682 wfProfileIn( __METHOD__ . '-miss-' . $this->name ); 00683 $hash = $this->doGetFileSha1Base36( $params ); 00684 wfProfileOut( __METHOD__ . '-miss-' . $this->name ); 00685 wfProfileOut( __METHOD__ . '-miss' ); 00686 if ( $hash ) { // don't cache negatives 00687 $this->cheapCache->set( $path, 'sha1', 00688 array( 'hash' => $hash, 'latest' => $latest ) ); 00689 } 00690 wfProfileOut( __METHOD__ . '-' . $this->name ); 00691 wfProfileOut( __METHOD__ ); 00692 return $hash; 00693 } 00694 00699 protected function doGetFileSha1Base36( array $params ) { 00700 $fsFile = $this->getLocalReference( $params ); 00701 if ( !$fsFile ) { 00702 return false; 00703 } else { 00704 return $fsFile->getSha1Base36(); 00705 } 00706 } 00707 00712 final public function getFileProps( array $params ) { 00713 wfProfileIn( __METHOD__ ); 00714 wfProfileIn( __METHOD__ . '-' . $this->name ); 00715 $fsFile = $this->getLocalReference( $params ); 00716 $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps(); 00717 wfProfileOut( __METHOD__ . '-' . $this->name ); 00718 wfProfileOut( __METHOD__ ); 00719 return $props; 00720 } 00721 00726 public function getLocalReference( array $params ) { 00727 $path = self::normalizeStoragePath( $params['src'] ); 00728 if ( $path === null ) { 00729 return null; // invalid storage path 00730 } 00731 wfProfileIn( __METHOD__ ); 00732 wfProfileIn( __METHOD__ . '-' . $this->name ); 00733 $latest = !empty( $params['latest'] ); // use latest data? 00734 if ( $this->expensiveCache->has( $path, 'localRef' ) ) { 00735 $val = $this->expensiveCache->get( $path, 'localRef' ); 00736 // If we want the latest data, check that this cached 00737 // value was in fact fetched with the latest available data. 00738 if ( !$latest || $val['latest'] ) { 00739 wfProfileOut( __METHOD__ . '-' . $this->name ); 00740 wfProfileOut( __METHOD__ ); 00741 return $val['object']; 00742 } 00743 } 00744 $tmpFile = $this->getLocalCopy( $params ); 00745 if ( $tmpFile ) { // don't cache negatives 00746 $this->expensiveCache->set( $path, 'localRef', 00747 array( 'object' => $tmpFile, 'latest' => $latest ) ); 00748 } 00749 wfProfileOut( __METHOD__ . '-' . $this->name ); 00750 wfProfileOut( __METHOD__ ); 00751 return $tmpFile; 00752 } 00753 00758 final public function streamFile( array $params ) { 00759 wfProfileIn( __METHOD__ ); 00760 wfProfileIn( __METHOD__ . '-' . $this->name ); 00761 $status = Status::newGood(); 00762 00763 $info = $this->getFileStat( $params ); 00764 if ( !$info ) { // let StreamFile handle the 404 00765 $status->fatal( 'backend-fail-notexists', $params['src'] ); 00766 } 00767 00768 // Set output buffer and HTTP headers for stream 00769 $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array(); 00770 $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders ); 00771 if ( $res == StreamFile::NOT_MODIFIED ) { 00772 // do nothing; client cache is up to date 00773 } elseif ( $res == StreamFile::READY_STREAM ) { 00774 wfProfileIn( __METHOD__ . '-send' ); 00775 wfProfileIn( __METHOD__ . '-send-' . $this->name ); 00776 $status = $this->doStreamFile( $params ); 00777 wfProfileOut( __METHOD__ . '-send-' . $this->name ); 00778 wfProfileOut( __METHOD__ . '-send' ); 00779 } else { 00780 $status->fatal( 'backend-fail-stream', $params['src'] ); 00781 } 00782 00783 wfProfileOut( __METHOD__ . '-' . $this->name ); 00784 wfProfileOut( __METHOD__ ); 00785 return $status; 00786 } 00787 00792 protected function doStreamFile( array $params ) { 00793 $status = Status::newGood(); 00794 00795 $fsFile = $this->getLocalReference( $params ); 00796 if ( !$fsFile ) { 00797 $status->fatal( 'backend-fail-stream', $params['src'] ); 00798 } elseif ( !readfile( $fsFile->getPath() ) ) { 00799 $status->fatal( 'backend-fail-stream', $params['src'] ); 00800 } 00801 00802 return $status; 00803 } 00804 00809 final public function directoryExists( array $params ) { 00810 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00811 if ( $dir === null ) { 00812 return false; // invalid storage path 00813 } 00814 if ( $shard !== null ) { // confined to a single container/shard 00815 return $this->doDirectoryExists( $fullCont, $dir, $params ); 00816 } else { // directory is on several shards 00817 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00818 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00819 $res = false; // response 00820 foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) { 00821 $exists = $this->doDirectoryExists( "{$fullCont}{$suffix}", $dir, $params ); 00822 if ( $exists ) { 00823 $res = true; 00824 break; // found one! 00825 } elseif ( $exists === null ) { // error? 00826 $res = null; // if we don't find anything, it is indeterminate 00827 } 00828 } 00829 return $res; 00830 } 00831 } 00832 00841 abstract protected function doDirectoryExists( $container, $dir, array $params ); 00842 00847 final public function getDirectoryList( array $params ) { 00848 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00849 if ( $dir === null ) { // invalid storage path 00850 return null; 00851 } 00852 if ( $shard !== null ) { 00853 // File listing is confined to a single container/shard 00854 return $this->getDirectoryListInternal( $fullCont, $dir, $params ); 00855 } else { 00856 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00857 // File listing spans multiple containers/shards 00858 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00859 return new FileBackendStoreShardDirIterator( $this, 00860 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params ); 00861 } 00862 } 00863 00874 abstract public function getDirectoryListInternal( $container, $dir, array $params ); 00875 00880 final public function getFileList( array $params ) { 00881 list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] ); 00882 if ( $dir === null ) { // invalid storage path 00883 return null; 00884 } 00885 if ( $shard !== null ) { 00886 // File listing is confined to a single container/shard 00887 return $this->getFileListInternal( $fullCont, $dir, $params ); 00888 } else { 00889 wfDebug( __METHOD__ . ": iterating over all container shards.\n" ); 00890 // File listing spans multiple containers/shards 00891 list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] ); 00892 return new FileBackendStoreShardFileIterator( $this, 00893 $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params ); 00894 } 00895 } 00896 00907 abstract public function getFileListInternal( $container, $dir, array $params ); 00908 00920 final public function getOperationsInternal( array $ops ) { 00921 $supportedOps = array( 00922 'store' => 'StoreFileOp', 00923 'copy' => 'CopyFileOp', 00924 'move' => 'MoveFileOp', 00925 'delete' => 'DeleteFileOp', 00926 'create' => 'CreateFileOp', 00927 'null' => 'NullFileOp' 00928 ); 00929 00930 $performOps = array(); // array of FileOp objects 00931 // Build up ordered array of FileOps... 00932 foreach ( $ops as $operation ) { 00933 $opName = $operation['op']; 00934 if ( isset( $supportedOps[$opName] ) ) { 00935 $class = $supportedOps[$opName]; 00936 // Get params for this operation 00937 $params = $operation; 00938 // Append the FileOp class 00939 $performOps[] = new $class( $this, $params ); 00940 } else { 00941 throw new MWException( "Operation '$opName' is not supported." ); 00942 } 00943 } 00944 00945 return $performOps; 00946 } 00947 00956 final public function getPathsToLockForOpsInternal( array $performOps ) { 00957 // Build up a list of files to lock... 00958 $paths = array( 'sh' => array(), 'ex' => array() ); 00959 foreach ( $performOps as $fileOp ) { 00960 $paths['sh'] = array_merge( $paths['sh'], $fileOp->storagePathsRead() ); 00961 $paths['ex'] = array_merge( $paths['ex'], $fileOp->storagePathsChanged() ); 00962 } 00963 // Optimization: if doing an EX lock anyway, don't also set an SH one 00964 $paths['sh'] = array_diff( $paths['sh'], $paths['ex'] ); 00965 // Get a shared lock on the parent directory of each path changed 00966 $paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) ); 00967 00968 return $paths; 00969 } 00970 00975 public function getScopedLocksForOps( array $ops, Status $status ) { 00976 $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) ); 00977 return array( 00978 $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ), 00979 $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status ) 00980 ); 00981 } 00982 00987 final protected function doOperationsInternal( array $ops, array $opts ) { 00988 wfProfileIn( __METHOD__ ); 00989 wfProfileIn( __METHOD__ . '-' . $this->name ); 00990 $status = Status::newGood(); 00991 00992 // Build up a list of FileOps... 00993 $performOps = $this->getOperationsInternal( $ops ); 00994 00995 // Acquire any locks as needed... 00996 if ( empty( $opts['nonLocking'] ) ) { 00997 // Build up a list of files to lock... 00998 $paths = $this->getPathsToLockForOpsInternal( $performOps ); 00999 // Try to lock those files for the scope of this function... 01000 $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ); 01001 $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status ); 01002 if ( !$status->isOK() ) { 01003 wfProfileOut( __METHOD__ . '-' . $this->name ); 01004 wfProfileOut( __METHOD__ ); 01005 return $status; // abort 01006 } 01007 } 01008 01009 // Clear any file cache entries (after locks acquired) 01010 if ( empty( $opts['preserveCache'] ) ) { 01011 $this->clearCache(); 01012 } 01013 01014 // Load from the persistent file and container caches 01015 $this->primeFileCache( $performOps ); 01016 $this->primeContainerCache( $performOps ); 01017 01018 // Actually attempt the operation batch... 01019 $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal ); 01020 01021 // Merge errors into status fields 01022 $status->merge( $subStatus ); 01023 $status->success = $subStatus->success; // not done in merge() 01024 01025 wfProfileOut( __METHOD__ . '-' . $this->name ); 01026 wfProfileOut( __METHOD__ ); 01027 return $status; 01028 } 01029 01035 final protected function doQuickOperationsInternal( array $ops ) { 01036 wfProfileIn( __METHOD__ ); 01037 wfProfileIn( __METHOD__ . '-' . $this->name ); 01038 $status = Status::newGood(); 01039 01040 $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' ); 01041 $async = ( $this->parallelize === 'implicit' ); 01042 $maxConcurrency = $this->concurrency; // throttle 01043 01044 $statuses = array(); // array of (index => Status) 01045 $fileOpHandles = array(); // list of (index => handle) arrays 01046 $curFileOpHandles = array(); // current handle batch 01047 // Perform the sync-only ops and build up op handles for the async ops... 01048 foreach ( $ops as $index => $params ) { 01049 if ( !in_array( $params['op'], $supportedOps ) ) { 01050 wfProfileOut( __METHOD__ . '-' . $this->name ); 01051 wfProfileOut( __METHOD__ ); 01052 throw new MWException( "Operation '{$params['op']}' is not supported." ); 01053 } 01054 $method = $params['op'] . 'Internal'; // e.g. "storeInternal" 01055 $subStatus = $this->$method( array( 'async' => $async ) + $params ); 01056 if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async 01057 if ( count( $curFileOpHandles ) >= $maxConcurrency ) { 01058 $fileOpHandles[] = $curFileOpHandles; // push this batch 01059 $curFileOpHandles = array(); 01060 } 01061 $curFileOpHandles[$index] = $subStatus->value; // keep index 01062 } else { // error or completed 01063 $statuses[$index] = $subStatus; // keep index 01064 } 01065 } 01066 if ( count( $curFileOpHandles ) ) { 01067 $fileOpHandles[] = $curFileOpHandles; // last batch 01068 } 01069 // Do all the async ops that can be done concurrently... 01070 foreach ( $fileOpHandles as $fileHandleBatch ) { 01071 $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch ); 01072 } 01073 // Marshall and merge all the responses... 01074 foreach ( $statuses as $index => $subStatus ) { 01075 $status->merge( $subStatus ); 01076 if ( $subStatus->isOK() ) { 01077 $status->success[$index] = true; 01078 ++$status->successCount; 01079 } else { 01080 $status->success[$index] = false; 01081 ++$status->failCount; 01082 } 01083 } 01084 01085 wfProfileOut( __METHOD__ . '-' . $this->name ); 01086 wfProfileOut( __METHOD__ ); 01087 return $status; 01088 } 01089 01099 final public function executeOpHandlesInternal( array $fileOpHandles ) { 01100 wfProfileIn( __METHOD__ ); 01101 wfProfileIn( __METHOD__ . '-' . $this->name ); 01102 foreach ( $fileOpHandles as $fileOpHandle ) { 01103 if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) { 01104 throw new MWException( "Given a non-FileBackendStoreOpHandle object." ); 01105 } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) { 01106 throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." ); 01107 } 01108 } 01109 $res = $this->doExecuteOpHandlesInternal( $fileOpHandles ); 01110 foreach ( $fileOpHandles as $fileOpHandle ) { 01111 $fileOpHandle->closeResources(); 01112 } 01113 wfProfileOut( __METHOD__ . '-' . $this->name ); 01114 wfProfileOut( __METHOD__ ); 01115 return $res; 01116 } 01117 01122 protected function doExecuteOpHandlesInternal( array $fileOpHandles ) { 01123 foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty 01124 throw new MWException( "This backend supports no asynchronous operations." ); 01125 } 01126 return array(); 01127 } 01128 01132 final public function preloadCache( array $paths ) { 01133 $fullConts = array(); // full container names 01134 foreach ( $paths as $path ) { 01135 list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path ); 01136 $fullConts[] = $fullCont; 01137 } 01138 // Load from the persistent file and container caches 01139 $this->primeContainerCache( $fullConts ); 01140 $this->primeFileCache( $paths ); 01141 } 01142 01146 final public function clearCache( array $paths = null ) { 01147 if ( is_array( $paths ) ) { 01148 $paths = array_map( 'FileBackend::normalizeStoragePath', $paths ); 01149 $paths = array_filter( $paths, 'strlen' ); // remove nulls 01150 } 01151 if ( $paths === null ) { 01152 $this->cheapCache->clear(); 01153 $this->expensiveCache->clear(); 01154 } else { 01155 foreach ( $paths as $path ) { 01156 $this->cheapCache->clear( $path ); 01157 $this->expensiveCache->clear( $path ); 01158 } 01159 } 01160 $this->doClearCache( $paths ); 01161 } 01162 01171 protected function doClearCache( array $paths = null ) {} 01172 01180 abstract protected function directoriesAreVirtual(); 01181 01189 final protected static function isValidContainerName( $container ) { 01190 // This accounts for Swift and S3 restrictions while leaving room 01191 // for things like '.xxx' (hex shard chars) or '.seg' (segments). 01192 // This disallows directory separators or traversal characters. 01193 // Note that matching strings URL encode to the same string; 01194 // in Swift, the length restriction is *after* URL encoding. 01195 return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container ); 01196 } 01197 01211 final protected function resolveStoragePath( $storagePath ) { 01212 list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath ); 01213 if ( $backend === $this->name ) { // must be for this backend 01214 $relPath = self::normalizeContainerPath( $relPath ); 01215 if ( $relPath !== null ) { 01216 // Get shard for the normalized path if this container is sharded 01217 $cShard = $this->getContainerShard( $container, $relPath ); 01218 // Validate and sanitize the relative path (backend-specific) 01219 $relPath = $this->resolveContainerPath( $container, $relPath ); 01220 if ( $relPath !== null ) { 01221 // Prepend any wiki ID prefix to the container name 01222 $container = $this->fullContainerName( $container ); 01223 if ( self::isValidContainerName( $container ) ) { 01224 // Validate and sanitize the container name (backend-specific) 01225 $container = $this->resolveContainerName( "{$container}{$cShard}" ); 01226 if ( $container !== null ) { 01227 return array( $container, $relPath, $cShard ); 01228 } 01229 } 01230 } 01231 } 01232 } 01233 return array( null, null, null ); 01234 } 01235 01245 final protected function resolveStoragePathReal( $storagePath ) { 01246 list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath ); 01247 if ( $cShard !== null ) { 01248 return array( $container, $relPath ); 01249 } 01250 return array( null, null ); 01251 } 01252 01261 final protected function getContainerShard( $container, $relPath ) { 01262 list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container ); 01263 if ( $levels == 1 || $levels == 2 ) { 01264 // Hash characters are either base 16 or 36 01265 $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]'; 01266 // Get a regex that represents the shard portion of paths. 01267 // The concatenation of the captures gives us the shard. 01268 if ( $levels === 1 ) { // 16 or 36 shards per container 01269 $hashDirRegex = '(' . $char . ')'; 01270 } else { // 256 or 1296 shards per container 01271 if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc") 01272 $hashDirRegex = $char . '/(' . $char . '{2})'; 01273 } else { // short hash dir format (e.g. "a/b/c") 01274 $hashDirRegex = '(' . $char . ')/(' . $char . ')'; 01275 } 01276 } 01277 // Allow certain directories to be above the hash dirs so as 01278 // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab"). 01279 // They must be 2+ chars to avoid any hash directory ambiguity. 01280 $m = array(); 01281 if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) { 01282 return '.' . implode( '', array_slice( $m, 1 ) ); 01283 } 01284 return null; // failed to match 01285 } 01286 return ''; // no sharding 01287 } 01288 01297 final public function isSingleShardPathInternal( $storagePath ) { 01298 list( $c, $r, $shard ) = $this->resolveStoragePath( $storagePath ); 01299 return ( $shard !== null ); 01300 } 01301 01310 final protected function getContainerHashLevels( $container ) { 01311 if ( isset( $this->shardViaHashLevels[$container] ) ) { 01312 $config = $this->shardViaHashLevels[$container]; 01313 $hashLevels = (int)$config['levels']; 01314 if ( $hashLevels == 1 || $hashLevels == 2 ) { 01315 $hashBase = (int)$config['base']; 01316 if ( $hashBase == 16 || $hashBase == 36 ) { 01317 return array( $hashLevels, $hashBase, $config['repeat'] ); 01318 } 01319 } 01320 } 01321 return array( 0, 0, false ); // no sharding 01322 } 01323 01330 final protected function getContainerSuffixes( $container ) { 01331 $shards = array(); 01332 list( $digits, $base ) = $this->getContainerHashLevels( $container ); 01333 if ( $digits > 0 ) { 01334 $numShards = pow( $base, $digits ); 01335 for ( $index = 0; $index < $numShards; $index++ ) { 01336 $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits ); 01337 } 01338 } 01339 return $shards; 01340 } 01341 01348 final protected function fullContainerName( $container ) { 01349 if ( $this->wikiId != '' ) { 01350 return "{$this->wikiId}-$container"; 01351 } else { 01352 return $container; 01353 } 01354 } 01355 01364 protected function resolveContainerName( $container ) { 01365 return $container; 01366 } 01367 01378 protected function resolveContainerPath( $container, $relStoragePath ) { 01379 return $relStoragePath; 01380 } 01381 01388 private function containerCacheKey( $container ) { 01389 return wfMemcKey( 'backend', $this->getName(), 'container', $container ); 01390 } 01391 01398 final protected function setContainerCache( $container, $val ) { 01399 $this->memCache->add( $this->containerCacheKey( $container ), $val, 14*86400 ); 01400 } 01401 01408 final protected function deleteContainerCache( $container ) { 01409 if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) { 01410 trigger_error( "Unable to delete stat cache for container $container." ); 01411 } 01412 } 01413 01421 final protected function primeContainerCache( array $items ) { 01422 wfProfileIn( __METHOD__ ); 01423 wfProfileIn( __METHOD__ . '-' . $this->name ); 01424 01425 $paths = array(); // list of storage paths 01426 $contNames = array(); // (cache key => resolved container name) 01427 // Get all the paths/containers from the items... 01428 foreach ( $items as $item ) { 01429 if ( $item instanceof FileOp ) { 01430 $paths = array_merge( $paths, $item->storagePathsRead() ); 01431 $paths = array_merge( $paths, $item->storagePathsChanged() ); 01432 } elseif ( self::isStoragePath( $item ) ) { 01433 $paths[] = $item; 01434 } elseif ( is_string( $item ) ) { // full container name 01435 $contNames[$this->containerCacheKey( $item )] = $item; 01436 } 01437 } 01438 // Get all the corresponding cache keys for paths... 01439 foreach ( $paths as $path ) { 01440 list( $fullCont, $r, $s ) = $this->resolveStoragePath( $path ); 01441 if ( $fullCont !== null ) { // valid path for this backend 01442 $contNames[$this->containerCacheKey( $fullCont )] = $fullCont; 01443 } 01444 } 01445 01446 $contInfo = array(); // (resolved container name => cache value) 01447 // Get all cache entries for these container cache keys... 01448 $values = $this->memCache->getMulti( array_keys( $contNames ) ); 01449 foreach ( $values as $cacheKey => $val ) { 01450 $contInfo[$contNames[$cacheKey]] = $val; 01451 } 01452 01453 // Populate the container process cache for the backend... 01454 $this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) ); 01455 01456 wfProfileOut( __METHOD__ . '-' . $this->name ); 01457 wfProfileOut( __METHOD__ ); 01458 } 01459 01468 protected function doPrimeContainerCache( array $containerInfo ) {} 01469 01476 private function fileCacheKey( $path ) { 01477 return wfMemcKey( 'backend', $this->getName(), 'file', sha1( $path ) ); 01478 } 01479 01488 final protected function setFileCache( $path, $val ) { 01489 $this->memCache->add( $this->fileCacheKey( $path ), $val, 7*86400 ); 01490 } 01491 01498 final protected function deleteFileCache( $path ) { 01499 if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) { 01500 trigger_error( "Unable to delete stat cache for file $path." ); 01501 } 01502 } 01503 01511 final protected function primeFileCache( array $items ) { 01512 wfProfileIn( __METHOD__ ); 01513 wfProfileIn( __METHOD__ . '-' . $this->name ); 01514 01515 $paths = array(); // list of storage paths 01516 $pathNames = array(); // (cache key => storage path) 01517 // Get all the paths/containers from the items... 01518 foreach ( $items as $item ) { 01519 if ( $item instanceof FileOp ) { 01520 $paths = array_merge( $paths, $item->storagePathsRead() ); 01521 $paths = array_merge( $paths, $item->storagePathsChanged() ); 01522 } elseif ( self::isStoragePath( $item ) ) { 01523 $paths[] = $item; 01524 } 01525 } 01526 // Get all the corresponding cache keys for paths... 01527 foreach ( $paths as $path ) { 01528 list( $cont, $rel, $s ) = $this->resolveStoragePath( $path ); 01529 if ( $rel !== null ) { // valid path for this backend 01530 $pathNames[$this->fileCacheKey( $path )] = $path; 01531 } 01532 } 01533 // Get all cache entries for these container cache keys... 01534 $values = $this->memCache->getMulti( array_keys( $pathNames ) ); 01535 foreach ( $values as $cacheKey => $val ) { 01536 if ( is_array( $val ) ) { 01537 $path = $pathNames[$cacheKey]; 01538 $this->cheapCache->set( $path, 'stat', $val ); 01539 if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata 01540 $this->cheapCache->set( $path, 'sha1', 01541 array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) ); 01542 } 01543 } 01544 } 01545 01546 wfProfileOut( __METHOD__ . '-' . $this->name ); 01547 wfProfileOut( __METHOD__ ); 01548 } 01549 } 01550 01559 abstract class FileBackendStoreOpHandle { 01561 public $params = array(); // params to caller functions 01563 public $backend; 01565 public $resourcesToClose = array(); 01566 01567 public $call; // string; name that identifies the function called 01568 01574 public function closeResources() { 01575 array_map( 'fclose', $this->resourcesToClose ); 01576 } 01577 } 01578 01585 abstract class FileBackendStoreShardListIterator implements Iterator { 01587 protected $backend; 01589 protected $params; 01591 protected $shardSuffixes; 01592 protected $container; // string; full container name 01593 protected $directory; // string; resolved relative path 01594 01596 protected $iter; 01597 protected $curShard = 0; // integer 01598 protected $pos = 0; // integer 01599 01601 protected $multiShardPaths = array(); // (rel path => 1) 01602 01610 public function __construct( 01611 FileBackendStore $backend, $container, $dir, array $suffixes, array $params 01612 ) { 01613 $this->backend = $backend; 01614 $this->container = $container; 01615 $this->directory = $dir; 01616 $this->shardSuffixes = $suffixes; 01617 $this->params = $params; 01618 } 01619 01624 public function key() { 01625 return $this->pos; 01626 } 01627 01632 public function valid() { 01633 if ( $this->iter instanceof Iterator ) { 01634 return $this->iter->valid(); 01635 } elseif ( is_array( $this->iter ) ) { 01636 return ( current( $this->iter ) !== false ); // no paths can have this value 01637 } 01638 return false; // some failure? 01639 } 01640 01645 public function current() { 01646 return ( $this->iter instanceof Iterator ) 01647 ? $this->iter->current() 01648 : current( $this->iter ); 01649 } 01650 01655 public function next() { 01656 ++$this->pos; 01657 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter ); 01658 do { 01659 $continue = false; // keep scanning shards? 01660 $this->filterViaNext(); // filter out duplicates 01661 // Find the next non-empty shard if no elements are left 01662 if ( !$this->valid() ) { 01663 $this->nextShardIteratorIfNotValid(); 01664 $continue = $this->valid(); // re-filter unless we ran out of shards 01665 } 01666 } while ( $continue ); 01667 } 01668 01673 public function rewind() { 01674 $this->pos = 0; 01675 $this->curShard = 0; 01676 $this->setIteratorFromCurrentShard(); 01677 do { 01678 $continue = false; // keep scanning shards? 01679 $this->filterViaNext(); // filter out duplicates 01680 // Find the next non-empty shard if no elements are left 01681 if ( !$this->valid() ) { 01682 $this->nextShardIteratorIfNotValid(); 01683 $continue = $this->valid(); // re-filter unless we ran out of shards 01684 } 01685 } while ( $continue ); 01686 } 01687 01691 protected function filterViaNext() { 01692 while ( $this->valid() ) { 01693 $rel = $this->iter->current(); // path relative to given directory 01694 $path = $this->params['dir'] . "/{$rel}"; // full storage path 01695 if ( $this->backend->isSingleShardPathInternal( $path ) ) { 01696 break; // path is only on one shard; no issue with duplicates 01697 } elseif ( isset( $this->multiShardPaths[$rel] ) ) { 01698 // Don't keep listing paths that are on multiple shards 01699 ( $this->iter instanceof Iterator ) ? $this->iter->next() : next( $this->iter ); 01700 } else { 01701 $this->multiShardPaths[$rel] = 1; 01702 break; 01703 } 01704 } 01705 } 01706 01712 protected function nextShardIteratorIfNotValid() { 01713 while ( !$this->valid() && ++$this->curShard < count( $this->shardSuffixes ) ) { 01714 $this->setIteratorFromCurrentShard(); 01715 } 01716 } 01717 01721 protected function setIteratorFromCurrentShard() { 01722 $this->iter = $this->listFromShard( 01723 $this->container . $this->shardSuffixes[$this->curShard], 01724 $this->directory, $this->params ); 01725 // Start loading results so that current() works 01726 if ( $this->iter ) { 01727 ( $this->iter instanceof Iterator ) ? $this->iter->rewind() : reset( $this->iter ); 01728 } 01729 } 01730 01739 abstract protected function listFromShard( $container, $dir, array $params ); 01740 } 01741 01745 class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator { 01750 protected function listFromShard( $container, $dir, array $params ) { 01751 return $this->backend->getDirectoryListInternal( $container, $dir, $params ); 01752 } 01753 } 01754 01758 class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator { 01763 protected function listFromShard( $container, $dir, array $params ) { 01764 return $this->backend->getFileListInternal( $container, $dir, $params ); 01765 } 01766 }