94 parent::__construct( $config );
96 $this->autoResync = $config[
'autoResync'] ??
false;
97 $this->asyncWrites = isset( $config[
'replication'] ) && $config[
'replication'] ===
'async';
101 foreach ( $config[
'backends']
as $index => $config ) {
102 $name = $config[
'name'];
103 if ( isset( $namesUsed[
$name] ) ) {
104 throw new LogicException(
"Two or more backends defined with the name $name." );
106 $namesUsed[
$name] = 1;
108 unset( $config[
'readOnly'] );
109 unset( $config[
'fileJournal'] );
110 unset( $config[
'lockManager'] );
112 if ( !empty( $config[
'isMultiMaster'] ) ) {
113 if ( $this->masterIndex >= 0 ) {
114 throw new LogicException(
'More than one master backend defined.' );
116 $this->masterIndex = $index;
119 if ( !empty( $config[
'readAffinity'] ) ) {
120 $this->readIndex = $index;
123 if ( !isset( $config[
'class'] ) ) {
124 throw new InvalidArgumentException(
'No class given for a backend config.' );
126 $class = $config[
'class'];
127 $this->backends[$index] =
new $class( $config );
129 if ( $this->masterIndex < 0 ) {
130 throw new LogicException(
'No master backend defined.' );
132 if ( $this->readIndex < 0 ) {
144 if ( empty( $opts[
'nonLocking'] ) ) {
154 $opts[
'preserveCache'] =
true;
164 if ( !$syncStatus->isOK() ) {
168 if ( $this->autoResync ===
false
169 || !$this->
resyncFiles( $relevantPaths, $this->autoResync )->isOK()
178 $masterStatus = $mbe->doOperations( $realOps, $opts );
179 $status->merge( $masterStatus );
184 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
185 foreach ( $this->backends
as $index => $backend ) {
186 if ( $index === $this->masterIndex ) {
194 function ()
use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
196 "'{$backend->getName()}' async replication; paths: " .
198 $backend->doOperations( $realOps, $opts );
203 "'{$backend->getName()}' sync replication; paths: " .
205 $status->merge( $backend->doOperations( $realOps, $opts ) );
212 $status->success = $masterStatus->success;
213 $status->successCount = $masterStatus->successCount;
214 $status->failCount = $masterStatus->failCount;
227 if ( $this->syncChecks == 0 ||
count( $this->backends ) <= 1 ) {
232 foreach ( $this->backends
as $backend ) {
233 $realPaths = $this->
substPaths( $paths, $backend );
234 $backend->preloadFileStat( [
'srcs' => $realPaths,
'latest' =>
true ] );
242 $mStat = $mBackend->getFileStat( $mParams );
243 if ( $this->syncChecks & self::CHECK_SHA1 ) {
244 $mSha1 = $mBackend->getFileSha1Base36( $mParams );
249 foreach ( $this->backends
as $index => $cBackend ) {
250 if ( $index === $this->masterIndex ) {
254 $cStat = $cBackend->getFileStat( $cParams );
260 if ( ( $this->syncChecks & self::CHECK_SIZE )
261 && $cStat[
'size'] != $mStat[
'size']
266 if ( $this->syncChecks & self::CHECK_TIME ) {
269 if ( abs( $mTs - $cTs ) > 30 ) {
274 if ( ( $this->syncChecks & self::CHECK_SHA1 ) && $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) {
278 } elseif ( $cStat ) {
295 if (
count( $this->backends ) <= 1 ) {
300 foreach ( $this->backends
as $backend ) {
301 $realPath = $this->
substPaths( $path, $backend );
302 if ( !$backend->isPathUsableInternal( $realPath ) ) {
324 $mPath = $this->
substPaths( $path, $mBackend );
325 $mSha1 = $mBackend->getFileSha1Base36( [
'src' => $mPath,
'latest' =>
true ] );
326 $mStat = $mBackend->getFileStat( [
'src' => $mPath,
'latest' =>
true ] );
327 if ( $mStat ===
null || ( $mSha1 !==
false && !$mStat ) ) {
328 $status->fatal(
'backend-fail-internal', $this->
name );
330 .
': File is not available on the master backend' );
334 foreach ( $this->backends
as $index => $cBackend ) {
335 if ( $index === $this->masterIndex ) {
338 $cPath = $this->
substPaths( $path, $cBackend );
339 $cSha1 = $cBackend->getFileSha1Base36( [
'src' => $cPath,
'latest' =>
true ] );
340 $cStat = $cBackend->getFileStat( [
'src' => $cPath,
'latest' =>
true ] );
341 if ( $cStat ===
null || ( $cSha1 !==
false && !$cStat ) ) {
342 $status->fatal(
'backend-fail-internal', $cBackend->getName() );
344 ': File is not available on the clone backend' );
347 if ( $mSha1 === $cSha1 ) {
349 } elseif ( $mSha1 !==
false ) {
350 if ( $resyncMode ===
'conservative'
351 && $cStat && $cStat[
'mtime'] > $mStat[
'mtime']
356 $fsFile = $mBackend->getLocalReference(
357 [
'src' => $mPath,
'latest' =>
true ] );
358 $status->merge( $cBackend->quickStore(
359 [
'src' => $fsFile->getPath(),
'dst' => $cPath ]
361 } elseif ( $mStat ===
false ) {
362 if ( $resyncMode ===
'conservative' ) {
366 $status->merge( $cBackend->quickDelete( [
'src' => $cPath ] ) );
387 foreach ( $ops
as $op ) {
388 if ( isset( $op[
'src'] ) ) {
391 if ( empty( $op[
'ignoreMissingSource'] )
392 || $this->
fileExists( [
'src' => $op[
'src'] ] )
394 $paths[] = $op[
'src'];
397 if ( isset( $op[
'srcs'] ) ) {
398 $paths = array_merge( $paths, $op[
'srcs'] );
400 if ( isset( $op[
'dst'] ) ) {
401 $paths[] = $op[
'dst'];
405 return array_values( array_unique( array_filter( $paths,
'FileBackend::isStoragePath' ) ) );
418 foreach ( $ops
as $op ) {
420 foreach ( [
'src',
'srcs',
'dst',
'dir' ]
as $par ) {
421 if ( isset( $newOp[$par] ) ) {
422 $newOp[$par] = $this->
substPaths( $newOp[$par], $backend );
453 '!^mwstore://' . preg_quote( $this->
name,
'!' ) .
'/!',
467 '!^mwstore://([^/]+)!',
478 foreach ( $ops
as $op ) {
479 if ( $op[
'op'] ===
'store' && !isset( $op[
'srcRef'] ) ) {
490 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
492 $status->merge( $masterStatus );
494 foreach ( $this->backends
as $index => $backend ) {
495 if ( $index === $this->masterIndex ) {
502 function ()
use ( $backend, $realOps ) {
503 $backend->doQuickOperations( $realOps );
507 $status->merge( $backend->doQuickOperations( $realOps ) );
513 $status->success = $masterStatus->success;
514 $status->successCount = $masterStatus->successCount;
515 $status->failCount = $masterStatus->failCount;
544 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
546 $status->merge( $masterStatus );
548 foreach ( $this->backends
as $index => $backend ) {
549 if ( $index === $this->masterIndex ) {
554 if ( $this->asyncWrites ) {
556 function ()
use ( $backend, $method, $realParams ) {
557 $backend->$method( $realParams );
561 $status->merge( $backend->$method( $realParams ) );
572 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
581 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
583 return $this->backends[$index]->fileExists( $realParams );
588 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
590 return $this->backends[$index]->getFileTimestamp( $realParams );
595 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
597 return $this->backends[$index]->getFileSize( $realParams );
602 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
604 return $this->backends[$index]->getFileStat( $realParams );
609 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
611 return $this->backends[$index]->getFileXAttributes( $realParams );
616 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
618 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
630 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
632 return $this->backends[$index]->getFileSha1Base36( $realParams );
637 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
639 return $this->backends[$index]->getFileProps( $realParams );
644 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
646 return $this->backends[$index]->streamFile( $realParams );
651 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
653 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
656 foreach ( $fsFilesM
as $path => $fsFile ) {
665 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
667 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
670 foreach ( $tempFilesM
as $path => $tempFile ) {
679 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
681 return $this->backends[$index]->getFileHttpUrl( $realParams );
685 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
691 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
697 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
707 foreach ( $this->backends
as $backend ) {
708 $realPaths = is_array( $paths ) ? $this->
substPaths( $paths, $backend ) :
null;
709 $backend->clearCache( $realPaths );
714 $realPaths = $this->
substPaths( $paths, $this->backends[$this->readIndex] );
720 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
722 return $this->backends[$index]->preloadFileStat( $realParams );
726 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
729 $paths = $this->backends[
$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );