94 parent::__construct( $config );
95 $this->syncChecks = isset( $config[
'syncChecks'] )
96 ? $config[
'syncChecks']
98 $this->autoResync = isset( $config[
'autoResync'] )
99 ? $config[
'autoResync']
101 $this->asyncWrites = isset( $config[
'replication'] ) && $config[
'replication'] ===
'async';
105 foreach ( $config[
'backends']
as $index => $config ) {
106 $name = $config[
'name'];
107 if ( isset( $namesUsed[
$name] ) ) {
108 throw new LogicException(
"Two or more backends defined with the name $name." );
110 $namesUsed[
$name] = 1;
112 unset( $config[
'readOnly'] );
113 unset( $config[
'fileJournal'] );
114 unset( $config[
'lockManager'] );
116 if ( !empty( $config[
'isMultiMaster'] ) ) {
117 if ( $this->masterIndex >= 0 ) {
118 throw new LogicException(
'More than one master backend defined.' );
120 $this->masterIndex = $index;
123 if ( !empty( $config[
'readAffinity'] ) ) {
124 $this->readIndex = $index;
127 if ( !isset( $config[
'class'] ) ) {
128 throw new InvalidArgumentException(
'No class given for a backend config.' );
130 $class = $config[
'class'];
131 $this->backends[$index] =
new $class( $config );
133 if ( $this->masterIndex < 0 ) {
134 throw new LogicException(
'No master backend defined.' );
136 if ( $this->readIndex < 0 ) {
148 if ( empty( $opts[
'nonLocking'] ) ) {
158 $opts[
'preserveCache'] =
true;
168 if ( !$syncStatus->isOK() ) {
172 if ( $this->autoResync ===
false
173 || !$this->
resyncFiles( $relevantPaths, $this->autoResync )->isOK()
182 $masterStatus = $mbe->doOperations( $realOps, $opts );
183 $status->merge( $masterStatus );
188 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
189 foreach ( $this->backends
as $index => $backend ) {
190 if ( $index === $this->masterIndex ) {
198 function ()
use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
200 "'{$backend->getName()}' async replication; paths: " .
202 $backend->doOperations( $realOps, $opts );
207 "'{$backend->getName()}' sync replication; paths: " .
209 $status->merge( $backend->doOperations( $realOps, $opts ) );
216 $status->success = $masterStatus->success;
217 $status->successCount = $masterStatus->successCount;
218 $status->failCount = $masterStatus->failCount;
231 if ( $this->syncChecks == 0 ||
count( $this->backends ) <= 1 ) {
236 foreach ( $this->backends
as $backend ) {
237 $realPaths = $this->
substPaths( $paths, $backend );
238 $backend->preloadFileStat( [
'srcs' => $realPaths,
'latest' =>
true ] );
246 $mStat = $mBackend->getFileStat( $mParams );
247 if ( $this->syncChecks & self::CHECK_SHA1 ) {
248 $mSha1 = $mBackend->getFileSha1Base36( $mParams );
253 foreach ( $this->backends
as $index => $cBackend ) {
254 if ( $index === $this->masterIndex ) {
258 $cStat = $cBackend->getFileStat( $cParams );
264 if ( $this->syncChecks & self::CHECK_SIZE ) {
265 if ( $cStat[
'size'] != $mStat[
'size'] ) {
270 if ( $this->syncChecks & self::CHECK_TIME ) {
273 if ( abs( $mTs - $cTs ) > 30 ) {
278 if ( $this->syncChecks & self::CHECK_SHA1 ) {
279 if ( $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) {
303 if (
count( $this->backends ) <= 1 ) {
308 foreach ( $this->backends
as $backend ) {
309 $realPath = $this->
substPaths( $path, $backend );
310 if ( !$backend->isPathUsableInternal( $realPath ) ) {
332 $mPath = $this->
substPaths( $path, $mBackend );
333 $mSha1 = $mBackend->getFileSha1Base36( [
'src' => $mPath,
'latest' =>
true ] );
334 $mStat = $mBackend->getFileStat( [
'src' => $mPath,
'latest' =>
true ] );
335 if ( $mStat ===
null || ( $mSha1 !==
false && !$mStat ) ) {
336 $status->fatal(
'backend-fail-internal', $this->
name );
338 .
': File is not available on the master backend' );
342 foreach ( $this->backends
as $index => $cBackend ) {
343 if ( $index === $this->masterIndex ) {
346 $cPath = $this->
substPaths( $path, $cBackend );
347 $cSha1 = $cBackend->getFileSha1Base36( [
'src' => $cPath,
'latest' =>
true ] );
348 $cStat = $cBackend->getFileStat( [
'src' => $cPath,
'latest' =>
true ] );
349 if ( $cStat ===
null || ( $cSha1 !==
false && !$cStat ) ) {
350 $status->fatal(
'backend-fail-internal', $cBackend->getName() );
352 ': File is not available on the clone backend' );
355 if ( $mSha1 === $cSha1 ) {
357 } elseif ( $mSha1 !==
false ) {
358 if ( $resyncMode ===
'conservative'
359 && $cStat && $cStat[
'mtime'] > $mStat[
'mtime']
364 $fsFile = $mBackend->getLocalReference(
365 [
'src' => $mPath,
'latest' =>
true ] );
366 $status->merge( $cBackend->quickStore(
367 [
'src' => $fsFile->getPath(),
'dst' => $cPath ]
369 } elseif ( $mStat ===
false ) {
370 if ( $resyncMode ===
'conservative' ) {
374 $status->merge( $cBackend->quickDelete( [
'src' => $cPath ] ) );
395 foreach ( $ops
as $op ) {
396 if ( isset( $op[
'src'] ) ) {
399 if ( empty( $op[
'ignoreMissingSource'] )
400 || $this->
fileExists( [
'src' => $op[
'src'] ] )
402 $paths[] = $op[
'src'];
405 if ( isset( $op[
'srcs'] ) ) {
406 $paths = array_merge( $paths, $op[
'srcs'] );
408 if ( isset( $op[
'dst'] ) ) {
409 $paths[] = $op[
'dst'];
413 return array_values( array_unique( array_filter( $paths,
'FileBackend::isStoragePath' ) ) );
426 foreach ( $ops
as $op ) {
428 foreach ( [
'src',
'srcs',
'dst',
'dir' ]
as $par ) {
429 if ( isset( $newOp[$par] ) ) {
430 $newOp[$par] = $this->
substPaths( $newOp[$par], $backend );
461 '!^mwstore://' . preg_quote( $this->
name,
'!' ) .
'/!',
475 '!^mwstore://([^/]+)!',
486 foreach ( $ops
as $op ) {
487 if ( $op[
'op'] ===
'store' && !isset( $op[
'srcRef'] ) ) {
498 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
500 $status->merge( $masterStatus );
502 foreach ( $this->backends
as $index => $backend ) {
503 if ( $index === $this->masterIndex ) {
510 function ()
use ( $backend, $realOps ) {
511 $backend->doQuickOperations( $realOps );
515 $status->merge( $backend->doQuickOperations( $realOps ) );
521 $status->success = $masterStatus->success;
522 $status->successCount = $masterStatus->successCount;
523 $status->failCount = $masterStatus->failCount;
552 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
554 $status->merge( $masterStatus );
556 foreach ( $this->backends
as $index => $backend ) {
557 if ( $index === $this->masterIndex ) {
562 if ( $this->asyncWrites ) {
564 function ()
use ( $backend, $method, $realParams ) {
565 $backend->$method( $realParams );
569 $status->merge( $backend->$method( $realParams ) );
580 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
589 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
591 return $this->backends[$index]->fileExists( $realParams );
596 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
598 return $this->backends[$index]->getFileTimestamp( $realParams );
603 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
605 return $this->backends[$index]->getFileSize( $realParams );
610 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
612 return $this->backends[$index]->getFileStat( $realParams );
617 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
619 return $this->backends[$index]->getFileXAttributes( $realParams );
624 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
626 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
629 foreach ( $contentsM
as $path => $data ) {
638 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
640 return $this->backends[$index]->getFileSha1Base36( $realParams );
645 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
647 return $this->backends[$index]->getFileProps( $realParams );
652 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
654 return $this->backends[$index]->streamFile( $realParams );
659 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
661 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
664 foreach ( $fsFilesM
as $path => $fsFile ) {
673 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
675 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
678 foreach ( $tempFilesM
as $path => $tempFile ) {
687 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
689 return $this->backends[$index]->getFileHttpUrl( $realParams );
693 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
699 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
705 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
715 foreach ( $this->backends
as $backend ) {
716 $realPaths = is_array( $paths ) ? $this->
substPaths( $paths, $backend ) :
null;
717 $backend->clearCache( $realPaths );
722 $realPaths = $this->
substPaths( $paths, $this->backends[$this->readIndex] );
728 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
730 return $this->backends[$index]->preloadFileStat( $realParams );
734 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
737 $paths = $this->backends[
$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );