MediaWiki master
FileBackendMultiWrite.php
Go to the documentation of this file.
1<?php
10namespace Wikimedia\FileBackend;
11
12use InvalidArgumentException;
13use LogicException;
14use Shellbox\Command\BoxedCommand;
15use StatusValue;
18
39 protected $backends = [];
40
42 protected $masterIndex = -1;
44 protected $readIndex = -1;
45
47 protected $asyncWrites = false;
48
63 public function __construct( array $config ) {
64 parent::__construct( $config );
65 $this->asyncWrites = isset( $config['replication'] ) && $config['replication'] === 'async';
66 // Construct backends here rather than via registration
67 // to keep these backends hidden from outside the proxy.
68 $namesUsed = [];
69 foreach ( $config['backends'] as $index => $beConfig ) {
70 $name = $beConfig['name'];
71 if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
72 throw new LogicException( "Two or more backends defined with the name $name." );
73 }
74 $namesUsed[$name] = 1;
75 // Alter certain sub-backend settings
76 unset( $beConfig['readOnly'] ); // use proxy backend setting
77 unset( $beConfig['lockManager'] ); // lock under proxy backend
78 $beConfig['domainId'] = $this->domainId; // use the proxy backend wiki ID
79 $beConfig['logger'] = $this->logger; // use the proxy backend logger
80 if ( !empty( $beConfig['isMultiMaster'] ) ) {
81 if ( $this->masterIndex >= 0 ) {
82 throw new LogicException( 'More than one master backend defined.' );
83 }
84 $this->masterIndex = $index; // this is the "master"
85 }
86 if ( !empty( $beConfig['readAffinity'] ) ) {
87 $this->readIndex = $index; // prefer this for reads
88 }
89 // Create sub-backend object
90 if ( !isset( $beConfig['class'] ) ) {
91 throw new InvalidArgumentException( 'No class given for a backend config.' );
92 }
93 $class = $beConfig['class'];
94 $this->backends[$index] = new $class( $beConfig );
95 }
96 if ( $this->masterIndex < 0 ) { // need backends and must have a master
97 throw new LogicException( 'No master backend defined.' );
98 }
99 if ( $this->readIndex < 0 ) {
100 $this->readIndex = $this->masterIndex; // default
101 }
102 }
103
105 final protected function doOperationsInternal( array $ops, array $opts ) {
106 $status = $this->newStatus();
107
108 $fname = __METHOD__;
109 $mbe = $this->backends[$this->masterIndex]; // convenience
110
111 // Acquire any locks as needed
112 $scopeLock = null;
113 if ( empty( $opts['nonLocking'] ) ) {
114 $scopeLock = $this->getScopedLocksForOps( $ops, $status );
115 if ( !$status->isOK() ) {
116 return $status; // abort
117 }
118 }
119 // Get the list of paths to read/write
120 $relevantPaths = $this->fileStoragePathsForOps( $ops );
121 // Clear any cache entries (after locks acquired)
122 $this->clearCache( $relevantPaths );
123 $opts['preserveCache'] = true;
124 // Actually attempt the operation batch on the master backend
125 $realOps = $this->substOpBatchPaths( $ops, $mbe );
126 $masterStatus = $mbe->doOperations( $realOps, $opts );
127 $status->merge( $masterStatus );
128 // Propagate the operations to the clone backends if there were no unexpected errors
129 // and everything didn't fail due to predicted errors. If $ops only had one operation,
130 // this might avoid backend sync inconsistencies.
131 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
132 foreach ( $this->backends as $index => $backend ) {
133 if ( $index === $this->masterIndex ) {
134 continue; // done already
135 }
136
137 $realOps = $this->substOpBatchPaths( $ops, $backend );
138 if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
139 // Bind $scopeLock to the callback to preserve locks
140 $this->callNowOrLater(
141 function () use (
142 // @phan-suppress-next-line PhanUnusedClosureUseVariable
143 $backend, $realOps, $opts, $scopeLock, $relevantPaths, $fname
144 ) {
145 $this->logger->debug(
146 "$fname: '{$backend->getName()}' async replication; paths: " .
147 implode( ', ', $relevantPaths )
148 );
149 $backend->doOperations( $realOps, $opts );
150 }
151 );
152 } else {
153 $this->logger->debug(
154 "$fname: '{$backend->getName()}' sync replication; paths: " .
155 implode( ', ', $relevantPaths )
156 );
157 $status->merge( $backend->doOperations( $realOps, $opts ) );
158 }
159 }
160 }
161 // Make 'success', 'successCount', and 'failCount' fields reflect
162 // the overall operation, rather than all the batches for each backend.
163 // Do this by only using success values from the master backend's batch.
164 $status->success = $masterStatus->success;
165 $status->successCount = $masterStatus->successCount;
166 $status->failCount = $masterStatus->failCount;
167
168 return $status;
169 }
170
181 public function resyncFiles( array $paths, $resyncMode = true ) {
182 $status = $this->newStatus();
183
184 $fname = __METHOD__;
185 foreach ( $paths as $path ) {
186 $params = [ 'src' => $path, 'latest' => true ];
187 // Get the state of the file on the master backend
188 $masterBackend = $this->backends[$this->masterIndex];
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" );
195 continue;
196 }
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" );
201 continue;
202 }
203
204 // Check of all clone backends agree with the master...
205 foreach ( $this->backends as $index => $cloneBackend ) {
206 if ( $index === $this->masterIndex ) {
207 continue; // master
208 }
209
210 // Get the state of the file on the clone backend
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" );
217 continue;
218 }
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" );
223 continue;
224 }
225
226 if ( $masterSha1 === $cloneSha1 ) {
227 // File is either the same in both backends or absent from both backends
228 $this->logger->debug( "$fname: file '$clonePath' matches '$masterPath'" );
229 } elseif ( $masterSha1 !== false ) {
230 // File is either missing from or different in the clone backend
231 if (
232 $resyncMode === 'conservative' &&
233 $cloneStat &&
234 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
235 $cloneStat['mtime'] > $masterStat['mtime']
236 ) {
237 // Do not replace files with older ones; reduces the risk of data loss
238 $status->fatal( 'backend-fail-synced', $path );
239 } else {
240 // Copy the master backend file to the clone backend in overwrite mode
241 $fsFile = $masterBackend->getLocalReference( $masterParams );
242 $status->merge( $cloneBackend->quickStore( [
243 'src' => $fsFile,
244 'dst' => $clonePath
245 ] ) );
246 }
247 } elseif ( $masterStat === false ) {
248 // Stray file exists in the clone backend
249 if ( $resyncMode === 'conservative' ) {
250 // Do not delete stray files; reduces the risk of data loss
251 $status->fatal( 'backend-fail-synced', $path );
252 $this->logger->error( "$fname: not allowed to delete file '$clonePath'" );
253 } else {
254 // Delete the stay file from the clone backend
255 $status->merge( $cloneBackend->quickDelete( [ 'src' => $clonePath ] ) );
256 }
257 }
258 }
259 }
260
261 if ( !$status->isOK() ) {
262 $this->logger->error( "$fname: failed to resync: " . implode( ', ', $paths ) );
263 }
264
265 return $status;
266 }
267
274 protected function fileStoragePathsForOps( array $ops ) {
275 $paths = [];
276 foreach ( $ops as $op ) {
277 if ( isset( $op['src'] ) ) {
278 $paths[] = $op['src'];
279 }
280 if ( isset( $op['srcs'] ) ) {
281 $paths = array_merge( $paths, $op['srcs'] );
282 }
283 if ( isset( $op['dst'] ) ) {
284 $paths[] = $op['dst'];
285 }
286 }
287
288 return array_values( array_unique( array_filter(
289 $paths,
291 ) ) );
292 }
293
302 protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
303 $newOps = []; // operations
304 foreach ( $ops as $op ) {
305 $newOp = $op; // operation
306 foreach ( [ 'src', 'srcs', 'dst', 'dir' ] as $par ) {
307 if ( isset( $newOp[$par] ) ) { // string or array
308 $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
309 }
310 }
311 $newOps[] = $newOp;
312 }
313
314 return $newOps;
315 }
316
324 protected function substOpPaths( array $ops, FileBackendStore $backend ) {
325 $newOps = $this->substOpBatchPaths( [ $ops ], $backend );
326
327 return $newOps[0];
328 }
329
337 protected function substPaths( $paths, FileBackendStore $backend ) {
338 return preg_replace(
339 '!^mwstore://' . preg_quote( $this->name, '!' ) . '/!',
340 StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
341 $paths // string or array
342 );
343 }
344
352 protected function unsubstPaths( $paths, FileBackendStore $backend ) {
353 return preg_replace(
354 '!^mwstore://' . preg_quote( $backend->getName(), '!' ) . '/!',
355 StringUtils::escapeRegexReplacement( "mwstore://{$this->name}/" ),
356 $paths // string or array
357 );
358 }
359
364 protected function hasVolatileSources( array $ops ) {
365 foreach ( $ops as $op ) {
366 if ( $op['op'] === 'store' && !isset( $op['srcRef'] ) ) {
367 return true; // source file might be deleted anytime after do*Operations()
368 }
369 }
370
371 return false;
372 }
373
375 protected function doQuickOperationsInternal( array $ops, array $opts ) {
376 $status = $this->newStatus();
377 // Do the operations on the master backend; setting StatusValue fields
378 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
379 $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
380 $status->merge( $masterStatus );
381 // Propagate the operations to the clone backends...
382 foreach ( $this->backends as $index => $backend ) {
383 if ( $index === $this->masterIndex ) {
384 continue; // done already
385 }
386
387 $realOps = $this->substOpBatchPaths( $ops, $backend );
388 if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
389 $this->callNowOrLater(
390 static function () use ( $backend, $realOps ) {
391 $backend->doQuickOperations( $realOps );
392 }
393 );
394 } else {
395 $status->merge( $backend->doQuickOperations( $realOps ) );
396 }
397 }
398 // Make 'success', 'successCount', and 'failCount' fields reflect
399 // the overall operation, rather than all the batches for each backend.
400 // Do this by only using success values from the master backend's batch.
401 $status->success = $masterStatus->success;
402 $status->successCount = $masterStatus->successCount;
403 $status->failCount = $masterStatus->failCount;
404
405 return $status;
406 }
407
409 protected function doPrepare( array $params ) {
410 return $this->doDirectoryOp( 'prepare', $params );
411 }
412
414 protected function doSecure( array $params ) {
415 return $this->doDirectoryOp( 'secure', $params );
416 }
417
419 protected function doPublish( array $params ) {
420 return $this->doDirectoryOp( 'publish', $params );
421 }
422
424 protected function doClean( array $params ) {
425 return $this->doDirectoryOp( 'clean', $params );
426 }
427
433 protected function doDirectoryOp( $method, array $params ) {
434 $status = $this->newStatus();
435
436 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
437 $masterStatus = $this->backends[$this->masterIndex]->$method( $realParams );
438 $status->merge( $masterStatus );
439
440 foreach ( $this->backends as $index => $backend ) {
441 if ( $index === $this->masterIndex ) {
442 continue; // already done
443 }
444
445 $realParams = $this->substOpPaths( $params, $backend );
446 if ( $this->asyncWrites ) {
447 $this->callNowOrLater(
448 static function () use ( $backend, $method, $realParams ) {
449 $backend->$method( $realParams );
450 }
451 );
452 } else {
453 $status->merge( $backend->$method( $realParams ) );
454 }
455 }
456
457 return $status;
458 }
459
461 public function concatenate( array $params ) {
462 $status = $this->newStatus();
463 // We are writing to an FS file, so we don't need to do this per-backend
464 $index = $this->getReadIndexFromParams( $params );
465 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
466
467 $status->merge( $this->backends[$index]->concatenate( $realParams ) );
468
469 return $status;
470 }
471
473 public function fileExists( array $params ) {
474 $index = $this->getReadIndexFromParams( $params );
475 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
476
477 return $this->backends[$index]->fileExists( $realParams );
478 }
479
481 public function getFileTimestamp( array $params ) {
482 $index = $this->getReadIndexFromParams( $params );
483 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
484
485 return $this->backends[$index]->getFileTimestamp( $realParams );
486 }
487
489 public function getFileSize( array $params ) {
490 $index = $this->getReadIndexFromParams( $params );
491 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
492
493 return $this->backends[$index]->getFileSize( $realParams );
494 }
495
497 public function getFileStat( array $params ) {
498 $index = $this->getReadIndexFromParams( $params );
499 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
500
501 return $this->backends[$index]->getFileStat( $realParams );
502 }
503
505 public function getFileXAttributes( array $params ) {
506 $index = $this->getReadIndexFromParams( $params );
507 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
508
509 return $this->backends[$index]->getFileXAttributes( $realParams );
510 }
511
513 public function getFileContentsMulti( array $params ) {
514 $index = $this->getReadIndexFromParams( $params );
515 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
516
517 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
518
519 $contents = []; // (path => FSFile) mapping using the proxy backend's name
520 foreach ( $contentsM as $path => $data ) {
521 $contents[$this->unsubstPaths( $path, $this->backends[$index] )] = $data;
522 }
523
524 return $contents;
525 }
526
528 public function getFileSha1Base36( array $params ) {
529 $index = $this->getReadIndexFromParams( $params );
530 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
531
532 return $this->backends[$index]->getFileSha1Base36( $realParams );
533 }
534
536 public function getFileProps( array $params ) {
537 $index = $this->getReadIndexFromParams( $params );
538 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
539
540 return $this->backends[$index]->getFileProps( $realParams );
541 }
542
544 public function streamFile( array $params ) {
545 $index = $this->getReadIndexFromParams( $params );
546 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
547
548 return $this->backends[$index]->streamFile( $realParams );
549 }
550
552 public function getLocalReferenceMulti( array $params ) {
553 $index = $this->getReadIndexFromParams( $params );
554 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
555
556 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
557
558 $fsFiles = []; // (path => FSFile) mapping using the proxy backend's name
559 foreach ( $fsFilesM as $path => $fsFile ) {
560 $fsFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $fsFile;
561 }
562
563 return $fsFiles;
564 }
565
567 public function getLocalCopyMulti( array $params ) {
568 $index = $this->getReadIndexFromParams( $params );
569 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
570
571 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
572
573 $tempFiles = []; // (path => TempFSFile) mapping using the proxy backend's name
574 foreach ( $tempFilesM as $path => $tempFile ) {
575 $tempFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $tempFile;
576 }
577
578 return $tempFiles;
579 }
580
582 public function getFileHttpUrl( array $params ) {
583 $index = $this->getReadIndexFromParams( $params );
584 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
585
586 return $this->backends[$index]->getFileHttpUrl( $realParams );
587 }
588
590 public function addShellboxInputFile( BoxedCommand $command, string $boxedName,
591 array $params
592 ) {
593 $index = $this->getReadIndexFromParams( $params );
594 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
595 return $this->backends[$index]->addShellboxInputFile( $command, $boxedName, $realParams );
596 }
597
599 public function directoryExists( array $params ) {
600 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
601
602 return $this->backends[$this->masterIndex]->directoryExists( $realParams );
603 }
604
606 public function getDirectoryList( array $params ) {
607 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
608
609 return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
610 }
611
613 public function getFileList( array $params ) {
614 if ( isset( $params['forWrite'] ) && $params['forWrite'] ) {
615 return $this->getFileListForWrite( $params );
616 }
617
618 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
619 return $this->backends[$this->masterIndex]->getFileList( $realParams );
620 }
621
622 private function getFileListForWrite( array $params ): array {
623 $files = [];
624 // Get the list of thumbnails from all backends to allow
625 // deleting all of them. Otherwise, old thumbnails existing on
626 // one backend only won't get updated in reupload (T331138).
627 foreach ( $this->backends as $backend ) {
628 $realParams = $this->substOpPaths( $params, $backend );
629 $iterator = $backend->getFileList( $realParams );
630 if ( $iterator !== null ) {
631 foreach ( $iterator as $file ) {
632 $files[] = $file;
633 }
634 }
635 }
636
637 return array_unique( $files );
638 }
639
641 public function getFeatures() {
642 return $this->backends[$this->masterIndex]->getFeatures();
643 }
644
646 public function clearCache( ?array $paths = null ) {
647 foreach ( $this->backends as $backend ) {
648 $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
649 $backend->clearCache( $realPaths );
650 }
651 }
652
654 public function preloadCache( array $paths ) {
655 $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] );
656 $this->backends[$this->readIndex]->preloadCache( $realPaths );
657 }
658
660 public function preloadFileStat( array $params ) {
661 $index = $this->getReadIndexFromParams( $params );
662 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
663
664 return $this->backends[$index]->preloadFileStat( $realParams );
665 }
666
668 public function getScopedLocksForOps( array $ops, StatusValue $status ) {
669 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
670 $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
671 // Get the paths to lock from the master backend
672 $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
673 // Get the paths under the proxy backend's name
674 $pbPaths = [
675 LockManager::LOCK_UW => $this->unsubstPaths(
676 $paths[LockManager::LOCK_UW],
677 $this->backends[$this->masterIndex]
678 ),
679 LockManager::LOCK_EX => $this->unsubstPaths(
680 $paths[LockManager::LOCK_EX],
681 $this->backends[$this->masterIndex]
682 )
683 ];
684
685 // Actually acquire the locks
686 return $this->getScopedFileLocks( $pbPaths, 'mixed', $status );
687 }
688
693 protected function getReadIndexFromParams( array $params ) {
694 return !empty( $params['latest'] ) ? $this->masterIndex : $this->readIndex;
695 }
696}
697
699class_alias( FileBackendMultiWrite::class, 'FileBackendMultiWrite' );
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Proxy backend that mirrors writes to several internal backends.
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.Normally, FileBackend::doOperation...
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.If the file does not exist,...
streamFile(array $params)
Stream the content of the file at a storage path in the backend.If the file does not exists,...
getFileSha1Base36(array $params)
Get a SHA-1 hash of the content of the file at a storage path in the backend.FileBackend::SHA1_FAILst...
doPublish(array $params)
FileBackend::publish() StatusValue
fileStoragePathsForOps(array $ops)
Get a list of file storage paths to read or write for a list of operations.
substPaths( $paths, FileBackendStore $backend)
Substitute the backend of storage paths with an internal backend's name.
doClean(array $params)
FileBackend::clean() StatusValue
__construct(array $config)
Construct a proxy backend that consists of several internal backends.
directoryExists(array $params)
Check if a directory exists at a given storage path.For backends using key/value stores,...
FileBackendStore[] $backends
Prioritized list of FileBackendStore objects.
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.This should be used wh...
getFileProps(array $params)
Get the properties of the content of the file at a storage path in the backend.This gives the result ...
doSecure(array $params)
FileBackend::secure() StatusValue
int $readIndex
Index of read affinity backend.
unsubstPaths( $paths, FileBackendStore $backend)
Substitute the backend of internal storage paths with the proxy backend's name.
getFeatures()
Get the a bitfield of extra features supported by the backend medium.to overrideint Bitfield of FileB...
fileExists(array $params)
Check if a file exists at a storage path in the backend.This returns false if only a directory exists...
doQuickOperationsInternal(array $ops, array $opts)
FileBackend::doQuickOperations() StatusValue 1.20
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.If the file does not exist,...
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.The URL may be pre-authenti...
substOpBatchPaths(array $ops, FileBackendStore $backend)
Substitute the backend name in storage path parameters for a set of operations with that of a given i...
concatenate(array $params)
Concatenate a list of storage files into a single file system file.The target path should refer to a ...
getLocalReferenceMulti(array $params)
Like getLocalReference() except it takes an array of storage paths and yields an order-preserved map ...
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.This should be used when ...
getLocalCopyMulti(array $params)
Like getLocalCopy() except it takes an array of storage paths and yields an order preserved-map of st...
doOperationsInternal(array $ops, array $opts)
FileBackend::doOperations() StatusValue
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.FileBackend::SIZE_FAILint|false File ...
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.If the directory is of the form "mw...
clearCache(?array $paths=null)
Invalidate any in-process file stat and property cache.If $paths is given, then only the cache for th...
resyncFiles(array $paths, $resyncMode=true)
Check that a set of files are consistent across all internal backends and re-synchronize those files ...
getFileTimestamp(array $params)
Get the last-modified timestamp of the file at a storage path.FileBackend::TIMESTAMP_FAILstring|false...
substOpPaths(array $ops, FileBackendStore $backend)
Same as substOpBatchPaths() but for a single operation.
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.If the directory is of the form "m...
getFileContentsMulti(array $params)
Like getFileContents() except it takes an array of storage paths and returns an order preserved map o...
addShellboxInputFile(BoxedCommand $command, string $boxedName, array $params)
Add a file to a Shellbox command as an input file.StatusValue 1.43
doPrepare(array $params)
FileBackend::prepare() StatusValue Good status without value for success, fatal otherwise.
Base class for all backends using particular storage medium.
Base class for all file backend classes (including multi-write backends).
string $name
Unique backend name.
getName()
Get the unique backend name.
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
newStatus( $message=null,... $params)
Yields the result of the status wrapper callback on either:
string $domainId
Unique domain name.
Resource locking handling.
A collection of static methods to play with strings.