97 parent::__construct( $config );
99 $this->autoResync = $config[
'autoResync'] ??
false;
100 $this->asyncWrites = isset( $config[
'replication'] ) && $config[
'replication'] ===
'async';
104 foreach ( $config[
'backends']
as $index => $config ) {
105 $name = $config[
'name'];
106 if ( isset( $namesUsed[
$name] ) ) {
107 throw new LogicException(
"Two or more backends defined with the name $name." );
109 $namesUsed[
$name] = 1;
111 unset( $config[
'readOnly'] );
112 unset( $config[
'fileJournal'] );
113 unset( $config[
'lockManager'] );
115 if ( !empty( $config[
'isMultiMaster'] ) ) {
116 if ( $this->masterIndex >= 0 ) {
117 throw new LogicException(
'More than one master backend defined.' );
119 $this->masterIndex = $index;
122 if ( !empty( $config[
'readAffinity'] ) ) {
123 $this->readIndex = $index;
126 if ( !isset( $config[
'class'] ) ) {
127 throw new InvalidArgumentException(
'No class given for a backend config.' );
129 $class = $config[
'class'];
130 $this->backends[$index] =
new $class( $config );
132 if ( $this->masterIndex < 0 ) {
133 throw new LogicException(
'No master backend defined.' );
135 if ( $this->readIndex < 0 ) {
147 if ( empty( $opts[
'nonLocking'] ) ) {
157 $opts[
'preserveCache'] =
true;
167 if ( !$syncStatus->isOK() ) {
171 if ( $this->autoResync ===
false
172 || !$this->
resyncFiles( $relevantPaths, $this->autoResync )->isOK()
181 $masterStatus = $mbe->doOperations( $realOps, $opts );
182 $status->merge( $masterStatus );
187 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
188 foreach ( $this->backends
as $index => $backend ) {
189 if ( $index === $this->masterIndex ) {
197 function ()
use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
199 "'{$backend->getName()}' async replication; paths: " .
201 $backend->doOperations( $realOps, $opts );
206 "'{$backend->getName()}' sync replication; paths: " .
208 $status->merge( $backend->doOperations( $realOps, $opts ) );
215 $status->success = $masterStatus->success;
216 $status->successCount = $masterStatus->successCount;
217 $status->failCount = $masterStatus->failCount;
230 if ( $this->syncChecks == 0 ||
count( $this->backends ) <= 1 ) {
235 foreach ( $this->backends
as $backend ) {
236 $realPaths = $this->
substPaths( $paths, $backend );
237 $backend->preloadFileStat( [
'srcs' => $realPaths,
'latest' =>
true ] );
245 $mStat = $mBackend->getFileStat( $mParams );
246 if ( $this->syncChecks & self::CHECK_SHA1 ) {
247 $mSha1 = $mBackend->getFileSha1Base36( $mParams );
252 foreach ( $this->backends
as $index => $cBackend ) {
253 if ( $index === $this->masterIndex ) {
257 $cStat = $cBackend->getFileStat( $cParams );
263 if ( $this->syncChecks & self::CHECK_SIZE ) {
264 if ( $cStat[
'size'] != $mStat[
'size'] ) {
269 if ( $this->syncChecks & self::CHECK_TIME ) {
272 if ( abs( $mTs - $cTs ) > 30 ) {
277 if ( $this->syncChecks & self::CHECK_SHA1 ) {
278 if ( $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) {
302 if (
count( $this->backends ) <= 1 ) {
307 foreach ( $this->backends
as $backend ) {
308 $realPath = $this->
substPaths( $path, $backend );
309 if ( !$backend->isPathUsableInternal( $realPath ) ) {
331 $mPath = $this->
substPaths( $path, $mBackend );
332 $mSha1 = $mBackend->getFileSha1Base36( [
'src' => $mPath,
'latest' =>
true ] );
333 $mStat = $mBackend->getFileStat( [
'src' => $mPath,
'latest' =>
true ] );
334 if ( $mStat ===
null || ( $mSha1 !==
false && !$mStat ) ) {
335 $status->fatal(
'backend-fail-internal', $this->
name );
337 .
': File is not available on the master backend' );
341 foreach ( $this->backends
as $index => $cBackend ) {
342 if ( $index === $this->masterIndex ) {
345 $cPath = $this->
substPaths( $path, $cBackend );
346 $cSha1 = $cBackend->getFileSha1Base36( [
'src' => $cPath,
'latest' =>
true ] );
347 $cStat = $cBackend->getFileStat( [
'src' => $cPath,
'latest' =>
true ] );
348 if ( $cStat ===
null || ( $cSha1 !==
false && !$cStat ) ) {
349 $status->fatal(
'backend-fail-internal', $cBackend->getName() );
351 ': File is not available on the clone backend' );
354 if ( $mSha1 === $cSha1 ) {
356 } elseif ( $mSha1 !==
false ) {
357 if ( $resyncMode ===
'conservative'
358 && $cStat && $cStat[
'mtime'] > $mStat[
'mtime']
363 $fsFile = $mBackend->getLocalReference(
364 [
'src' => $mPath,
'latest' =>
true ] );
365 $status->merge( $cBackend->quickStore(
366 [
'src' => $fsFile->getPath(),
'dst' => $cPath ]
368 } elseif ( $mStat ===
false ) {
369 if ( $resyncMode ===
'conservative' ) {
373 $status->merge( $cBackend->quickDelete( [
'src' => $cPath ] ) );
394 foreach ( $ops
as $op ) {
395 if ( isset( $op[
'src'] ) ) {
398 if ( empty( $op[
'ignoreMissingSource'] )
399 || $this->
fileExists( [
'src' => $op[
'src'] ] )
401 $paths[] = $op[
'src'];
404 if ( isset( $op[
'srcs'] ) ) {
405 $paths = array_merge( $paths, $op[
'srcs'] );
407 if ( isset( $op[
'dst'] ) ) {
408 $paths[] = $op[
'dst'];
412 return array_values( array_unique( array_filter( $paths,
'FileBackend::isStoragePath' ) ) );
425 foreach ( $ops
as $op ) {
427 foreach ( [
'src',
'srcs',
'dst',
'dir' ]
as $par ) {
428 if ( isset( $newOp[$par] ) ) {
429 $newOp[$par] = $this->
substPaths( $newOp[$par], $backend );
460 '!^mwstore://' . preg_quote( $this->
name,
'!' ) .
'/!',
474 '!^mwstore://([^/]+)!',
485 foreach ( $ops
as $op ) {
486 if ( $op[
'op'] ===
'store' && !isset( $op[
'srcRef'] ) ) {
497 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
499 $status->merge( $masterStatus );
501 foreach ( $this->backends
as $index => $backend ) {
502 if ( $index === $this->masterIndex ) {
509 function ()
use ( $backend, $realOps ) {
510 $backend->doQuickOperations( $realOps );
514 $status->merge( $backend->doQuickOperations( $realOps ) );
520 $status->success = $masterStatus->success;
521 $status->successCount = $masterStatus->successCount;
522 $status->failCount = $masterStatus->failCount;
551 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
553 $status->merge( $masterStatus );
555 foreach ( $this->backends
as $index => $backend ) {
556 if ( $index === $this->masterIndex ) {
561 if ( $this->asyncWrites ) {
563 function ()
use ( $backend, $method, $realParams ) {
564 $backend->$method( $realParams );
568 $status->merge( $backend->$method( $realParams ) );
579 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
588 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
590 return $this->backends[$index]->fileExists( $realParams );
595 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
597 return $this->backends[$index]->getFileTimestamp( $realParams );
602 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
604 return $this->backends[$index]->getFileSize( $realParams );
609 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
611 return $this->backends[$index]->getFileStat( $realParams );
616 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
618 return $this->backends[$index]->getFileXAttributes( $realParams );
623 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
625 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
628 foreach ( $contentsM
as $path => $data ) {
637 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
639 return $this->backends[$index]->getFileSha1Base36( $realParams );
644 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
646 return $this->backends[$index]->getFileProps( $realParams );
651 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
653 return $this->backends[$index]->streamFile( $realParams );
658 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
660 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
663 foreach ( $fsFilesM
as $path => $fsFile ) {
672 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
674 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
677 foreach ( $tempFilesM
as $path => $tempFile ) {
686 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
688 return $this->backends[$index]->getFileHttpUrl( $realParams );
692 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
698 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
704 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
714 foreach ( $this->backends
as $backend ) {
715 $realPaths = is_array( $paths ) ? $this->
substPaths( $paths, $backend ) :
null;
716 $backend->clearCache( $realPaths );
721 $realPaths = $this->
substPaths( $paths, $this->backends[$this->readIndex] );
727 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
729 return $this->backends[$index]->preloadFileStat( $realParams );
733 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
736 $paths = $this->backends[
$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );