[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

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

   1  <?php
   2  /**
   3   * Helper class for representing operations with transaction support.
   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   * FileBackend helper class for representing operations.
  27   * Do not use this class from places outside FileBackend.
  28   *
  29   * Methods called from FileOpBatch::attempt() should avoid throwing
  30   * exceptions at all costs. FileOp objects should be lightweight in order
  31   * to support large arrays in memory and serialization.
  32   *
  33   * @ingroup FileBackend
  34   * @since 1.19
  35   */
  36  abstract class FileOp {
  37      /** @var array */
  38      protected $params = array();
  39  
  40      /** @var FileBackendStore */
  41      protected $backend;
  42  
  43      /** @var int */
  44      protected $state = self::STATE_NEW;
  45  
  46      /** @var bool */
  47      protected $failed = false;
  48  
  49      /** @var bool */
  50      protected $async = false;
  51  
  52      /** @var string */
  53      protected $batchId;
  54  
  55      /** @var bool Operation is not a no-op */
  56      protected $doOperation = true;
  57  
  58      /** @var string */
  59      protected $sourceSha1;
  60  
  61      /** @var bool */
  62      protected $overwriteSameCase;
  63  
  64      /** @var bool */
  65      protected $destExists;
  66  
  67      /* Object life-cycle */
  68      const STATE_NEW = 1;
  69      const STATE_CHECKED = 2;
  70      const STATE_ATTEMPTED = 3;
  71  
  72      /**
  73       * Build a new batch file operation transaction
  74       *
  75       * @param FileBackendStore $backend
  76       * @param array $params
  77       * @throws FileBackendError
  78       */
  79  	final public function __construct( FileBackendStore $backend, array $params ) {
  80          $this->backend = $backend;
  81          list( $required, $optional, $paths ) = $this->allowedParams();
  82          foreach ( $required as $name ) {
  83              if ( isset( $params[$name] ) ) {
  84                  $this->params[$name] = $params[$name];
  85              } else {
  86                  throw new FileBackendError( "File operation missing parameter '$name'." );
  87              }
  88          }
  89          foreach ( $optional as $name ) {
  90              if ( isset( $params[$name] ) ) {
  91                  $this->params[$name] = $params[$name];
  92              }
  93          }
  94          foreach ( $paths as $name ) {
  95              if ( isset( $this->params[$name] ) ) {
  96                  // Normalize paths so the paths to the same file have the same string
  97                  $this->params[$name] = self::normalizeIfValidStoragePath( $this->params[$name] );
  98              }
  99          }
 100      }
 101  
 102      /**
 103       * Normalize a string if it is a valid storage path
 104       *
 105       * @param string $path
 106       * @return string
 107       */
 108  	protected static function normalizeIfValidStoragePath( $path ) {
 109          if ( FileBackend::isStoragePath( $path ) ) {
 110              $res = FileBackend::normalizeStoragePath( $path );
 111  
 112              return ( $res !== null ) ? $res : $path;
 113          }
 114  
 115          return $path;
 116      }
 117  
 118      /**
 119       * Set the batch UUID this operation belongs to
 120       *
 121       * @param string $batchId
 122       */
 123  	final public function setBatchId( $batchId ) {
 124          $this->batchId = $batchId;
 125      }
 126  
 127      /**
 128       * Get the value of the parameter with the given name
 129       *
 130       * @param string $name
 131       * @return mixed Returns null if the parameter is not set
 132       */
 133  	final public function getParam( $name ) {
 134          return isset( $this->params[$name] ) ? $this->params[$name] : null;
 135      }
 136  
 137      /**
 138       * Check if this operation failed precheck() or attempt()
 139       *
 140       * @return bool
 141       */
 142  	final public function failed() {
 143          return $this->failed;
 144      }
 145  
 146      /**
 147       * Get a new empty predicates array for precheck()
 148       *
 149       * @return array
 150       */
 151  	final public static function newPredicates() {
 152          return array( 'exists' => array(), 'sha1' => array() );
 153      }
 154  
 155      /**
 156       * Get a new empty dependency tracking array for paths read/written to
 157       *
 158       * @return array
 159       */
 160  	final public static function newDependencies() {
 161          return array( 'read' => array(), 'write' => array() );
 162      }
 163  
 164      /**
 165       * Update a dependency tracking array to account for this operation
 166       *
 167       * @param array $deps Prior path reads/writes; format of FileOp::newPredicates()
 168       * @return array
 169       */
 170  	final public function applyDependencies( array $deps ) {
 171          $deps['read'] += array_fill_keys( $this->storagePathsRead(), 1 );
 172          $deps['write'] += array_fill_keys( $this->storagePathsChanged(), 1 );
 173  
 174          return $deps;
 175      }
 176  
 177      /**
 178       * Check if this operation changes files listed in $paths
 179       *
 180       * @param array $deps Prior path reads/writes; format of FileOp::newPredicates()
 181       * @return bool
 182       */
 183  	final public function dependsOn( array $deps ) {
 184          foreach ( $this->storagePathsChanged() as $path ) {
 185              if ( isset( $deps['read'][$path] ) || isset( $deps['write'][$path] ) ) {
 186                  return true; // "output" or "anti" dependency
 187              }
 188          }
 189          foreach ( $this->storagePathsRead() as $path ) {
 190              if ( isset( $deps['write'][$path] ) ) {
 191                  return true; // "flow" dependency
 192              }
 193          }
 194  
 195          return false;
 196      }
 197  
 198      /**
 199       * Get the file journal entries for this file operation
 200       *
 201       * @param array $oPredicates Pre-op info about files (format of FileOp::newPredicates)
 202       * @param array $nPredicates Post-op info about files (format of FileOp::newPredicates)
 203       * @return array
 204       */
 205  	final public function getJournalEntries( array $oPredicates, array $nPredicates ) {
 206          if ( !$this->doOperation ) {
 207              return array(); // this is a no-op
 208          }
 209          $nullEntries = array();
 210          $updateEntries = array();
 211          $deleteEntries = array();
 212          $pathsUsed = array_merge( $this->storagePathsRead(), $this->storagePathsChanged() );
 213          foreach ( array_unique( $pathsUsed ) as $path ) {
 214              $nullEntries[] = array( // assertion for recovery
 215                  'op' => 'null',
 216                  'path' => $path,
 217                  'newSha1' => $this->fileSha1( $path, $oPredicates )
 218              );
 219          }
 220          foreach ( $this->storagePathsChanged() as $path ) {
 221              if ( $nPredicates['sha1'][$path] === false ) { // deleted
 222                  $deleteEntries[] = array(
 223                      'op' => 'delete',
 224                      'path' => $path,
 225                      'newSha1' => ''
 226                  );
 227              } else { // created/updated
 228                  $updateEntries[] = array(
 229                      'op' => $this->fileExists( $path, $oPredicates ) ? 'update' : 'create',
 230                      'path' => $path,
 231                      'newSha1' => $nPredicates['sha1'][$path]
 232                  );
 233              }
 234          }
 235  
 236          return array_merge( $nullEntries, $updateEntries, $deleteEntries );
 237      }
 238  
 239      /**
 240       * Check preconditions of the operation without writing anything.
 241       * This must update $predicates for each path that the op can change
 242       * except when a failing status object is returned.
 243       *
 244       * @param array $predicates
 245       * @return Status
 246       */
 247  	final public function precheck( array &$predicates ) {
 248          if ( $this->state !== self::STATE_NEW ) {
 249              return Status::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
 250          }
 251          $this->state = self::STATE_CHECKED;
 252          $status = $this->doPrecheck( $predicates );
 253          if ( !$status->isOK() ) {
 254              $this->failed = true;
 255          }
 256  
 257          return $status;
 258      }
 259  
 260      /**
 261       * @param array $predicates
 262       * @return Status
 263       */
 264  	protected function doPrecheck( array &$predicates ) {
 265          return Status::newGood();
 266      }
 267  
 268      /**
 269       * Attempt the operation
 270       *
 271       * @return Status
 272       */
 273  	final public function attempt() {
 274          if ( $this->state !== self::STATE_CHECKED ) {
 275              return Status::newFatal( 'fileop-fail-state', self::STATE_CHECKED, $this->state );
 276          } elseif ( $this->failed ) { // failed precheck
 277              return Status::newFatal( 'fileop-fail-attempt-precheck' );
 278          }
 279          $this->state = self::STATE_ATTEMPTED;
 280          if ( $this->doOperation ) {
 281              $status = $this->doAttempt();
 282              if ( !$status->isOK() ) {
 283                  $this->failed = true;
 284                  $this->logFailure( 'attempt' );
 285              }
 286          } else { // no-op
 287              $status = Status::newGood();
 288          }
 289  
 290          return $status;
 291      }
 292  
 293      /**
 294       * @return Status
 295       */
 296  	protected function doAttempt() {
 297          return Status::newGood();
 298      }
 299  
 300      /**
 301       * Attempt the operation in the background
 302       *
 303       * @return Status
 304       */
 305  	final public function attemptAsync() {
 306          $this->async = true;
 307          $result = $this->attempt();
 308          $this->async = false;
 309  
 310          return $result;
 311      }
 312  
 313      /**
 314       * Get the file operation parameters
 315       *
 316       * @return array (required params list, optional params list, list of params that are paths)
 317       */
 318  	protected function allowedParams() {
 319          return array( array(), array(), array() );
 320      }
 321  
 322      /**
 323       * Adjust params to FileBackendStore internal file calls
 324       *
 325       * @param array $params
 326       * @return array (required params list, optional params list)
 327       */
 328  	protected function setFlags( array $params ) {
 329          return array( 'async' => $this->async ) + $params;
 330      }
 331  
 332      /**
 333       * Get a list of storage paths read from for this operation
 334       *
 335       * @return array
 336       */
 337  	public function storagePathsRead() {
 338          return array();
 339      }
 340  
 341      /**
 342       * Get a list of storage paths written to for this operation
 343       *
 344       * @return array
 345       */
 346  	public function storagePathsChanged() {
 347          return array();
 348      }
 349  
 350      /**
 351       * Check for errors with regards to the destination file already existing.
 352       * Also set the destExists, overwriteSameCase and sourceSha1 member variables.
 353       * A bad status will be returned if there is no chance it can be overwritten.
 354       *
 355       * @param array $predicates
 356       * @return Status
 357       */
 358  	protected function precheckDestExistence( array $predicates ) {
 359          $status = Status::newGood();
 360          // Get hash of source file/string and the destination file
 361          $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string
 362          if ( $this->sourceSha1 === null ) { // file in storage?
 363              $this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
 364          }
 365          $this->overwriteSameCase = false;
 366          $this->destExists = $this->fileExists( $this->params['dst'], $predicates );
 367          if ( $this->destExists ) {
 368              if ( $this->getParam( 'overwrite' ) ) {
 369                  return $status; // OK
 370              } elseif ( $this->getParam( 'overwriteSame' ) ) {
 371                  $dhash = $this->fileSha1( $this->params['dst'], $predicates );
 372                  // Check if hashes are valid and match each other...
 373                  if ( !strlen( $this->sourceSha1 ) || !strlen( $dhash ) ) {
 374                      $status->fatal( 'backend-fail-hashes' );
 375                  } elseif ( $this->sourceSha1 !== $dhash ) {
 376                      // Give an error if the files are not identical
 377                      $status->fatal( 'backend-fail-notsame', $this->params['dst'] );
 378                  } else {
 379                      $this->overwriteSameCase = true; // OK
 380                  }
 381  
 382                  return $status; // do nothing; either OK or bad status
 383              } else {
 384                  $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
 385  
 386                  return $status;
 387              }
 388          }
 389  
 390          return $status;
 391      }
 392  
 393      /**
 394       * precheckDestExistence() helper function to get the source file SHA-1.
 395       * Subclasses should overwride this if the source is not in storage.
 396       *
 397       * @return string|bool Returns false on failure
 398       */
 399  	protected function getSourceSha1Base36() {
 400          return null; // N/A
 401      }
 402  
 403      /**
 404       * Check if a file will exist in storage when this operation is attempted
 405       *
 406       * @param string $source Storage path
 407       * @param array $predicates
 408       * @return bool
 409       */
 410  	final protected function fileExists( $source, array $predicates ) {
 411          if ( isset( $predicates['exists'][$source] ) ) {
 412              return $predicates['exists'][$source]; // previous op assures this
 413          } else {
 414              $params = array( 'src' => $source, 'latest' => true );
 415  
 416              return $this->backend->fileExists( $params );
 417          }
 418      }
 419  
 420      /**
 421       * Get the SHA-1 of a file in storage when this operation is attempted
 422       *
 423       * @param string $source Storage path
 424       * @param array $predicates
 425       * @return string|bool False on failure
 426       */
 427  	final protected function fileSha1( $source, array $predicates ) {
 428          if ( isset( $predicates['sha1'][$source] ) ) {
 429              return $predicates['sha1'][$source]; // previous op assures this
 430          } elseif ( isset( $predicates['exists'][$source] ) && !$predicates['exists'][$source] ) {
 431              return false; // previous op assures this
 432          } else {
 433              $params = array( 'src' => $source, 'latest' => true );
 434  
 435              return $this->backend->getFileSha1Base36( $params );
 436          }
 437      }
 438  
 439      /**
 440       * Get the backend this operation is for
 441       *
 442       * @return FileBackendStore
 443       */
 444  	public function getBackend() {
 445          return $this->backend;
 446      }
 447  
 448      /**
 449       * Log a file operation failure and preserve any temp files
 450       *
 451       * @param string $action
 452       */
 453  	final public function logFailure( $action ) {
 454          $params = $this->params;
 455          $params['failedAction'] = $action;
 456          try {
 457              wfDebugLog( 'FileOperation', get_class( $this ) .
 458                  " failed (batch #{$this->batchId}): " . FormatJson::encode( $params ) );
 459          } catch ( Exception $e ) {
 460              // bad config? debug log error?
 461          }
 462      }
 463  }
 464  
 465  /**
 466   * Create a file in the backend with the given content.
 467   * Parameters for this operation are outlined in FileBackend::doOperations().
 468   */
 469  class CreateFileOp extends FileOp {
 470  	protected function allowedParams() {
 471          return array(
 472              array( 'content', 'dst' ),
 473              array( 'overwrite', 'overwriteSame', 'headers' ),
 474              array( 'dst' )
 475          );
 476      }
 477  
 478  	protected function doPrecheck( array &$predicates ) {
 479          $status = Status::newGood();
 480          // Check if the source data is too big
 481          if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
 482              $status->fatal( 'backend-fail-maxsize',
 483                  $this->params['dst'], $this->backend->maxFileSizeInternal() );
 484              $status->fatal( 'backend-fail-create', $this->params['dst'] );
 485  
 486              return $status;
 487          // Check if a file can be placed/changed at the destination
 488          } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
 489              $status->fatal( 'backend-fail-usable', $this->params['dst'] );
 490              $status->fatal( 'backend-fail-create', $this->params['dst'] );
 491  
 492              return $status;
 493          }
 494          // Check if destination file exists
 495          $status->merge( $this->precheckDestExistence( $predicates ) );
 496          $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
 497          if ( $status->isOK() ) {
 498              // Update file existence predicates
 499              $predicates['exists'][$this->params['dst']] = true;
 500              $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 501          }
 502  
 503          return $status; // safe to call attempt()
 504      }
 505  
 506  	protected function doAttempt() {
 507          if ( !$this->overwriteSameCase ) {
 508              // Create the file at the destination
 509              return $this->backend->createInternal( $this->setFlags( $this->params ) );
 510          }
 511  
 512          return Status::newGood();
 513      }
 514  
 515  	protected function getSourceSha1Base36() {
 516          return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
 517      }
 518  
 519  	public function storagePathsChanged() {
 520          return array( $this->params['dst'] );
 521      }
 522  }
 523  
 524  /**
 525   * Store a file into the backend from a file on the file system.
 526   * Parameters for this operation are outlined in FileBackend::doOperations().
 527   */
 528  class StoreFileOp extends FileOp {
 529  	protected function allowedParams() {
 530          return array(
 531              array( 'src', 'dst' ),
 532              array( 'overwrite', 'overwriteSame', 'headers' ),
 533              array( 'src', 'dst' )
 534          );
 535      }
 536  
 537  	protected function doPrecheck( array &$predicates ) {
 538          $status = Status::newGood();
 539          // Check if the source file exists on the file system
 540          if ( !is_file( $this->params['src'] ) ) {
 541              $status->fatal( 'backend-fail-notexists', $this->params['src'] );
 542  
 543              return $status;
 544          // Check if the source file is too big
 545          } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
 546              $status->fatal( 'backend-fail-maxsize',
 547                  $this->params['dst'], $this->backend->maxFileSizeInternal() );
 548              $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
 549  
 550              return $status;
 551          // Check if a file can be placed/changed at the destination
 552          } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
 553              $status->fatal( 'backend-fail-usable', $this->params['dst'] );
 554              $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
 555  
 556              return $status;
 557          }
 558          // Check if destination file exists
 559          $status->merge( $this->precheckDestExistence( $predicates ) );
 560          $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
 561          if ( $status->isOK() ) {
 562              // Update file existence predicates
 563              $predicates['exists'][$this->params['dst']] = true;
 564              $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 565          }
 566  
 567          return $status; // safe to call attempt()
 568      }
 569  
 570  	protected function doAttempt() {
 571          if ( !$this->overwriteSameCase ) {
 572              // Store the file at the destination
 573              return $this->backend->storeInternal( $this->setFlags( $this->params ) );
 574          }
 575  
 576          return Status::newGood();
 577      }
 578  
 579  	protected function getSourceSha1Base36() {
 580          wfSuppressWarnings();
 581          $hash = sha1_file( $this->params['src'] );
 582          wfRestoreWarnings();
 583          if ( $hash !== false ) {
 584              $hash = wfBaseConvert( $hash, 16, 36, 31 );
 585          }
 586  
 587          return $hash;
 588      }
 589  
 590  	public function storagePathsChanged() {
 591          return array( $this->params['dst'] );
 592      }
 593  }
 594  
 595  /**
 596   * Copy a file from one storage path to another in the backend.
 597   * Parameters for this operation are outlined in FileBackend::doOperations().
 598   */
 599  class CopyFileOp extends FileOp {
 600  	protected function allowedParams() {
 601          return array(
 602              array( 'src', 'dst' ),
 603              array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ),
 604              array( 'src', 'dst' )
 605          );
 606      }
 607  
 608  	protected function doPrecheck( array &$predicates ) {
 609          $status = Status::newGood();
 610          // Check if the source file exists
 611          if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
 612              if ( $this->getParam( 'ignoreMissingSource' ) ) {
 613                  $this->doOperation = false; // no-op
 614                  // Update file existence predicates (cache 404s)
 615                  $predicates['exists'][$this->params['src']] = false;
 616                  $predicates['sha1'][$this->params['src']] = false;
 617  
 618                  return $status; // nothing to do
 619              } else {
 620                  $status->fatal( 'backend-fail-notexists', $this->params['src'] );
 621  
 622                  return $status;
 623              }
 624              // Check if a file can be placed/changed at the destination
 625          } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
 626              $status->fatal( 'backend-fail-usable', $this->params['dst'] );
 627              $status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
 628  
 629              return $status;
 630          }
 631          // Check if destination file exists
 632          $status->merge( $this->precheckDestExistence( $predicates ) );
 633          $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
 634          if ( $status->isOK() ) {
 635              // Update file existence predicates
 636              $predicates['exists'][$this->params['dst']] = true;
 637              $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 638          }
 639  
 640          return $status; // safe to call attempt()
 641      }
 642  
 643  	protected function doAttempt() {
 644          if ( $this->overwriteSameCase ) {
 645              $status = Status::newGood(); // nothing to do
 646          } elseif ( $this->params['src'] === $this->params['dst'] ) {
 647              // Just update the destination file headers
 648              $headers = $this->getParam( 'headers' ) ?: array();
 649              $status = $this->backend->describeInternal( $this->setFlags( array(
 650                  'src' => $this->params['dst'], 'headers' => $headers
 651              ) ) );
 652          } else {
 653              // Copy the file to the destination
 654              $status = $this->backend->copyInternal( $this->setFlags( $this->params ) );
 655          }
 656  
 657          return $status;
 658      }
 659  
 660  	public function storagePathsRead() {
 661          return array( $this->params['src'] );
 662      }
 663  
 664  	public function storagePathsChanged() {
 665          return array( $this->params['dst'] );
 666      }
 667  }
 668  
 669  /**
 670   * Move a file from one storage path to another in the backend.
 671   * Parameters for this operation are outlined in FileBackend::doOperations().
 672   */
 673  class MoveFileOp extends FileOp {
 674  	protected function allowedParams() {
 675          return array(
 676              array( 'src', 'dst' ),
 677              array( 'overwrite', 'overwriteSame', 'ignoreMissingSource', 'headers' ),
 678              array( 'src', 'dst' )
 679          );
 680      }
 681  
 682  	protected function doPrecheck( array &$predicates ) {
 683          $status = Status::newGood();
 684          // Check if the source file exists
 685          if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
 686              if ( $this->getParam( 'ignoreMissingSource' ) ) {
 687                  $this->doOperation = false; // no-op
 688                  // Update file existence predicates (cache 404s)
 689                  $predicates['exists'][$this->params['src']] = false;
 690                  $predicates['sha1'][$this->params['src']] = false;
 691  
 692                  return $status; // nothing to do
 693              } else {
 694                  $status->fatal( 'backend-fail-notexists', $this->params['src'] );
 695  
 696                  return $status;
 697              }
 698          // Check if a file can be placed/changed at the destination
 699          } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
 700              $status->fatal( 'backend-fail-usable', $this->params['dst'] );
 701              $status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
 702  
 703              return $status;
 704          }
 705          // Check if destination file exists
 706          $status->merge( $this->precheckDestExistence( $predicates ) );
 707          $this->params['dstExists'] = $this->destExists; // see FileBackendStore::setFileCache()
 708          if ( $status->isOK() ) {
 709              // Update file existence predicates
 710              $predicates['exists'][$this->params['src']] = false;
 711              $predicates['sha1'][$this->params['src']] = false;
 712              $predicates['exists'][$this->params['dst']] = true;
 713              $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 714          }
 715  
 716          return $status; // safe to call attempt()
 717      }
 718  
 719  	protected function doAttempt() {
 720          if ( $this->overwriteSameCase ) {
 721              if ( $this->params['src'] === $this->params['dst'] ) {
 722                  // Do nothing to the destination (which is also the source)
 723                  $status = Status::newGood();
 724              } else {
 725                  // Just delete the source as the destination file needs no changes
 726                  $status = $this->backend->deleteInternal( $this->setFlags(
 727                      array( 'src' => $this->params['src'] )
 728                  ) );
 729              }
 730          } elseif ( $this->params['src'] === $this->params['dst'] ) {
 731              // Just update the destination file headers
 732              $headers = $this->getParam( 'headers' ) ?: array();
 733              $status = $this->backend->describeInternal( $this->setFlags(
 734                  array( 'src' => $this->params['dst'], 'headers' => $headers )
 735              ) );
 736          } else {
 737              // Move the file to the destination
 738              $status = $this->backend->moveInternal( $this->setFlags( $this->params ) );
 739          }
 740  
 741          return $status;
 742      }
 743  
 744  	public function storagePathsRead() {
 745          return array( $this->params['src'] );
 746      }
 747  
 748  	public function storagePathsChanged() {
 749          return array( $this->params['src'], $this->params['dst'] );
 750      }
 751  }
 752  
 753  /**
 754   * Delete a file at the given storage path from the backend.
 755   * Parameters for this operation are outlined in FileBackend::doOperations().
 756   */
 757  class DeleteFileOp extends FileOp {
 758  	protected function allowedParams() {
 759          return array( array( 'src' ), array( 'ignoreMissingSource' ), array( 'src' ) );
 760      }
 761  
 762  	protected function doPrecheck( array &$predicates ) {
 763          $status = Status::newGood();
 764          // Check if the source file exists
 765          if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
 766              if ( $this->getParam( 'ignoreMissingSource' ) ) {
 767                  $this->doOperation = false; // no-op
 768                  // Update file existence predicates (cache 404s)
 769                  $predicates['exists'][$this->params['src']] = false;
 770                  $predicates['sha1'][$this->params['src']] = false;
 771  
 772                  return $status; // nothing to do
 773              } else {
 774                  $status->fatal( 'backend-fail-notexists', $this->params['src'] );
 775  
 776                  return $status;
 777              }
 778          // Check if a file can be placed/changed at the source
 779          } elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
 780              $status->fatal( 'backend-fail-usable', $this->params['src'] );
 781              $status->fatal( 'backend-fail-delete', $this->params['src'] );
 782  
 783              return $status;
 784          }
 785          // Update file existence predicates
 786          $predicates['exists'][$this->params['src']] = false;
 787          $predicates['sha1'][$this->params['src']] = false;
 788  
 789          return $status; // safe to call attempt()
 790      }
 791  
 792  	protected function doAttempt() {
 793          // Delete the source file
 794          return $this->backend->deleteInternal( $this->setFlags( $this->params ) );
 795      }
 796  
 797  	public function storagePathsChanged() {
 798          return array( $this->params['src'] );
 799      }
 800  }
 801  
 802  /**
 803   * Change metadata for a file at the given storage path in the backend.
 804   * Parameters for this operation are outlined in FileBackend::doOperations().
 805   */
 806  class DescribeFileOp extends FileOp {
 807  	protected function allowedParams() {
 808          return array( array( 'src' ), array( 'headers' ), array( 'src' ) );
 809      }
 810  
 811  	protected function doPrecheck( array &$predicates ) {
 812          $status = Status::newGood();
 813          // Check if the source file exists
 814          if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
 815              $status->fatal( 'backend-fail-notexists', $this->params['src'] );
 816  
 817              return $status;
 818          // Check if a file can be placed/changed at the source
 819          } elseif ( !$this->backend->isPathUsableInternal( $this->params['src'] ) ) {
 820              $status->fatal( 'backend-fail-usable', $this->params['src'] );
 821              $status->fatal( 'backend-fail-describe', $this->params['src'] );
 822  
 823              return $status;
 824          }
 825          // Update file existence predicates
 826          $predicates['exists'][$this->params['src']] =
 827              $this->fileExists( $this->params['src'], $predicates );
 828          $predicates['sha1'][$this->params['src']] =
 829              $this->fileSha1( $this->params['src'], $predicates );
 830  
 831          return $status; // safe to call attempt()
 832      }
 833  
 834  	protected function doAttempt() {
 835          // Update the source file's metadata
 836          return $this->backend->describeInternal( $this->setFlags( $this->params ) );
 837      }
 838  
 839  	public function storagePathsChanged() {
 840          return array( $this->params['src'] );
 841      }
 842  }
 843  
 844  /**
 845   * Placeholder operation that has no params and does nothing
 846   */
 847  class NullFileOp extends FileOp {
 848  }


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