MediaWiki REL1_28
FileBackendMultiWrite.php
Go to the documentation of this file.
1<?php
46 protected $backends = [];
47
49 protected $masterIndex = -1;
51 protected $readIndex = -1;
52
54 protected $syncChecks = 0;
56 protected $autoResync = false;
57
59 protected $asyncWrites = false;
60
61 /* Possible internal backend consistency checks */
62 const CHECK_SIZE = 1;
63 const CHECK_TIME = 2;
64 const CHECK_SHA1 = 4;
65
94 public function __construct( array $config ) {
95 parent::__construct( $config );
96 $this->syncChecks = isset( $config['syncChecks'] )
97 ? $config['syncChecks']
99 $this->autoResync = isset( $config['autoResync'] )
100 ? $config['autoResync']
101 : false;
102 $this->asyncWrites = isset( $config['replication'] ) && $config['replication'] === 'async';
103 // Construct backends here rather than via registration
104 // to keep these backends hidden from outside the proxy.
105 $namesUsed = [];
106 foreach ( $config['backends'] as $index => $config ) {
107 $name = $config['name'];
108 if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
109 throw new LogicException( "Two or more backends defined with the name $name." );
110 }
111 $namesUsed[$name] = 1;
112 // Alter certain sub-backend settings for sanity
113 unset( $config['readOnly'] ); // use proxy backend setting
114 unset( $config['fileJournal'] ); // use proxy backend journal
115 unset( $config['lockManager'] ); // lock under proxy backend
116 $config['domainId'] = $this->domainId; // use the proxy backend wiki ID
117 if ( !empty( $config['isMultiMaster'] ) ) {
118 if ( $this->masterIndex >= 0 ) {
119 throw new LogicException( 'More than one master backend defined.' );
120 }
121 $this->masterIndex = $index; // this is the "master"
122 $config['fileJournal'] = $this->fileJournal; // log under proxy backend
123 }
124 if ( !empty( $config['readAffinity'] ) ) {
125 $this->readIndex = $index; // prefer this for reads
126 }
127 // Create sub-backend object
128 if ( !isset( $config['class'] ) ) {
129 throw new InvalidArgumentException( 'No class given for a backend config.' );
130 }
131 $class = $config['class'];
132 $this->backends[$index] = new $class( $config );
133 }
134 if ( $this->masterIndex < 0 ) { // need backends and must have a master
135 throw new LogicException( 'No master backend defined.' );
136 }
137 if ( $this->readIndex < 0 ) {
138 $this->readIndex = $this->masterIndex; // default
139 }
140 }
141
142 final protected function doOperationsInternal( array $ops, array $opts ) {
143 $status = $this->newStatus();
144
145 $mbe = $this->backends[$this->masterIndex]; // convenience
146
147 // Try to lock those files for the scope of this function...
148 $scopeLock = null;
149 if ( empty( $opts['nonLocking'] ) ) {
150 // Try to lock those files for the scope of this function...
152 $scopeLock = $this->getScopedLocksForOps( $ops, $status );
153 if ( !$status->isOK() ) {
154 return $status; // abort
155 }
156 }
157 // Clear any cache entries (after locks acquired)
158 $this->clearCache();
159 $opts['preserveCache'] = true; // only locked files are cached
160 // Get the list of paths to read/write...
161 $relevantPaths = $this->fileStoragePathsForOps( $ops );
162 // Check if the paths are valid and accessible on all backends...
163 $status->merge( $this->accessibilityCheck( $relevantPaths ) );
164 if ( !$status->isOK() ) {
165 return $status; // abort
166 }
167 // Do a consistency check to see if the backends are consistent...
168 $syncStatus = $this->consistencyCheck( $relevantPaths );
169 if ( !$syncStatus->isOK() ) {
170 wfDebugLog( 'FileOperation', get_class( $this ) .
171 " failed sync check: " . FormatJson::encode( $relevantPaths ) );
172 // Try to resync the clone backends to the master on the spot...
173 if ( $this->autoResync === false
174 || !$this->resyncFiles( $relevantPaths, $this->autoResync )->isOK()
175 ) {
176 $status->merge( $syncStatus );
177
178 return $status; // abort
179 }
180 }
181 // Actually attempt the operation batch on the master backend...
182 $realOps = $this->substOpBatchPaths( $ops, $mbe );
183 $masterStatus = $mbe->doOperations( $realOps, $opts );
184 $status->merge( $masterStatus );
185 // Propagate the operations to the clone backends if there were no unexpected errors
186 // and if there were either no expected errors or if the 'force' option was used.
187 // However, if nothing succeeded at all, then don't replicate any of the operations.
188 // If $ops only had one operation, this might avoid backend sync inconsistencies.
189 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
190 foreach ( $this->backends as $index => $backend ) {
191 if ( $index === $this->masterIndex ) {
192 continue; // done already
193 }
194
195 $realOps = $this->substOpBatchPaths( $ops, $backend );
196 if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
197 // Bind $scopeLock to the callback to preserve locks
198 DeferredUpdates::addCallableUpdate(
199 function() use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
200 wfDebugLog( 'FileOperationReplication',
201 "'{$backend->getName()}' async replication; paths: " .
202 FormatJson::encode( $relevantPaths ) );
203 $backend->doOperations( $realOps, $opts );
204 }
205 );
206 } else {
207 wfDebugLog( 'FileOperationReplication',
208 "'{$backend->getName()}' sync replication; paths: " .
209 FormatJson::encode( $relevantPaths ) );
210 $status->merge( $backend->doOperations( $realOps, $opts ) );
211 }
212 }
213 }
214 // Make 'success', 'successCount', and 'failCount' fields reflect
215 // the overall operation, rather than all the batches for each backend.
216 // Do this by only using success values from the master backend's batch.
217 $status->success = $masterStatus->success;
218 $status->successCount = $masterStatus->successCount;
219 $status->failCount = $masterStatus->failCount;
220
221 return $status;
222 }
223
230 public function consistencyCheck( array $paths ) {
231 $status = $this->newStatus();
232 if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
233 return $status; // skip checks
234 }
235
236 // Preload all of the stat info in as few round trips as possible...
237 foreach ( $this->backends as $backend ) {
238 $realPaths = $this->substPaths( $paths, $backend );
239 $backend->preloadFileStat( [ 'srcs' => $realPaths, 'latest' => true ] );
240 }
241
242 $mBackend = $this->backends[$this->masterIndex];
243 foreach ( $paths as $path ) {
244 $params = [ 'src' => $path, 'latest' => true ];
245 $mParams = $this->substOpPaths( $params, $mBackend );
246 // Stat the file on the 'master' backend
247 $mStat = $mBackend->getFileStat( $mParams );
248 if ( $this->syncChecks & self::CHECK_SHA1 ) {
249 $mSha1 = $mBackend->getFileSha1Base36( $mParams );
250 } else {
251 $mSha1 = false;
252 }
253 // Check if all clone backends agree with the master...
254 foreach ( $this->backends as $index => $cBackend ) {
255 if ( $index === $this->masterIndex ) {
256 continue; // master
257 }
258 $cParams = $this->substOpPaths( $params, $cBackend );
259 $cStat = $cBackend->getFileStat( $cParams );
260 if ( $mStat ) { // file is in master
261 if ( !$cStat ) { // file should exist
262 $status->fatal( 'backend-fail-synced', $path );
263 continue;
264 }
265 if ( $this->syncChecks & self::CHECK_SIZE ) {
266 if ( $cStat['size'] != $mStat['size'] ) { // wrong size
267 $status->fatal( 'backend-fail-synced', $path );
268 continue;
269 }
270 }
271 if ( $this->syncChecks & self::CHECK_TIME ) {
272 $mTs = wfTimestamp( TS_UNIX, $mStat['mtime'] );
273 $cTs = wfTimestamp( TS_UNIX, $cStat['mtime'] );
274 if ( abs( $mTs - $cTs ) > 30 ) { // outdated file somewhere
275 $status->fatal( 'backend-fail-synced', $path );
276 continue;
277 }
278 }
279 if ( $this->syncChecks & self::CHECK_SHA1 ) {
280 if ( $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) { // wrong SHA1
281 $status->fatal( 'backend-fail-synced', $path );
282 continue;
283 }
284 }
285 } else { // file is not in master
286 if ( $cStat ) { // file should not exist
287 $status->fatal( 'backend-fail-synced', $path );
288 }
289 }
290 }
291 }
292
293 return $status;
294 }
295
302 public function accessibilityCheck( array $paths ) {
303 $status = $this->newStatus();
304 if ( count( $this->backends ) <= 1 ) {
305 return $status; // skip checks
306 }
307
308 foreach ( $paths as $path ) {
309 foreach ( $this->backends as $backend ) {
310 $realPath = $this->substPaths( $path, $backend );
311 if ( !$backend->isPathUsableInternal( $realPath ) ) {
312 $status->fatal( 'backend-fail-usable', $path );
313 }
314 }
315 }
316
317 return $status;
318 }
319
328 public function resyncFiles( array $paths, $resyncMode = true ) {
329 $status = $this->newStatus();
330
331 $mBackend = $this->backends[$this->masterIndex];
332 foreach ( $paths as $path ) {
333 $mPath = $this->substPaths( $path, $mBackend );
334 $mSha1 = $mBackend->getFileSha1Base36( [ 'src' => $mPath, 'latest' => true ] );
335 $mStat = $mBackend->getFileStat( [ 'src' => $mPath, 'latest' => true ] );
336 if ( $mStat === null || ( $mSha1 !== false && !$mStat ) ) { // sanity
337 $status->fatal( 'backend-fail-internal', $this->name );
338 wfDebugLog( 'FileOperation', __METHOD__
339 . ': File is not available on the master backend' );
340 continue; // file is not available on the master backend...
341 }
342 // Check of all clone backends agree with the master...
343 foreach ( $this->backends as $index => $cBackend ) {
344 if ( $index === $this->masterIndex ) {
345 continue; // master
346 }
347 $cPath = $this->substPaths( $path, $cBackend );
348 $cSha1 = $cBackend->getFileSha1Base36( [ 'src' => $cPath, 'latest' => true ] );
349 $cStat = $cBackend->getFileStat( [ 'src' => $cPath, 'latest' => true ] );
350 if ( $cStat === null || ( $cSha1 !== false && !$cStat ) ) { // sanity
351 $status->fatal( 'backend-fail-internal', $cBackend->getName() );
352 wfDebugLog( 'FileOperation', __METHOD__ .
353 ': File is not available on the clone backend' );
354 continue; // file is not available on the clone backend...
355 }
356 if ( $mSha1 === $cSha1 ) {
357 // already synced; nothing to do
358 } elseif ( $mSha1 !== false ) { // file is in master
359 if ( $resyncMode === 'conservative'
360 && $cStat && $cStat['mtime'] > $mStat['mtime']
361 ) {
362 $status->fatal( 'backend-fail-synced', $path );
363 continue; // don't rollback data
364 }
365 $fsFile = $mBackend->getLocalReference(
366 [ 'src' => $mPath, 'latest' => true ] );
367 $status->merge( $cBackend->quickStore(
368 [ 'src' => $fsFile->getPath(), 'dst' => $cPath ]
369 ) );
370 } elseif ( $mStat === false ) { // file is not in master
371 if ( $resyncMode === 'conservative' ) {
372 $status->fatal( 'backend-fail-synced', $path );
373 continue; // don't delete data
374 }
375 $status->merge( $cBackend->quickDelete( [ 'src' => $cPath ] ) );
376 }
377 }
378 }
379
380 if ( !$status->isOK() ) {
381 wfDebugLog( 'FileOperation', get_class( $this ) .
382 " failed to resync: " . FormatJson::encode( $paths ) );
383 }
384
385 return $status;
386 }
387
394 protected function fileStoragePathsForOps( array $ops ) {
395 $paths = [];
396 foreach ( $ops as $op ) {
397 if ( isset( $op['src'] ) ) {
398 // For things like copy/move/delete with "ignoreMissingSource" and there
399 // is no source file, nothing should happen and there should be no errors.
400 if ( empty( $op['ignoreMissingSource'] )
401 || $this->fileExists( [ 'src' => $op['src'] ] )
402 ) {
403 $paths[] = $op['src'];
404 }
405 }
406 if ( isset( $op['srcs'] ) ) {
407 $paths = array_merge( $paths, $op['srcs'] );
408 }
409 if ( isset( $op['dst'] ) ) {
410 $paths[] = $op['dst'];
411 }
412 }
413
414 return array_values( array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) ) );
415 }
416
425 protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
426 $newOps = []; // operations
427 foreach ( $ops as $op ) {
428 $newOp = $op; // operation
429 foreach ( [ 'src', 'srcs', 'dst', 'dir' ] as $par ) {
430 if ( isset( $newOp[$par] ) ) { // string or array
431 $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
432 }
433 }
434 $newOps[] = $newOp;
435 }
436
437 return $newOps;
438 }
439
447 protected function substOpPaths( array $ops, FileBackendStore $backend ) {
448 $newOps = $this->substOpBatchPaths( [ $ops ], $backend );
449
450 return $newOps[0];
451 }
452
460 protected function substPaths( $paths, FileBackendStore $backend ) {
461 return preg_replace(
462 '!^mwstore://' . preg_quote( $this->name, '!' ) . '/!',
463 StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
464 $paths // string or array
465 );
466 }
467
474 protected function unsubstPaths( $paths ) {
475 return preg_replace(
476 '!^mwstore://([^/]+)!',
477 StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ),
478 $paths // string or array
479 );
480 }
481
486 protected function hasVolatileSources( array $ops ) {
487 foreach ( $ops as $op ) {
488 if ( $op['op'] === 'store' && !isset( $op['srcRef'] ) ) {
489 return true; // source file might be deleted anytime after do*Operations()
490 }
491 }
492
493 return false;
494 }
495
496 protected function doQuickOperationsInternal( array $ops ) {
497 $status = $this->newStatus();
498 // Do the operations on the master backend; setting StatusValue fields...
499 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
500 $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
501 $status->merge( $masterStatus );
502 // Propagate the operations to the clone backends...
503 foreach ( $this->backends as $index => $backend ) {
504 if ( $index === $this->masterIndex ) {
505 continue; // done already
506 }
507
508 $realOps = $this->substOpBatchPaths( $ops, $backend );
509 if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
510 DeferredUpdates::addCallableUpdate(
511 function() use ( $backend, $realOps ) {
512 $backend->doQuickOperations( $realOps );
513 }
514 );
515 } else {
516 $status->merge( $backend->doQuickOperations( $realOps ) );
517 }
518 }
519 // Make 'success', 'successCount', and 'failCount' fields reflect
520 // the overall operation, rather than all the batches for each backend.
521 // Do this by only using success values from the master backend's batch.
522 $status->success = $masterStatus->success;
523 $status->successCount = $masterStatus->successCount;
524 $status->failCount = $masterStatus->failCount;
525
526 return $status;
527 }
528
529 protected function doPrepare( array $params ) {
530 return $this->doDirectoryOp( 'prepare', $params );
531 }
532
533 protected function doSecure( array $params ) {
534 return $this->doDirectoryOp( 'secure', $params );
535 }
536
537 protected function doPublish( array $params ) {
538 return $this->doDirectoryOp( 'publish', $params );
539 }
540
541 protected function doClean( array $params ) {
542 return $this->doDirectoryOp( 'clean', $params );
543 }
544
550 protected function doDirectoryOp( $method, array $params ) {
551 $status = $this->newStatus();
552
553 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
554 $masterStatus = $this->backends[$this->masterIndex]->$method( $realParams );
555 $status->merge( $masterStatus );
556
557 foreach ( $this->backends as $index => $backend ) {
558 if ( $index === $this->masterIndex ) {
559 continue; // already done
560 }
561
562 $realParams = $this->substOpPaths( $params, $backend );
563 if ( $this->asyncWrites ) {
564 DeferredUpdates::addCallableUpdate(
565 function() use ( $backend, $method, $realParams ) {
566 $backend->$method( $realParams );
567 }
568 );
569 } else {
570 $status->merge( $backend->$method( $realParams ) );
571 }
572 }
573
574 return $status;
575 }
576
577 public function concatenate( array $params ) {
578 $status = $this->newStatus();
579 // We are writing to an FS file, so we don't need to do this per-backend
580 $index = $this->getReadIndexFromParams( $params );
581 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
582
583 $status->merge( $this->backends[$index]->concatenate( $realParams ) );
584
585 return $status;
586 }
587
588 public function fileExists( array $params ) {
589 $index = $this->getReadIndexFromParams( $params );
590 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
591
592 return $this->backends[$index]->fileExists( $realParams );
593 }
594
595 public function getFileTimestamp( array $params ) {
596 $index = $this->getReadIndexFromParams( $params );
597 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
598
599 return $this->backends[$index]->getFileTimestamp( $realParams );
600 }
601
602 public function getFileSize( array $params ) {
603 $index = $this->getReadIndexFromParams( $params );
604 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
605
606 return $this->backends[$index]->getFileSize( $realParams );
607 }
608
609 public function getFileStat( array $params ) {
610 $index = $this->getReadIndexFromParams( $params );
611 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
612
613 return $this->backends[$index]->getFileStat( $realParams );
614 }
615
616 public function getFileXAttributes( array $params ) {
617 $index = $this->getReadIndexFromParams( $params );
618 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
619
620 return $this->backends[$index]->getFileXAttributes( $realParams );
621 }
622
623 public function getFileContentsMulti( array $params ) {
624 $index = $this->getReadIndexFromParams( $params );
625 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
626
627 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
628
629 $contents = []; // (path => FSFile) mapping using the proxy backend's name
630 foreach ( $contentsM as $path => $data ) {
631 $contents[$this->unsubstPaths( $path )] = $data;
632 }
633
634 return $contents;
635 }
636
637 public function getFileSha1Base36( array $params ) {
638 $index = $this->getReadIndexFromParams( $params );
639 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
640
641 return $this->backends[$index]->getFileSha1Base36( $realParams );
642 }
643
644 public function getFileProps( array $params ) {
645 $index = $this->getReadIndexFromParams( $params );
646 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
647
648 return $this->backends[$index]->getFileProps( $realParams );
649 }
650
651 public function streamFile( array $params ) {
652 $index = $this->getReadIndexFromParams( $params );
653 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
654
655 return $this->backends[$index]->streamFile( $realParams );
656 }
657
659 $index = $this->getReadIndexFromParams( $params );
660 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
661
662 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
663
664 $fsFiles = []; // (path => FSFile) mapping using the proxy backend's name
665 foreach ( $fsFilesM as $path => $fsFile ) {
666 $fsFiles[$this->unsubstPaths( $path )] = $fsFile;
667 }
668
669 return $fsFiles;
670 }
671
672 public function getLocalCopyMulti( array $params ) {
673 $index = $this->getReadIndexFromParams( $params );
674 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
675
676 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
677
678 $tempFiles = []; // (path => TempFSFile) mapping using the proxy backend's name
679 foreach ( $tempFilesM as $path => $tempFile ) {
680 $tempFiles[$this->unsubstPaths( $path )] = $tempFile;
681 }
682
683 return $tempFiles;
684 }
685
686 public function getFileHttpUrl( array $params ) {
687 $index = $this->getReadIndexFromParams( $params );
688 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
689
690 return $this->backends[$index]->getFileHttpUrl( $realParams );
691 }
692
693 public function directoryExists( array $params ) {
694 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
695
696 return $this->backends[$this->masterIndex]->directoryExists( $realParams );
697 }
698
699 public function getDirectoryList( array $params ) {
700 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
701
702 return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
703 }
704
705 public function getFileList( array $params ) {
706 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
707
708 return $this->backends[$this->masterIndex]->getFileList( $realParams );
709 }
710
711 public function getFeatures() {
712 return $this->backends[$this->masterIndex]->getFeatures();
713 }
714
715 public function clearCache( array $paths = null ) {
716 foreach ( $this->backends as $backend ) {
717 $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
718 $backend->clearCache( $realPaths );
719 }
720 }
721
722 public function preloadCache( array $paths ) {
723 $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] );
724 $this->backends[$this->readIndex]->preloadCache( $realPaths );
725 }
726
727 public function preloadFileStat( array $params ) {
728 $index = $this->getReadIndexFromParams( $params );
729 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
730
731 return $this->backends[$index]->preloadFileStat( $realParams );
732 }
733
734 public function getScopedLocksForOps( array $ops, StatusValue $status ) {
735 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
736 $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
737 // Get the paths to lock from the master backend
738 $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
739 // Get the paths under the proxy backend's name
740 $pbPaths = [
743 ];
744
745 // Actually acquire the locks
746 return $this->getScopedFileLocks( $pbPaths, 'mixed', $status );
747 }
748
753 protected function getReadIndexFromParams( array $params ) {
754 return !empty( $params['latest'] ) ? $this->masterIndex : $this->readIndex;
755 }
756}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Proxy backend that mirrors writes to several internal backends.
FileBackendStore[] $backends
Prioritized list of FileBackendStore objects.
fileExists(array $params)
Check if a file exists at a storage path in the backend.
getLocalReferenceMulti(array $params)
Like getLocalReference() except it takes an array of storage paths and returns a map of storage paths...
getLocalCopyMulti(array $params)
Like getLocalCopy() except it takes an array of storage paths and returns a map of storage paths to T...
consistencyCheck(array $paths)
Check that a set of files are consistent across all internal backends.
getFileSha1Base36(array $params)
Get a SHA-1 hash of the file at a storage path in the backend.
int $readIndex
Index of read affinity backend.
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
getFileContentsMulti(array $params)
Like getFileContents() except it takes an array of storage paths and returns a map of storage paths t...
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
resyncFiles(array $paths, $resyncMode=true)
Check that a set of files are consistent across all internal backends and re-synchronize those files ...
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.
__construct(array $config)
Construct a proxy backend that consists of several internal backends.
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.
doOperationsInternal(array $ops, array $opts)
streamFile(array $params)
Stream the file at a storage path in the backend.
int $masterIndex
Index of master backend.
accessibilityCheck(array $paths)
Check that a set of file paths are usable across all internal backends.
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
fileStoragePathsForOps(array $ops)
Get a list of file storage paths to read or write for a list of operations.
concatenate(array $params)
Concatenate a list of storage files into a single file system file.
substOpPaths(array $ops, FileBackendStore $backend)
Same as substOpBatchPaths() but for a single operation.
substOpBatchPaths(array $ops, FileBackendStore $backend)
Substitute the backend name in storage path parameters for a set of operations with that of a given i...
getFileProps(array $params)
Get the properties of the file at a storage path in the backend.
doDirectoryOp( $method, array $params)
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.
substPaths( $paths, FileBackendStore $backend)
Substitute the backend of storage paths with an internal backend's name.
directoryExists(array $params)
Check if a directory exists at a given storage path.
getFileTimestamp(array $params)
Get the last-modified timestamp of the file at a storage path.
unsubstPaths( $paths)
Substitute the backend of internal storage paths with the proxy backend's name.
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.
string $domainId
Unique domain name.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
newStatus()
Yields the result of the status wrapper callback on either:
FileJournal $fileJournal
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
Definition design.txt:12
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
the array() calling protocol came about after MediaWiki 1.4rc1.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:1950
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
$params
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition defines.php:6