Index: branches/FileBackend/phase3/includes/filerepo/file/LocalFile.php |
— | — | @@ -1002,8 +1002,10 @@ |
1003 | 1003 | |
1004 | 1004 | if ( $dbw->affectedRows() == 0 ) { |
1005 | 1005 | $reupload = true; |
1006 | | - |
1007 | | - #if ( !$oldver ) wfDebugDieBacktrace(); |
| 1006 | + if ( $oldver == '' ) { |
| 1007 | + throw new MWException( |
| 1008 | + "Bogus oi_archive_name given. Database and storage out of sync?" ); |
| 1009 | + } |
1008 | 1010 | # Collision, this is an update of a file |
1009 | 1011 | # Insert previous contents into oldimage |
1010 | 1012 | $dbw->insertSelect( 'oldimage', 'image', |
Index: branches/FileBackend/phase3/includes/filerepo/file/TempFSFile.php |
— | — | @@ -45,6 +45,19 @@ |
46 | 46 | } |
47 | 47 | |
48 | 48 | /** |
| 49 | + * Purge this file off the file system |
| 50 | + * |
| 51 | + * @return bool Success |
| 52 | + */ |
| 53 | + public function purge() { |
| 54 | + $this->canDelete = false; // done |
| 55 | + wfSuppressWarnings(); |
| 56 | + $ok = unlink( $this->path ); |
| 57 | + wfRestoreWarnings(); |
| 58 | + return $ok; |
| 59 | + } |
| 60 | + |
| 61 | + /** |
49 | 62 | * Flag to not clean up after the temporary file |
50 | 63 | * |
51 | 64 | * @return void |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php |
— | — | @@ -98,9 +98,10 @@ |
99 | 99 | } |
100 | 100 | |
101 | 101 | $failedOps = array(); // failed ops with ignoreErrors |
| 102 | + $predicates = FileOp::newPredicates(); // account for previous op in prechecks |
102 | 103 | // Do pre-checks for each operation; abort on failure... |
103 | 104 | foreach ( $performOps as $index => $fileOp ) { |
104 | | - $status->merge( $fileOp->precheck() ); |
| 105 | + $status->merge( $fileOp->precheck( $predicates ) ); |
105 | 106 | if ( !$status->isOK() ) { // operation failed? |
106 | 107 | if ( !empty( $ops[$index]['ignoreErrors'] ) ) { |
107 | 108 | $failedOps[$index] = 1; // remember not to call attempt()/finish() |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileOp.php |
— | — | @@ -43,16 +43,26 @@ |
44 | 44 | } |
45 | 45 | |
46 | 46 | /** |
| 47 | + * Get a new empty predicates array for precheck() |
| 48 | + * |
| 49 | + * @return Array |
| 50 | + */ |
| 51 | + final public static function newPredicates() { |
| 52 | + return array( 'exists' => array() ); |
| 53 | + } |
| 54 | + |
| 55 | + /** |
47 | 56 | * Check preconditions of the operation and possibly stash temp files |
48 | 57 | * |
| 58 | + * @param $predicates Array |
49 | 59 | * @return Status |
50 | 60 | */ |
51 | | - final public function precheck() { |
| 61 | + final public function precheck( array &$predicates ) { |
52 | 62 | if ( $this->state !== self::STATE_NEW ) { |
53 | 63 | return Status::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state ); |
54 | 64 | } |
55 | 65 | $this->state = self::STATE_CHECKED; |
56 | | - $status = $this->doPrecheck(); |
| 66 | + $status = $this->doPrecheck( $predicates ); |
57 | 67 | if ( !$status->isOK() ) { |
58 | 68 | $this->failed = true; |
59 | 69 | } |
— | — | @@ -105,6 +115,13 @@ |
106 | 116 | if ( $this->state !== self::STATE_ATTEMPTED ) { |
107 | 117 | return Status::newFatal( 'fileop-fail-state', self::STATE_ATTEMPTED, $this->state ); |
108 | 118 | } |
| 119 | + // Kill any backup files (useful for background scripts) |
| 120 | + if ( isset( $this->tmpDestFile ) ) { |
| 121 | + $this->tmpDestFile->purge(); |
| 122 | + } |
| 123 | + if ( isset( $this->tmpSourceFile ) ) { |
| 124 | + $this->tmpSourceFile->purge(); |
| 125 | + } |
109 | 126 | $this->state = self::STATE_DONE; |
110 | 127 | if ( $this->failed ) { |
111 | 128 | $status = Status::newGood(); // nothing to finish |
— | — | @@ -131,7 +148,7 @@ |
132 | 149 | /** |
133 | 150 | * @return Status |
134 | 151 | */ |
135 | | - abstract protected function doPrecheck(); |
| 152 | + abstract protected function doPrecheck( array &$predicates ); |
136 | 153 | |
137 | 154 | /** |
138 | 155 | * @return Status |
— | — | @@ -146,7 +163,9 @@ |
147 | 164 | /** |
148 | 165 | * @return Status |
149 | 166 | */ |
150 | | - abstract protected function doFinish(); |
| 167 | + protected function doFinish() { |
| 168 | + return Status::newGood(); |
| 169 | + } |
151 | 170 | |
152 | 171 | /** |
153 | 172 | * Backup any file at the source to a temporary file |
— | — | @@ -164,11 +183,6 @@ |
165 | 184 | $status->fatal( 'backend-fail-backup', $this->params['source'] ); |
166 | 185 | return $status; |
167 | 186 | } |
168 | | - } else { |
169 | | - if ( empty( $this->params['ignoreMissingSource'] ) ) { |
170 | | - $status->fatal( 'backend-fail-notexists', $this->params['source'] ); |
171 | | - return $status; |
172 | | - } |
173 | 187 | } |
174 | 188 | return $status; |
175 | 189 | } |
— | — | @@ -178,16 +192,12 @@ |
179 | 193 | * Don't bother backing it up unless we might overwrite the file. |
180 | 194 | * This assumes that the destination is in the backend and that |
181 | 195 | * the source is either in the backend or on the file system. |
| 196 | + * This also handles the 'overwriteSame' check logic. |
182 | 197 | * |
183 | 198 | * @return Status |
184 | 199 | */ |
185 | 200 | protected function checkAndBackupDest() { |
186 | 201 | $status = Status::newGood(); |
187 | | - // Check if a file already exists at the destination |
188 | | - $params = array( 'source' => $this->params['dest'] ); |
189 | | - if ( !$this->backend->fileExists( $params ) ) { |
190 | | - return $status; // nothing to do |
191 | | - } |
192 | 202 | |
193 | 203 | if ( !empty( $this->params['overwriteDest'] ) ) { |
194 | 204 | // Create a temporary backup copy... |
— | — | @@ -250,7 +260,7 @@ |
251 | 261 | } |
252 | 262 | $hash = md5_file( $tmp->getPath() ); |
253 | 263 | } |
254 | | - // Source file is on disk (FS) |
| 264 | + // Source file is on file system |
255 | 265 | } else { |
256 | 266 | $hash = md5_file( $path ); |
257 | 267 | } |
— | — | @@ -298,32 +308,65 @@ |
299 | 309 | } |
300 | 310 | return $status; |
301 | 311 | } |
| 312 | + |
| 313 | + /** |
| 314 | + * Check if a file will exist when this operation is attempted |
| 315 | + * |
| 316 | + * @param $source string |
| 317 | + * @param $predicates Array |
| 318 | + * @return bool |
| 319 | + */ |
| 320 | + final protected function fileExists( $source, $predicates ) { |
| 321 | + if ( isset( $predicates['exists'][$source] ) ) { |
| 322 | + return $predicates['exists'][$source]; // previous op assures this |
| 323 | + } else { |
| 324 | + return $this->backend->fileExists( array( 'source' => $source ) ); |
| 325 | + } |
| 326 | + } |
302 | 327 | } |
303 | 328 | |
304 | 329 | /** |
305 | | - * Store a file into the backend from a file on disk. |
| 330 | + * Store a file into the backend from a file on the file system. |
306 | 331 | * Parameters similar to FileBackend::store(), which include: |
307 | | - * source : source path on disk (FS) |
| 332 | + * source : source path on file system |
308 | 333 | * dest : destination storage path |
309 | 334 | * overwriteDest : do nothing and pass if an identical file exists at destination |
310 | 335 | * overwriteSame : override any existing file at destination |
311 | 336 | */ |
312 | 337 | class StoreFileOp extends FileOp { |
313 | | - protected function doPrecheck() { |
| 338 | + protected function doPrecheck( array &$predicates ) { |
314 | 339 | $status = Status::newGood(); |
315 | | - // Check if the source files exists on disk (FS) |
| 340 | + // Check if destination file exists |
| 341 | + if ( $this->fileExists( $this->params['dest'], $predicates ) ) { |
| 342 | + if ( empty( $this->params['overwriteDest'] ) ) { |
| 343 | + $status->fatal( 'backend-fail-alreadyexists', $this->params['dest'] ); |
| 344 | + return $status; |
| 345 | + } |
| 346 | + } else { |
| 347 | + $this->checkDest = false; |
| 348 | + } |
| 349 | + // Check if the source file exists on the file system |
316 | 350 | if ( !file_exists( $this->params['source'] ) ) { |
317 | 351 | $status->fatal( 'backend-fail-notexists', $this->params['source'] ); |
318 | 352 | return $status; |
319 | 353 | } |
320 | | - // Create a destination backup copy as needed |
321 | | - $status->merge( $this->checkAndBackupDest() ); |
| 354 | + // Update file existence predicates |
| 355 | + $predicates['exists'][$this->params['dest']] = true; |
322 | 356 | return $status; |
323 | 357 | } |
324 | 358 | |
325 | 359 | protected function doAttempt() { |
| 360 | + $status = Status::newGood(); |
| 361 | + // Create a destination backup copy as needed |
| 362 | + if ( $this->checkDest ) { |
| 363 | + $status->merge( $this->checkAndBackupDest() ); |
| 364 | + if ( !$status->isOK() ) { |
| 365 | + return $status; |
| 366 | + } |
| 367 | + } |
326 | 368 | // Store the file at the destination |
327 | | - return $this->backend->store( $this->params ); |
| 369 | + $status->merge( $this->backend->store( $this->params ) ); |
| 370 | + return $status; |
328 | 371 | } |
329 | 372 | |
330 | 373 | protected function doRevert() { |
— | — | @@ -338,10 +381,6 @@ |
339 | 382 | return $status; |
340 | 383 | } |
341 | 384 | |
342 | | - protected function doFinish() { |
343 | | - return Status::newGood(); |
344 | | - } |
345 | | - |
346 | 385 | protected function getSourceMD5() { |
347 | 386 | return md5_file( $this->params['source'] ); |
348 | 387 | } |
— | — | @@ -360,14 +399,34 @@ |
361 | 400 | * overwriteSame : override any existing file at destination |
362 | 401 | */ |
363 | 402 | class CreateFileOp extends FileOp { |
364 | | - protected function doPrecheck() { |
365 | | - // Create a destination backup copy as needed |
366 | | - return $this->checkAndBackupDest(); |
| 403 | + protected function doPrecheck( array &$predicates ) { |
| 404 | + $status = Status::newGood(); |
| 405 | + // Check if destination file exists |
| 406 | + if ( $this->fileExists( $this->params['dest'], $predicates ) ) { |
| 407 | + if ( empty( $this->params['overwriteDest'] ) ) { |
| 408 | + $status->fatal( 'backend-fail-alreadyexists', $this->params['dest'] ); |
| 409 | + return $status; |
| 410 | + } |
| 411 | + } else { |
| 412 | + $this->checkDest = false; |
| 413 | + } |
| 414 | + // Update file existence predicates |
| 415 | + $predicates['exists'][$this->params['dest']] = true; |
| 416 | + return $status; |
367 | 417 | } |
368 | 418 | |
369 | 419 | protected function doAttempt() { |
| 420 | + $status = Status::newGood(); |
| 421 | + // Create a destination backup copy as needed |
| 422 | + if ( $this->checkDest ) { |
| 423 | + $status->merge( $this->checkAndBackupDest() ); |
| 424 | + if ( !$status->isOK() ) { |
| 425 | + return $status; |
| 426 | + } |
| 427 | + } |
370 | 428 | // Create the file at the destination |
371 | | - return $this->backend->create( $this->params ); |
| 429 | + $status->merge( $this->backend->create( $this->params ) ); |
| 430 | + return $status; |
372 | 431 | } |
373 | 432 | |
374 | 433 | protected function doRevert() { |
— | — | @@ -382,10 +441,6 @@ |
383 | 442 | return $status; |
384 | 443 | } |
385 | 444 | |
386 | | - protected function doFinish() { |
387 | | - return Status::newGood(); |
388 | | - } |
389 | | - |
390 | 445 | protected function getSourceMD5() { |
391 | 446 | return md5( $this->params['content'] ); |
392 | 447 | } |
— | — | @@ -404,28 +459,48 @@ |
405 | 460 | * overwriteSame : override any existing file at destination |
406 | 461 | */ |
407 | 462 | class CopyFileOp extends FileOp { |
408 | | - protected function doPrecheck() { |
| 463 | + protected $checkDest = true; |
| 464 | + |
| 465 | + protected function doPrecheck( array &$predicates ) { |
409 | 466 | $status = Status::newGood(); |
410 | | - // Check if the source files exists on disk |
411 | | - $params = array( 'source' => $this->params['source'] ); |
412 | | - if ( !$this->backend->fileExists( $params ) ) { |
| 467 | + // Check if destination file exists |
| 468 | + if ( $this->fileExists( $this->params['dest'], $predicates ) ) { |
| 469 | + if ( empty( $this->params['overwriteDest'] ) ) { |
| 470 | + $status->fatal( 'backend-fail-alreadyexists', $this->params['dest'] ); |
| 471 | + return $status; |
| 472 | + } |
| 473 | + } else { |
| 474 | + $this->checkDest = false; |
| 475 | + } |
| 476 | + // Check if the source file exists |
| 477 | + if ( !$this->fileExists( $this->params['source'], $predicates ) ) { |
413 | 478 | $status->fatal( 'backend-fail-notexists', $this->params['source'] ); |
414 | 479 | return $status; |
415 | 480 | } |
416 | | - // Create a destination backup copy as needed |
417 | | - $status->merge( $this->checkAndBackupDest() ); |
| 481 | + // Update file existence predicates |
| 482 | + $predicates['exists'][$this->params['dest']] = true; |
418 | 483 | return $status; |
419 | 484 | } |
420 | 485 | |
421 | 486 | protected function doAttempt() { |
| 487 | + $status = Status::newGood(); |
| 488 | + // Create a destination backup copy as needed |
| 489 | + if ( $this->checkDest ) { |
| 490 | + $status->merge( $this->checkAndBackupDest() ); |
| 491 | + if ( !$status->isOK() ) { |
| 492 | + return $status; |
| 493 | + } |
| 494 | + } |
422 | 495 | // Copy the file into the destination |
423 | | - return $this->backend->copy( $this->params ); |
| 496 | + $status->merge( $this->backend->copy( $this->params ) ); |
| 497 | + return $status; |
424 | 498 | } |
425 | 499 | |
426 | 500 | protected function doRevert() { |
| 501 | + $status = Status::newGood(); |
427 | 502 | // Remove the file saved to the destination |
428 | 503 | $params = array( 'source' => $this->params['dest'] ); |
429 | | - $status = $this->backend->delete( $params ); |
| 504 | + $status->merge( $this->backend->delete( $params ) ); |
430 | 505 | if ( !$status->isOK() ) { |
431 | 506 | return $status; // also can't restore any dest file |
432 | 507 | } |
— | — | @@ -434,10 +509,6 @@ |
435 | 510 | return $status; |
436 | 511 | } |
437 | 512 | |
438 | | - protected function doFinish() { |
439 | | - return Status::newGood(); |
440 | | - } |
441 | | - |
442 | 513 | protected function getSourceMD5() { |
443 | 514 | return $this->getFileMD5( $this->params['source'] ); |
444 | 515 | } |
— | — | @@ -457,42 +528,57 @@ |
458 | 529 | */ |
459 | 530 | class MoveFileOp extends FileOp { |
460 | 531 | protected $usingMove = false; // using backend move() function? |
| 532 | + protected $checkDest = true; |
461 | 533 | |
462 | 534 | function initialize() { |
463 | 535 | // Use faster, native, move() if applicable |
464 | 536 | $this->usingMove = $this->backend->canMove( $this->params ); |
465 | 537 | } |
466 | 538 | |
467 | | - protected function doPrecheck() { |
| 539 | + protected function doPrecheck( array &$predicates ) { |
468 | 540 | $status = Status::newGood(); |
469 | | - // Check if the source files exists on disk |
470 | | - $params = array( 'source' => $this->params['source'] ); |
471 | | - if ( !$this->backend->fileExists( $params ) ) { |
| 541 | + // Check if destination file exists |
| 542 | + if ( $this->fileExists( $this->params['dest'], $predicates ) ) { |
| 543 | + if ( empty( $this->params['overwriteDest'] ) ) { |
| 544 | + $status->fatal( 'backend-fail-alreadyexists', $this->params['dest'] ); |
| 545 | + return $status; |
| 546 | + } |
| 547 | + } else { |
| 548 | + $this->checkDest = false; |
| 549 | + } |
| 550 | + // Check if the source file exists |
| 551 | + if ( !$this->fileExists( $this->params['source'], $predicates ) ) { |
472 | 552 | $status->fatal( 'backend-fail-notexists', $this->params['source'] ); |
473 | 553 | return $status; |
474 | 554 | } |
475 | | - // Create a destination backup copy as needed |
476 | | - $status->merge( $this->checkAndBackupDest() ); |
477 | | - if ( !$status->isOK() ) { |
478 | | - return $status; |
479 | | - } |
| 555 | + // Update file existence predicates |
| 556 | + $predicates['exists'][$this->params['source']] = false; |
| 557 | + $predicates['exists'][$this->params['dest']] = true; |
480 | 558 | return $status; |
481 | 559 | } |
482 | 560 | |
483 | 561 | protected function doAttempt() { |
| 562 | + $status = Status::newGood(); |
| 563 | + // Create a destination backup copy as needed |
| 564 | + if ( $this->checkDest ) { |
| 565 | + $status->merge( $this->checkAndBackupDest() ); |
| 566 | + if ( !$status->isOK() ) { |
| 567 | + return $status; |
| 568 | + } |
| 569 | + } |
484 | 570 | // Native moves: move the file into the destination |
485 | 571 | if ( $this->usingMove ) { |
486 | | - $status = $this->backend->move( $this->params ); |
| 572 | + $status->merge( $this->backend->move( $this->params ) ); |
487 | 573 | // Non-native moves: copy the file into the destination & delete source |
488 | 574 | } else { |
489 | 575 | // Copy source to dest |
490 | | - $status = $this->backend->copy( $this->params ); |
| 576 | + $status->merge( $this->backend->copy( $this->params ) ); |
491 | 577 | if ( !$status->isOK() ) { |
492 | 578 | return $status; |
493 | 579 | } |
494 | 580 | // Delete source |
495 | 581 | $params = array( 'source' => $this->params['source'] ); |
496 | | - $status = $this->backend->delete( $params ); |
| 582 | + $status->merge( $this->backend->delete( $params ) ); |
497 | 583 | if ( !$status->isOK() ) { |
498 | 584 | return $status; |
499 | 585 | } |
— | — | @@ -501,13 +587,14 @@ |
502 | 588 | } |
503 | 589 | |
504 | 590 | protected function doRevert() { |
| 591 | + $status = Status::newGood(); |
505 | 592 | // Native moves: move the file back to the source |
506 | 593 | if ( $this->usingMove ) { |
507 | 594 | $params = array( |
508 | 595 | 'source' => $this->params['dest'], |
509 | 596 | 'dest' => $this->params['source'] |
510 | 597 | ); |
511 | | - $status = $this->backend->move( $params ); |
| 598 | + $status->merge( $this->backend->move( $params ) ); |
512 | 599 | if ( !$status->isOK() ) { |
513 | 600 | return $status; // also can't restore any dest file |
514 | 601 | } |
— | — | @@ -531,10 +618,6 @@ |
532 | 619 | return $status; |
533 | 620 | } |
534 | 621 | |
535 | | - protected function doFinish() { |
536 | | - return Status::newGood(); |
537 | | - } |
538 | | - |
539 | 622 | protected function getSourceMD5() { |
540 | 623 | return $this->getFileMD5( $this->params['source'] ); |
541 | 624 | } |
— | — | @@ -550,17 +633,45 @@ |
551 | 634 | * sources : ordered source storage paths (e.g. chunk1,chunk2,...) |
552 | 635 | * dest : destination storage path |
553 | 636 | * overwriteDest : do nothing and pass if an identical file exists at destination |
554 | | - * overwriteSame : override any existing file at destination |
555 | 637 | */ |
556 | 638 | class ConcatenateFileOp extends FileOp { |
557 | | - protected function doPrecheck() { |
558 | | - // Create a destination backup copy as needed |
559 | | - return $this->checkAndBackupDest(); |
| 639 | + protected $checkDest = true; |
| 640 | + |
| 641 | + protected function doPrecheck( array &$predicates ) { |
| 642 | + $status = Status::newGood(); |
| 643 | + // Check if destination file exists |
| 644 | + if ( $this->fileExists( $this->params['dest'], $predicates ) ) { |
| 645 | + if ( empty( $this->params['overwriteDest'] ) ) { |
| 646 | + $status->fatal( 'backend-fail-alreadyexists', $this->params['dest'] ); |
| 647 | + return $status; |
| 648 | + } |
| 649 | + } else { |
| 650 | + $this->checkDest = false; |
| 651 | + } |
| 652 | + // Check that source files exists |
| 653 | + foreach ( $this->params['sources'] as $source ) { |
| 654 | + if ( !$this->fileExists( $source, $predicates ) ) { |
| 655 | + $status->fatal( 'backend-fail-notexists', $source ); |
| 656 | + return $status; |
| 657 | + } |
| 658 | + } |
| 659 | + // Update file existence predicates |
| 660 | + $predicates['exists'][$this->params['dest']] = true; |
| 661 | + return $status; |
560 | 662 | } |
561 | 663 | |
562 | 664 | protected function doAttempt() { |
| 665 | + $status = Status::newGood(); |
| 666 | + // Create a destination backup copy as needed |
| 667 | + if ( $this->checkDest ) { |
| 668 | + $status->merge( $this->checkAndBackupDest() ); |
| 669 | + if ( !$status->isOK() ) { |
| 670 | + return $status; |
| 671 | + } |
| 672 | + } |
563 | 673 | // Concatenate the file at the destination |
564 | | - return $this->backend->concatenate( $this->params ); |
| 674 | + $status->merge( $this->backend->concatenate( $this->params ) ); |
| 675 | + return $status; |
565 | 676 | } |
566 | 677 | |
567 | 678 | protected function doRevert() { |
— | — | @@ -575,10 +686,6 @@ |
576 | 687 | return $status; |
577 | 688 | } |
578 | 689 | |
579 | | - protected function doFinish() { |
580 | | - return Status::newGood(); |
581 | | - } |
582 | | - |
583 | 690 | protected function getSourceMD5() { |
584 | 691 | return null; // defer this until we finish building the new file |
585 | 692 | } |
— | — | @@ -595,21 +702,38 @@ |
596 | 703 | * ignoreMissingSource : don't return an error if the file does not exist |
597 | 704 | */ |
598 | 705 | class DeleteFileOp extends FileOp { |
599 | | - protected function doPrecheck() { |
| 706 | + protected $needsDelete = true; |
| 707 | + |
| 708 | + protected function doPrecheck( array &$predicates ) { |
600 | 709 | $status = Status::newGood(); |
601 | | - $params = array( 'source' => $this->params['source'] ); |
602 | | - if ( $this->backend->fileExists( $params ) ) { |
603 | | - // Create a source backup copy as needed |
604 | | - $status->merge( $this->backupSource() ); |
605 | | - } elseif ( empty( $this->params['ignoreMissingSource'] ) ) { |
606 | | - $status->fatal( 'backend-fail-notexists', $this->params['source'] ); |
| 710 | + // Check if the source file exists |
| 711 | + if ( !$this->fileExists( $this->params['source'], $predicates ) ) { |
| 712 | + if ( empty( $this->params['ignoreMissingSource'] ) ) { |
| 713 | + $status->fatal( 'backend-fail-notexists', $this->params['source'] ); |
| 714 | + return $status; |
| 715 | + } |
| 716 | + $this->needsDelete = false; |
607 | 717 | } |
| 718 | + // Update file existence predicates |
| 719 | + $predicates['exists'][$this->params['source']] = false; |
608 | 720 | return $status; |
609 | 721 | } |
610 | 722 | |
611 | 723 | protected function doAttempt() { |
612 | | - // Delete the source file |
613 | | - return $this->backend->delete( $this->params ); |
| 724 | + $status = Status::newGood(); |
| 725 | + if ( $this->needsDelete ) { |
| 726 | + // Create a source backup copy as needed |
| 727 | + $status->merge( $this->backupSource() ); |
| 728 | + if ( !$status->isOK() ) { |
| 729 | + return $status; |
| 730 | + } |
| 731 | + // Delete the source file |
| 732 | + $status->merge( $this->backend->delete( $this->params ) ); |
| 733 | + if ( !$status->isOK() ) { |
| 734 | + return $status; |
| 735 | + } |
| 736 | + } |
| 737 | + return $status; |
614 | 738 | } |
615 | 739 | |
616 | 740 | protected function doRevert() { |
— | — | @@ -617,10 +741,6 @@ |
618 | 742 | return $this->restoreSource(); |
619 | 743 | } |
620 | 744 | |
621 | | - protected function doFinish() { |
622 | | - return Status::newGood(); |
623 | | - } |
624 | | - |
625 | 745 | function storagePathsUsed() { |
626 | 746 | return array( $this->params['source'] ); |
627 | 747 | } |
— | — | @@ -630,7 +750,7 @@ |
631 | 751 | * Placeholder operation that has no params and does nothing |
632 | 752 | */ |
633 | 753 | class NullFileOp extends FileOp { |
634 | | - protected function doPrecheck() { |
| 754 | + protected function doPrecheck( array &$predicates ) { |
635 | 755 | return Status::newGood(); |
636 | 756 | } |
637 | 757 | |
— | — | @@ -641,8 +761,4 @@ |
642 | 762 | protected function doRevert() { |
643 | 763 | return Status::newGood(); |
644 | 764 | } |
645 | | - |
646 | | - protected function doFinish() { |
647 | | - return Status::newGood(); |
648 | | - } |
649 | 765 | } |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php |
— | — | @@ -43,7 +43,7 @@ |
44 | 44 | return $status; |
45 | 45 | } |
46 | 46 | if ( is_file( $dest ) ) { |
47 | | - if ( isset( $params['overwriteDest'] ) ) { |
| 47 | + if ( !empty( $params['overwriteDest'] ) ) { |
48 | 48 | wfSuppressWarnings(); |
49 | 49 | $ok = unlink( $dest ); |
50 | 50 | wfRestoreWarnings(); |
— | — | @@ -107,7 +107,7 @@ |
108 | 108 | } |
109 | 109 | |
110 | 110 | if ( file_exists( $dest ) ) { |
111 | | - if ( isset( $params['overwriteDest'] ) ) { |
| 111 | + if ( !empty( $params['overwriteDest'] ) ) { |
112 | 112 | // Windows does not support moving over existing files |
113 | 113 | if ( wfIsWindows() ) { |
114 | 114 | wfSuppressWarnings(); |
— | — | @@ -131,6 +131,7 @@ |
132 | 132 | |
133 | 133 | wfSuppressWarnings(); |
134 | 134 | $ok = rename( $source, $dest ); |
| 135 | + clearstatcache(); // file no longer at source |
135 | 136 | wfRestoreWarnings(); |
136 | 137 | if ( !$ok ) { |
137 | 138 | $status->fatal( 'backend-fail-move', $params['source'], $params['dest'] ); |
— | — | @@ -149,7 +150,7 @@ |
150 | 151 | return $status; |
151 | 152 | } |
152 | 153 | |
153 | | - if ( !file_exists( $source ) ) { |
| 154 | + if ( !is_file( $source ) ) { |
154 | 155 | if ( empty( $params['ignoreMissingSource'] ) ) { |
155 | 156 | $status->fatal( 'backend-fail-delete', $params['source'] ); |
156 | 157 | } |
— | — | @@ -270,7 +271,7 @@ |
271 | 272 | } |
272 | 273 | |
273 | 274 | if ( file_exists( $dest ) ) { |
274 | | - if ( isset( $params['overwriteDest'] ) ) { |
| 275 | + if ( !empty( $params['overwriteDest'] ) ) { |
275 | 276 | wfSuppressWarnings(); |
276 | 277 | $ok = unlink( $dest ); |
277 | 278 | wfRestoreWarnings(); |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php |
— | — | @@ -432,9 +432,10 @@ |
433 | 433 | } |
434 | 434 | |
435 | 435 | $failedOps = array(); // failed ops with ignoreErrors |
| 436 | + $predicates = FileOp::newPredicates(); // account for previous op in prechecks |
436 | 437 | // Do pre-checks for each operation; abort on failure... |
437 | 438 | foreach ( $performOps as $index => $fileOp ) { |
438 | | - $status->merge( $fileOp->precheck() ); |
| 439 | + $status->merge( $fileOp->precheck( $predicates ) ); |
439 | 440 | if ( !$status->isOK() ) { // operation failed? |
440 | 441 | if ( !empty( $ops[$index]['ignoreErrors'] ) ) { |
441 | 442 | $failedOps[$index] = 1; // remember not to call attempt()/finish() |
Index: branches/FileBackend/phase3/languages/messages/MessagesEn.php |
— | — | @@ -2253,6 +2253,23 @@ |
2254 | 2254 | 'uploadstash-errclear' => 'Clearing the files was unsuccessful.', |
2255 | 2255 | 'uploadstash-refresh' => 'Refresh the list of files', |
2256 | 2256 | |
| 2257 | +# file backend |
| 2258 | +'backend-fail-stream' => 'Could not stream file $1.', |
| 2259 | +'backend-fail-backup' => 'Could not backup file $1.', |
| 2260 | +'backend-fail-notexists' => 'The file $1 does not exist.', |
| 2261 | +'backend-fail-hashes' => 'Could not get file hashes for comparison.', |
| 2262 | +'backend-fail-notsame' => 'A non-identical file already exists at $1.', |
| 2263 | +'backend-fail-invalidpath' => '$1 is not a valid storage path.', |
| 2264 | +'backend-fail-delete' => 'Could not delete file $1.', |
| 2265 | +'backend-fail-alreadyexists' => 'The file $1 already exists.', |
| 2266 | +'backend-fail-copy' => 'Could not copy file $1 to $2', |
| 2267 | +'backend-fail-move' => 'Could not move file $1 to $2', |
| 2268 | +'backend-fail-opentemp' => 'Could not open temporary file.', |
| 2269 | +'backend-fail-writetemp' => 'Could not write to temporary file.', |
| 2270 | +'backend-fail-closetemp' => 'Could not close temporary file.', |
| 2271 | +'backend-fail-read' => 'Could not read file $1', |
| 2272 | +'backend-fail-create' => 'Could not create file $1', |
| 2273 | + |
2257 | 2274 | # img_auth script messages |
2258 | 2275 | 'img-auth-accessdenied' => 'Access denied', |
2259 | 2276 | 'img-auth-nopathinfo' => 'Missing PATH_INFO. |