[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/filebackend/ -> FileBackendMultiWrite.php (source)

   1  <?php
   2  /**
   3   * Proxy backend that mirrors writes to several internal backends.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup FileBackend
  22   * @author Aaron Schulz
  23   */
  24  
  25  /**
  26   * @brief Proxy backend that mirrors writes to several internal backends.
  27   *
  28   * This class defines a multi-write backend. Multiple backends can be
  29   * registered to this proxy backend and it will act as a single backend.
  30   * Use this when all access to those backends is through this proxy backend.
  31   * At least one of the backends must be declared the "master" backend.
  32   *
  33   * Only use this class when transitioning from one storage system to another.
  34   *
  35   * Read operations are only done on the 'master' backend for consistency.
  36   * Write operations are performed on all backends, in the order defined.
  37   * If an operation fails on one backend it will be rolled back from the others.
  38   *
  39   * @ingroup FileBackend
  40   * @since 1.19
  41   */
  42  class FileBackendMultiWrite extends FileBackend {
  43      /** @var array Prioritized list of FileBackendStore objects.
  44       * array of (backend index => backends)
  45       */
  46      protected $backends = array();
  47  
  48      /** @var int Index of master backend */
  49      protected $masterIndex = -1;
  50  
  51      /** @var int Bitfield */
  52      protected $syncChecks = 0;
  53  
  54      /** @var string|bool */
  55      protected $autoResync = false;
  56  
  57      /** @var array */
  58      protected $noPushDirConts = array();
  59  
  60      /** @var bool */
  61      protected $noPushQuickOps = false;
  62  
  63      /* Possible internal backend consistency checks */
  64      const CHECK_SIZE = 1;
  65      const CHECK_TIME = 2;
  66      const CHECK_SHA1 = 4;
  67  
  68      /**
  69       * Construct a proxy backend that consists of several internal backends.
  70       * Locking, journaling, and read-only checks are handled by the proxy backend.
  71       *
  72       * Additional $config params include:
  73       *   - backends       : Array of backend config and multi-backend settings.
  74       *                      Each value is the config used in the constructor of a
  75       *                      FileBackendStore class, but with these additional settings:
  76       *                        - class         : The name of the backend class
  77       *                        - isMultiMaster : This must be set for one backend.
  78       *                        - template:     : If given a backend name, this will use
  79       *                                          the config of that backend as a template.
  80       *                                          Values specified here take precedence.
  81       *   - syncChecks     : Integer bitfield of internal backend sync checks to perform.
  82       *                      Possible bits include the FileBackendMultiWrite::CHECK_* constants.
  83       *                      There are constants for SIZE, TIME, and SHA1.
  84       *                      The checks are done before allowing any file operations.
  85       *   - autoResync     : Automatically resync the clone backends to the master backend
  86       *                      when pre-operation sync checks fail. This should only be used
  87       *                      if the master backend is stable and not missing any files.
  88       *                      Use "conservative" to limit resyncing to copying newer master
  89       *                      backend files over older (or non-existing) clone backend files.
  90       *                      Cases that cannot be handled will result in operation abortion.
  91       *   - noPushQuickOps : (hack) Only apply doQuickOperations() to the master backend.
  92       *   - noPushDirConts : (hack) Only apply directory functions to the master backend.
  93       *
  94       * @param array $config
  95       * @throws FileBackendError
  96       */
  97  	public function __construct( array $config ) {
  98          parent::__construct( $config );
  99          $this->syncChecks = isset( $config['syncChecks'] )
 100              ? $config['syncChecks']
 101              : self::CHECK_SIZE;
 102          $this->autoResync = isset( $config['autoResync'] )
 103              ? $config['autoResync']
 104              : false;
 105          $this->noPushQuickOps = isset( $config['noPushQuickOps'] )
 106              ? $config['noPushQuickOps']
 107              : false;
 108          $this->noPushDirConts = isset( $config['noPushDirConts'] )
 109              ? $config['noPushDirConts']
 110              : array();
 111          // Construct backends here rather than via registration
 112          // to keep these backends hidden from outside the proxy.
 113          $namesUsed = array();
 114          foreach ( $config['backends'] as $index => $config ) {
 115              if ( isset( $config['template'] ) ) {
 116                  // Config is just a modified version of a registered backend's.
 117                  // This should only be used when that config is used only by this backend.
 118                  $config = $config + FileBackendGroup::singleton()->config( $config['template'] );
 119              }
 120              $name = $config['name'];
 121              if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
 122                  throw new FileBackendError( "Two or more backends defined with the name $name." );
 123              }
 124              $namesUsed[$name] = 1;
 125              // Alter certain sub-backend settings for sanity
 126              unset( $config['readOnly'] ); // use proxy backend setting
 127              unset( $config['fileJournal'] ); // use proxy backend journal
 128              unset( $config['lockManager'] ); // lock under proxy backend
 129              $config['wikiId'] = $this->wikiId; // use the proxy backend wiki ID
 130              if ( !empty( $config['isMultiMaster'] ) ) {
 131                  if ( $this->masterIndex >= 0 ) {
 132                      throw new FileBackendError( 'More than one master backend defined.' );
 133                  }
 134                  $this->masterIndex = $index; // this is the "master"
 135                  $config['fileJournal'] = $this->fileJournal; // log under proxy backend
 136              }
 137              // Create sub-backend object
 138              if ( !isset( $config['class'] ) ) {
 139                  throw new FileBackendError( 'No class given for a backend config.' );
 140              }
 141              $class = $config['class'];
 142              $this->backends[$index] = new $class( $config );
 143          }
 144          if ( $this->masterIndex < 0 ) { // need backends and must have a master
 145              throw new FileBackendError( 'No master backend defined.' );
 146          }
 147      }
 148  
 149  	final protected function doOperationsInternal( array $ops, array $opts ) {
 150          $status = Status::newGood();
 151  
 152          $mbe = $this->backends[$this->masterIndex]; // convenience
 153  
 154          // Try to lock those files for the scope of this function...
 155          if ( empty( $opts['nonLocking'] ) ) {
 156              // Try to lock those files for the scope of this function...
 157              $scopeLock = $this->getScopedLocksForOps( $ops, $status );
 158              if ( !$status->isOK() ) {
 159                  return $status; // abort
 160              }
 161          }
 162          // Clear any cache entries (after locks acquired)
 163          $this->clearCache();
 164          $opts['preserveCache'] = true; // only locked files are cached
 165          // Get the list of paths to read/write...
 166          $relevantPaths = $this->fileStoragePathsForOps( $ops );
 167          // Check if the paths are valid and accessible on all backends...
 168          $status->merge( $this->accessibilityCheck( $relevantPaths ) );
 169          if ( !$status->isOK() ) {
 170              return $status; // abort
 171          }
 172          // Do a consistency check to see if the backends are consistent...
 173          $syncStatus = $this->consistencyCheck( $relevantPaths );
 174          if ( !$syncStatus->isOK() ) {
 175              wfDebugLog( 'FileOperation', get_class( $this ) .
 176                  " failed sync check: " . FormatJson::encode( $relevantPaths ) );
 177              // Try to resync the clone backends to the master on the spot...
 178              if ( !$this->autoResync || !$this->resyncFiles( $relevantPaths )->isOK() ) {
 179                  $status->merge( $syncStatus );
 180  
 181                  return $status; // abort
 182              }
 183          }
 184          // Actually attempt the operation batch on the master backend...
 185          $realOps = $this->substOpBatchPaths( $ops, $mbe );
 186          $masterStatus = $mbe->doOperations( $realOps, $opts );
 187          $status->merge( $masterStatus );
 188          // Propagate the operations to the clone backends if there were no unexpected errors
 189          // and if there were either no expected errors or if the 'force' option was used.
 190          // However, if nothing succeeded at all, then don't replicate any of the operations.
 191          // If $ops only had one operation, this might avoid backend sync inconsistencies.
 192          if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
 193              foreach ( $this->backends as $index => $backend ) {
 194                  if ( $index !== $this->masterIndex ) { // not done already
 195                      $realOps = $this->substOpBatchPaths( $ops, $backend );
 196                      $status->merge( $backend->doOperations( $realOps, $opts ) );
 197                  }
 198              }
 199          }
 200          // Make 'success', 'successCount', and 'failCount' fields reflect
 201          // the overall operation, rather than all the batches for each backend.
 202          // Do this by only using success values from the master backend's batch.
 203          $status->success = $masterStatus->success;
 204          $status->successCount = $masterStatus->successCount;
 205          $status->failCount = $masterStatus->failCount;
 206  
 207          return $status;
 208      }
 209  
 210      /**
 211       * Check that a set of files are consistent across all internal backends
 212       *
 213       * @param array $paths List of storage paths
 214       * @return Status
 215       */
 216  	public function consistencyCheck( array $paths ) {
 217          $status = Status::newGood();
 218          if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
 219              return $status; // skip checks
 220          }
 221  
 222          $mBackend = $this->backends[$this->masterIndex];
 223          foreach ( $paths as $path ) {
 224              $params = array( 'src' => $path, 'latest' => true );
 225              $mParams = $this->substOpPaths( $params, $mBackend );
 226              // Stat the file on the 'master' backend
 227              $mStat = $mBackend->getFileStat( $mParams );
 228              if ( $this->syncChecks & self::CHECK_SHA1 ) {
 229                  $mSha1 = $mBackend->getFileSha1Base36( $mParams );
 230              } else {
 231                  $mSha1 = false;
 232              }
 233              // Check if all clone backends agree with the master...
 234              foreach ( $this->backends as $index => $cBackend ) {
 235                  if ( $index === $this->masterIndex ) {
 236                      continue; // master
 237                  }
 238                  $cParams = $this->substOpPaths( $params, $cBackend );
 239                  $cStat = $cBackend->getFileStat( $cParams );
 240                  if ( $mStat ) { // file is in master
 241                      if ( !$cStat ) { // file should exist
 242                          $status->fatal( 'backend-fail-synced', $path );
 243                          continue;
 244                      }
 245                      if ( $this->syncChecks & self::CHECK_SIZE ) {
 246                          if ( $cStat['size'] != $mStat['size'] ) { // wrong size
 247                              $status->fatal( 'backend-fail-synced', $path );
 248                              continue;
 249                          }
 250                      }
 251                      if ( $this->syncChecks & self::CHECK_TIME ) {
 252                          $mTs = wfTimestamp( TS_UNIX, $mStat['mtime'] );
 253                          $cTs = wfTimestamp( TS_UNIX, $cStat['mtime'] );
 254                          if ( abs( $mTs - $cTs ) > 30 ) { // outdated file somewhere
 255                              $status->fatal( 'backend-fail-synced', $path );
 256                              continue;
 257                          }
 258                      }
 259                      if ( $this->syncChecks & self::CHECK_SHA1 ) {
 260                          if ( $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) { // wrong SHA1
 261                              $status->fatal( 'backend-fail-synced', $path );
 262                              continue;
 263                          }
 264                      }
 265                  } else { // file is not in master
 266                      if ( $cStat ) { // file should not exist
 267                          $status->fatal( 'backend-fail-synced', $path );
 268                      }
 269                  }
 270              }
 271          }
 272  
 273          return $status;
 274      }
 275  
 276      /**
 277       * Check that a set of file paths are usable across all internal backends
 278       *
 279       * @param array $paths List of storage paths
 280       * @return Status
 281       */
 282  	public function accessibilityCheck( array $paths ) {
 283          $status = Status::newGood();
 284          if ( count( $this->backends ) <= 1 ) {
 285              return $status; // skip checks
 286          }
 287  
 288          foreach ( $paths as $path ) {
 289              foreach ( $this->backends as $backend ) {
 290                  $realPath = $this->substPaths( $path, $backend );
 291                  if ( !$backend->isPathUsableInternal( $realPath ) ) {
 292                      $status->fatal( 'backend-fail-usable', $path );
 293                  }
 294              }
 295          }
 296  
 297          return $status;
 298      }
 299  
 300      /**
 301       * Check that a set of files are consistent across all internal backends
 302       * and re-synchronize those files againt the "multi master" if needed.
 303       *
 304       * @param array $paths List of storage paths
 305       * @return Status
 306       */
 307  	public function resyncFiles( array $paths ) {
 308          $status = Status::newGood();
 309  
 310          $mBackend = $this->backends[$this->masterIndex];
 311          foreach ( $paths as $path ) {
 312              $mPath = $this->substPaths( $path, $mBackend );
 313              $mSha1 = $mBackend->getFileSha1Base36( array( 'src' => $mPath, 'latest' => true ) );
 314              $mStat = $mBackend->getFileStat( array( 'src' => $mPath, 'latest' => true ) );
 315              if ( $mStat === null || ( $mSha1 !== false && !$mStat ) ) { // sanity
 316                  $status->fatal( 'backend-fail-internal', $this->name );
 317                  continue; // file is not available on the master backend...
 318              }
 319              // Check of all clone backends agree with the master...
 320              foreach ( $this->backends as $index => $cBackend ) {
 321                  if ( $index === $this->masterIndex ) {
 322                      continue; // master
 323                  }
 324                  $cPath = $this->substPaths( $path, $cBackend );
 325                  $cSha1 = $cBackend->getFileSha1Base36( array( 'src' => $cPath, 'latest' => true ) );
 326                  $cStat = $cBackend->getFileStat( array( 'src' => $cPath, 'latest' => true ) );
 327                  if ( $cStat === null || ( $cSha1 !== false && !$cStat ) ) { // sanity
 328                      $status->fatal( 'backend-fail-internal', $cBackend->getName() );
 329                      continue; // file is not available on the clone backend...
 330                  }
 331                  if ( $mSha1 === $cSha1 ) {
 332                      // already synced; nothing to do
 333                  } elseif ( $mSha1 !== false ) { // file is in master
 334                      if ( $this->autoResync === 'conservative'
 335                          && $cStat && $cStat['mtime'] > $mStat['mtime']
 336                      ) {
 337                          $status->fatal( 'backend-fail-synced', $path );
 338                          continue; // don't rollback data
 339                      }
 340                      $fsFile = $mBackend->getLocalReference(
 341                          array( 'src' => $mPath, 'latest' => true ) );
 342                      $status->merge( $cBackend->quickStore(
 343                          array( 'src' => $fsFile->getPath(), 'dst' => $cPath )
 344                      ) );
 345                  } elseif ( $mStat === false ) { // file is not in master
 346                      if ( $this->autoResync === 'conservative' ) {
 347                          $status->fatal( 'backend-fail-synced', $path );
 348                          continue; // don't delete data
 349                      }
 350                      $status->merge( $cBackend->quickDelete( array( 'src' => $cPath ) ) );
 351                  }
 352              }
 353          }
 354  
 355          return $status;
 356      }
 357  
 358      /**
 359       * Get a list of file storage paths to read or write for a list of operations
 360       *
 361       * @param array $ops Same format as doOperations()
 362       * @return array List of storage paths to files (does not include directories)
 363       */
 364  	protected function fileStoragePathsForOps( array $ops ) {
 365          $paths = array();
 366          foreach ( $ops as $op ) {
 367              if ( isset( $op['src'] ) ) {
 368                  // For things like copy/move/delete with "ignoreMissingSource" and there
 369                  // is no source file, nothing should happen and there should be no errors.
 370                  if ( empty( $op['ignoreMissingSource'] )
 371                      || $this->fileExists( array( 'src' => $op['src'] ) )
 372                  ) {
 373                      $paths[] = $op['src'];
 374                  }
 375              }
 376              if ( isset( $op['srcs'] ) ) {
 377                  $paths = array_merge( $paths, $op['srcs'] );
 378              }
 379              if ( isset( $op['dst'] ) ) {
 380                  $paths[] = $op['dst'];
 381              }
 382          }
 383  
 384          return array_values( array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) ) );
 385      }
 386  
 387      /**
 388       * Substitute the backend name in storage path parameters
 389       * for a set of operations with that of a given internal backend.
 390       *
 391       * @param array $ops List of file operation arrays
 392       * @param FileBackendStore $backend
 393       * @return array
 394       */
 395  	protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
 396          $newOps = array(); // operations
 397          foreach ( $ops as $op ) {
 398              $newOp = $op; // operation
 399              foreach ( array( 'src', 'srcs', 'dst', 'dir' ) as $par ) {
 400                  if ( isset( $newOp[$par] ) ) { // string or array
 401                      $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
 402                  }
 403              }
 404              $newOps[] = $newOp;
 405          }
 406  
 407          return $newOps;
 408      }
 409  
 410      /**
 411       * Same as substOpBatchPaths() but for a single operation
 412       *
 413       * @param array $ops File operation array
 414       * @param FileBackendStore $backend
 415       * @return array
 416       */
 417  	protected function substOpPaths( array $ops, FileBackendStore $backend ) {
 418          $newOps = $this->substOpBatchPaths( array( $ops ), $backend );
 419  
 420          return $newOps[0];
 421      }
 422  
 423      /**
 424       * Substitute the backend of storage paths with an internal backend's name
 425       *
 426       * @param array|string $paths List of paths or single string path
 427       * @param FileBackendStore $backend
 428       * @return array|string
 429       */
 430  	protected function substPaths( $paths, FileBackendStore $backend ) {
 431          return preg_replace(
 432              '!^mwstore://' . preg_quote( $this->name ) . '/!',
 433              StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
 434              $paths // string or array
 435          );
 436      }
 437  
 438      /**
 439       * Substitute the backend of internal storage paths with the proxy backend's name
 440       *
 441       * @param array|string $paths List of paths or single string path
 442       * @return array|string
 443       */
 444  	protected function unsubstPaths( $paths ) {
 445          return preg_replace(
 446              '!^mwstore://([^/]+)!',
 447              StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ),
 448              $paths // string or array
 449          );
 450      }
 451  
 452  	protected function doQuickOperationsInternal( array $ops ) {
 453          $status = Status::newGood();
 454          // Do the operations on the master backend; setting Status fields...
 455          $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
 456          $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
 457          $status->merge( $masterStatus );
 458          // Propagate the operations to the clone backends...
 459          if ( !$this->noPushQuickOps ) {
 460              foreach ( $this->backends as $index => $backend ) {
 461                  if ( $index !== $this->masterIndex ) { // not done already
 462                      $realOps = $this->substOpBatchPaths( $ops, $backend );
 463                      $status->merge( $backend->doQuickOperations( $realOps ) );
 464                  }
 465              }
 466          }
 467          // Make 'success', 'successCount', and 'failCount' fields reflect
 468          // the overall operation, rather than all the batches for each backend.
 469          // Do this by only using success values from the master backend's batch.
 470          $status->success = $masterStatus->success;
 471          $status->successCount = $masterStatus->successCount;
 472          $status->failCount = $masterStatus->failCount;
 473  
 474          return $status;
 475      }
 476  
 477      /**
 478       * @param string $path Storage path
 479       * @return bool Path container should have dir changes pushed to all backends
 480       */
 481  	protected function replicateContainerDirChanges( $path ) {
 482          list( , $shortCont, ) = self::splitStoragePath( $path );
 483  
 484          return !in_array( $shortCont, $this->noPushDirConts );
 485      }
 486  
 487  	protected function doPrepare( array $params ) {
 488          $status = Status::newGood();
 489          $replicate = $this->replicateContainerDirChanges( $params['dir'] );
 490          foreach ( $this->backends as $index => $backend ) {
 491              if ( $replicate || $index == $this->masterIndex ) {
 492                  $realParams = $this->substOpPaths( $params, $backend );
 493                  $status->merge( $backend->doPrepare( $realParams ) );
 494              }
 495          }
 496  
 497          return $status;
 498      }
 499  
 500  	protected function doSecure( array $params ) {
 501          $status = Status::newGood();
 502          $replicate = $this->replicateContainerDirChanges( $params['dir'] );
 503          foreach ( $this->backends as $index => $backend ) {
 504              if ( $replicate || $index == $this->masterIndex ) {
 505                  $realParams = $this->substOpPaths( $params, $backend );
 506                  $status->merge( $backend->doSecure( $realParams ) );
 507              }
 508          }
 509  
 510          return $status;
 511      }
 512  
 513  	protected function doPublish( array $params ) {
 514          $status = Status::newGood();
 515          $replicate = $this->replicateContainerDirChanges( $params['dir'] );
 516          foreach ( $this->backends as $index => $backend ) {
 517              if ( $replicate || $index == $this->masterIndex ) {
 518                  $realParams = $this->substOpPaths( $params, $backend );
 519                  $status->merge( $backend->doPublish( $realParams ) );
 520              }
 521          }
 522  
 523          return $status;
 524      }
 525  
 526  	protected function doClean( array $params ) {
 527          $status = Status::newGood();
 528          $replicate = $this->replicateContainerDirChanges( $params['dir'] );
 529          foreach ( $this->backends as $index => $backend ) {
 530              if ( $replicate || $index == $this->masterIndex ) {
 531                  $realParams = $this->substOpPaths( $params, $backend );
 532                  $status->merge( $backend->doClean( $realParams ) );
 533              }
 534          }
 535  
 536          return $status;
 537      }
 538  
 539  	public function concatenate( array $params ) {
 540          // We are writing to an FS file, so we don't need to do this per-backend
 541          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 542  
 543          return $this->backends[$this->masterIndex]->concatenate( $realParams );
 544      }
 545  
 546  	public function fileExists( array $params ) {
 547          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 548  
 549          return $this->backends[$this->masterIndex]->fileExists( $realParams );
 550      }
 551  
 552  	public function getFileTimestamp( array $params ) {
 553          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 554  
 555          return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams );
 556      }
 557  
 558  	public function getFileSize( array $params ) {
 559          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 560  
 561          return $this->backends[$this->masterIndex]->getFileSize( $realParams );
 562      }
 563  
 564  	public function getFileStat( array $params ) {
 565          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 566  
 567          return $this->backends[$this->masterIndex]->getFileStat( $realParams );
 568      }
 569  
 570  	public function getFileXAttributes( array $params ) {
 571          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 572  
 573          return $this->backends[$this->masterIndex]->getFileXAttributes( $realParams );
 574      }
 575  
 576  	public function getFileContentsMulti( array $params ) {
 577          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 578          $contentsM = $this->backends[$this->masterIndex]->getFileContentsMulti( $realParams );
 579  
 580          $contents = array(); // (path => FSFile) mapping using the proxy backend's name
 581          foreach ( $contentsM as $path => $data ) {
 582              $contents[$this->unsubstPaths( $path )] = $data;
 583          }
 584  
 585          return $contents;
 586      }
 587  
 588  	public function getFileSha1Base36( array $params ) {
 589          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 590  
 591          return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams );
 592      }
 593  
 594  	public function getFileProps( array $params ) {
 595          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 596  
 597          return $this->backends[$this->masterIndex]->getFileProps( $realParams );
 598      }
 599  
 600  	public function streamFile( array $params ) {
 601          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 602  
 603          return $this->backends[$this->masterIndex]->streamFile( $realParams );
 604      }
 605  
 606  	public function getLocalReferenceMulti( array $params ) {
 607          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 608          $fsFilesM = $this->backends[$this->masterIndex]->getLocalReferenceMulti( $realParams );
 609  
 610          $fsFiles = array(); // (path => FSFile) mapping using the proxy backend's name
 611          foreach ( $fsFilesM as $path => $fsFile ) {
 612              $fsFiles[$this->unsubstPaths( $path )] = $fsFile;
 613          }
 614  
 615          return $fsFiles;
 616      }
 617  
 618  	public function getLocalCopyMulti( array $params ) {
 619          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 620          $tempFilesM = $this->backends[$this->masterIndex]->getLocalCopyMulti( $realParams );
 621  
 622          $tempFiles = array(); // (path => TempFSFile) mapping using the proxy backend's name
 623          foreach ( $tempFilesM as $path => $tempFile ) {
 624              $tempFiles[$this->unsubstPaths( $path )] = $tempFile;
 625          }
 626  
 627          return $tempFiles;
 628      }
 629  
 630  	public function getFileHttpUrl( array $params ) {
 631          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 632  
 633          return $this->backends[$this->masterIndex]->getFileHttpUrl( $realParams );
 634      }
 635  
 636  	public function directoryExists( array $params ) {
 637          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 638  
 639          return $this->backends[$this->masterIndex]->directoryExists( $realParams );
 640      }
 641  
 642  	public function getDirectoryList( array $params ) {
 643          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 644  
 645          return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
 646      }
 647  
 648  	public function getFileList( array $params ) {
 649          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 650  
 651          return $this->backends[$this->masterIndex]->getFileList( $realParams );
 652      }
 653  
 654  	public function getFeatures() {
 655          return $this->backends[$this->masterIndex]->getFeatures();
 656      }
 657  
 658  	public function clearCache( array $paths = null ) {
 659          foreach ( $this->backends as $backend ) {
 660              $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
 661              $backend->clearCache( $realPaths );
 662          }
 663      }
 664  
 665  	public function preloadCache( array $paths ) {
 666          $realPaths = $this->substPaths( $paths, $this->backends[$this->masterIndex] );
 667          $this->backends[$this->masterIndex]->preloadCache( $realPaths );
 668      }
 669  
 670  	public function preloadFileStat( array $params ) {
 671          $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
 672          return $this->backends[$this->masterIndex]->preloadFileStat( $realParams );
 673      }
 674  
 675  	public function getScopedLocksForOps( array $ops, Status $status ) {
 676          $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
 677          $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
 678          // Get the paths to lock from the master backend
 679          $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
 680          // Get the paths under the proxy backend's name
 681          $pbPaths = array(
 682              LockManager::LOCK_UW => $this->unsubstPaths( $paths[LockManager::LOCK_UW] ),
 683              LockManager::LOCK_EX => $this->unsubstPaths( $paths[LockManager::LOCK_EX] )
 684          );
 685  
 686          // Actually acquire the locks
 687          return array( $this->getScopedFileLocks( $pbPaths, 'mixed', $status ) );
 688      }
 689  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1