[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |