12use InvalidArgumentException;
14use Shellbox\Command\BoxedCommand;
64 parent::__construct( $config );
65 $this->asyncWrites = isset( $config[
'replication'] ) && $config[
'replication'] ===
'async';
69 foreach ( $config[
'backends'] as $index => $beConfig ) {
70 $name = $beConfig[
'name'];
71 if ( isset( $namesUsed[
$name] ) ) {
72 throw new LogicException(
"Two or more backends defined with the name $name." );
74 $namesUsed[
$name] = 1;
76 unset( $beConfig[
'readOnly'] );
77 unset( $beConfig[
'lockManager'] );
80 if ( !empty( $beConfig[
'isMultiMaster'] ) ) {
81 if ( $this->masterIndex >= 0 ) {
82 throw new LogicException(
'More than one master backend defined.' );
84 $this->masterIndex = $index;
86 if ( !empty( $beConfig[
'readAffinity'] ) ) {
87 $this->readIndex = $index;
90 if ( !isset( $beConfig[
'class'] ) ) {
91 throw new InvalidArgumentException(
'No class given for a backend config.' );
93 $class = $beConfig[
'class'];
94 $this->backends[$index] =
new $class( $beConfig );
96 if ( $this->masterIndex < 0 ) {
97 throw new LogicException(
'No master backend defined.' );
99 if ( $this->readIndex < 0 ) {
113 if ( empty( $opts[
'nonLocking'] ) ) {
115 if ( !$status->isOK() ) {
123 $opts[
'preserveCache'] =
true;
126 $masterStatus = $mbe->doOperations( $realOps, $opts );
127 $status->merge( $masterStatus );
131 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
132 foreach ( $this->backends as $index => $backend ) {
133 if ( $index === $this->masterIndex ) {
143 $backend, $realOps, $opts, $scopeLock, $relevantPaths, $fname
145 $this->logger->debug(
146 "$fname: '{$backend->getName()}' async replication; paths: " .
147 implode(
', ', $relevantPaths )
149 $backend->doOperations( $realOps, $opts );
153 $this->logger->debug(
154 "$fname: '{$backend->getName()}' sync replication; paths: " .
155 implode(
', ', $relevantPaths )
157 $status->merge( $backend->doOperations( $realOps, $opts ) );
164 $status->success = $masterStatus->success;
165 $status->successCount = $masterStatus->successCount;
166 $status->failCount = $masterStatus->failCount;
185 foreach ( $paths as
$path ) {
186 $params = [
'src' =>
$path,
'latest' => true ];
189 $masterParams = $this->
substOpPaths( $params, $masterBackend );
190 $masterPath = $masterParams[
'src'];
191 $masterStat = $masterBackend->getFileStat( $masterParams );
192 if ( $masterStat === self::STAT_ERROR ) {
193 $status->fatal(
'backend-fail-stat',
$path );
194 $this->logger->error(
"$fname: file '$masterPath' is not available" );
197 $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
198 if ( ( $masterSha1 !==
false ) !== (
bool)$masterStat ) {
199 $status->fatal(
'backend-fail-hash',
$path );
200 $this->logger->error(
"$fname: file '$masterPath' hash does not match stat" );
205 foreach ( $this->backends as $index => $cloneBackend ) {
206 if ( $index === $this->masterIndex ) {
211 $cloneParams = $this->
substOpPaths( $params, $cloneBackend );
212 $clonePath = $cloneParams[
'src'];
213 $cloneStat = $cloneBackend->getFileStat( $cloneParams );
214 if ( $cloneStat === self::STAT_ERROR ) {
215 $status->fatal(
'backend-fail-stat',
$path );
216 $this->logger->error(
"$fname: file '$clonePath' is not available" );
219 $cloneSha1 = $cloneBackend->getFileSha1Base36( $cloneParams );
220 if ( ( $cloneSha1 !==
false ) !== (
bool)$cloneStat ) {
221 $status->fatal(
'backend-fail-hash',
$path );
222 $this->logger->error(
"$fname: file '$clonePath' hash does not match stat" );
226 if ( $masterSha1 === $cloneSha1 ) {
228 $this->logger->debug(
"$fname: file '$clonePath' matches '$masterPath'" );
229 } elseif ( $masterSha1 !==
false ) {
232 $resyncMode ===
'conservative' &&
235 $cloneStat[
'mtime'] > $masterStat[
'mtime']
238 $status->fatal(
'backend-fail-synced',
$path );
241 $fsFile = $masterBackend->getLocalReference( $masterParams );
242 $status->merge( $cloneBackend->quickStore( [
247 } elseif ( $masterStat ===
false ) {
249 if ( $resyncMode ===
'conservative' ) {
251 $status->fatal(
'backend-fail-synced',
$path );
252 $this->logger->error(
"$fname: not allowed to delete file '$clonePath'" );
255 $status->merge( $cloneBackend->quickDelete( [
'src' => $clonePath ] ) );
261 if ( !$status->isOK() ) {
262 $this->logger->error(
"$fname: failed to resync: " . implode(
', ', $paths ) );
276 foreach ( $ops as $op ) {
277 if ( isset( $op[
'src'] ) ) {
278 $paths[] = $op[
'src'];
280 if ( isset( $op[
'srcs'] ) ) {
281 $paths = array_merge( $paths, $op[
'srcs'] );
283 if ( isset( $op[
'dst'] ) ) {
284 $paths[] = $op[
'dst'];
288 return array_values( array_unique( array_filter(
304 foreach ( $ops as $op ) {
306 foreach ( [
'src',
'srcs',
'dst',
'dir' ] as $par ) {
307 if ( isset( $newOp[$par] ) ) {
308 $newOp[$par] = $this->
substPaths( $newOp[$par], $backend );
339 '!^mwstore://' . preg_quote( $this->name,
'!' ) .
'/!',
340 StringUtils::escapeRegexReplacement(
"mwstore://{$backend->getName()}/" ),
354 '!^mwstore://' . preg_quote( $backend->
getName(),
'!' ) .
'/!',
355 StringUtils::escapeRegexReplacement(
"mwstore://{$this->name}/" ),
365 foreach ( $ops as $op ) {
366 if ( $op[
'op'] ===
'store' && !isset( $op[
'srcRef'] ) ) {
378 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
380 $status->merge( $masterStatus );
382 foreach ( $this->backends as $index => $backend ) {
383 if ( $index === $this->masterIndex ) {
390 static function () use ( $backend, $realOps ) {
391 $backend->doQuickOperations( $realOps );
395 $status->merge( $backend->doQuickOperations( $realOps ) );
401 $status->success = $masterStatus->success;
402 $status->successCount = $masterStatus->successCount;
403 $status->failCount = $masterStatus->failCount;
436 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
438 $status->merge( $masterStatus );
440 foreach ( $this->backends as $index => $backend ) {
441 if ( $index === $this->masterIndex ) {
446 if ( $this->asyncWrites ) {
448 static function () use ( $backend, $method, $realParams ) {
449 $backend->$method( $realParams );
453 $status->merge( $backend->$method( $realParams ) );
465 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
467 $status->merge( $this->backends[$index]->
concatenate( $realParams ) );
475 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
477 return $this->backends[$index]->fileExists( $realParams );
483 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
485 return $this->backends[$index]->getFileTimestamp( $realParams );
491 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
493 return $this->backends[$index]->getFileSize( $realParams );
499 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
501 return $this->backends[$index]->getFileStat( $realParams );
507 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
509 return $this->backends[$index]->getFileXAttributes( $realParams );
515 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
517 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
520 foreach ( $contentsM as
$path => $data ) {
530 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
532 return $this->backends[$index]->getFileSha1Base36( $realParams );
538 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
540 return $this->backends[$index]->getFileProps( $realParams );
546 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
548 return $this->backends[$index]->streamFile( $realParams );
554 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
556 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
559 foreach ( $fsFilesM as
$path => $fsFile ) {
569 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
571 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
574 foreach ( $tempFilesM as
$path => $tempFile ) {
584 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
586 return $this->backends[$index]->getFileHttpUrl( $realParams );
594 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
595 return $this->backends[$index]->addShellboxInputFile( $command, $boxedName, $realParams );
600 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
607 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
614 if ( isset( $params[
'forWrite'] ) && $params[
'forWrite'] ) {
615 return $this->getFileListForWrite( $params );
618 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
622 private function getFileListForWrite( array $params ): array {
627 foreach ( $this->backends as $backend ) {
629 $iterator = $backend->getFileList( $realParams );
630 if ( $iterator !==
null ) {
631 foreach ( $iterator as $file ) {
637 return array_unique( $files );
642 return $this->backends[$this->masterIndex]->getFeatures();
647 foreach ( $this->backends as $backend ) {
648 $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) :
null;
649 $backend->clearCache( $realPaths );
655 $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] );
656 $this->backends[$this->readIndex]->preloadCache( $realPaths );
661 $index = $this->getReadIndexFromParams( $params );
662 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
664 return $this->backends[$index]->preloadFileStat( $realParams );
669 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
670 $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
672 $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
675 LockManager::LOCK_UW => $this->unsubstPaths(
676 $paths[LockManager::LOCK_UW],
677 $this->backends[$this->masterIndex]
679 LockManager::LOCK_EX => $this->unsubstPaths(
680 $paths[LockManager::LOCK_EX],
681 $this->backends[$this->masterIndex]
686 return $this->getScopedFileLocks( $pbPaths,
'mixed', $status );
694 return !empty( $params[
'latest'] ) ? $this->masterIndex : $this->readIndex;
699class_alias( FileBackendMultiWrite::class,
'FileBackendMultiWrite' );
Generic operation result class Has warning/error list, boolean status and arbitrary value.