r109009 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r109008‎ | r109009 | r109010 >
Date:22:45, 15 January 2012
Author:aaron
Status:ok
Tags:filebackend 
Comment:
In FileOp/FileBackend:
* Added SHA-1 to FileOp::attemptBatch $predicates (since concatenate was removed from FileOp). This lets us get rid of temp file backups, as the remaining failure case is just the backend medium going down (the ops would break in that case anyway). Doing so reduces RTTs and backup file I/O overhead. This also simplifies expiry object support by avoiding having to stash the expiry values of temp backup objects somewhere.
* Improved precheck() and attempt() status logic in FileOp::attemptBatch() a bit. Use separate $subStatus var to check if each op is OK.
* A few minor code cleanups and comment tweaks.
* Fixed MoveFileOp bug found in unit tests when the source/dest are the same and an overwriteDest/overwriteSame param is given (the file would just get deleted). Improved CopyFileOp in this case too.
Other:
* Added more unit tests.
Modified paths:
  • /trunk/phase3/includes/filerepo/backend/FileBackend.php (modified) (history)
  • /trunk/phase3/includes/filerepo/backend/FileOp.php (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/filerepo/FileBackendTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/tests/phpunit/includes/filerepo/FileBackendTest.php
@@ -760,8 +760,91 @@
761761
762762 // @TODO: testSecure
763763
764 - // @TODO: testDoOperations
 764+ public function testDoOperations() {
 765+ $this->backend = $this->singleBackend;
 766+ $this->doTestDoOperations();
765767
 768+ $this->backend = $this->multiBackend;
 769+ $this->doTestDoOperations();
 770+ }
 771+
 772+ function doTestDoOperations() {
 773+ $base = $this->baseStorePath();
 774+
 775+ $fileA = "$base/cont1/a/b/fileA.txt";
 776+ $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
 777+ $fileB = "$base/cont1/a/b/fileB.txt";
 778+ $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
 779+ $fileC = "$base/cont1/a/b/fileC.txt";
 780+ $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
 781+ $fileD = "$base/cont1/a/b/fileD.txt";
 782+
 783+ $this->pathsToPrune[] = $fileA;
 784+ $this->pathsToPrune[] = $fileB;
 785+ $this->pathsToPrune[] = $fileC;
 786+ $this->pathsToPrune[] = $fileD;
 787+
 788+ $this->backend->prepare( array( 'dir' => dirname( $fileA ) ) );
 789+ $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
 790+ $this->backend->prepare( array( 'dir' => dirname( $fileB ) ) );
 791+ $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
 792+ $this->backend->prepare( array( 'dir' => dirname( $fileC ) ) );
 793+ $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
 794+
 795+ $status = $this->backend->doOperations( array(
 796+ array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwriteDest' => 1 ),
 797+ // Now: A:<A>, B:<B>, C:<A>, D:<D> (file:<orginal contents>)
 798+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ),
 799+ // Now: A:<A>, B:<B>, C:<A>, D:<D>
 800+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwriteDest' => 1 ),
 801+ // Now: A:<A>, B:<B>, C:<empty>, D:<A>
 802+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ),
 803+ // Now: A:<A>, B:<empty>, C:<B>, D:<A>
 804+ array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ),
 805+ // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
 806+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwriteDest' => 1 ),
 807+ // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
 808+ array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ),
 809+ // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
 810+ array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ),
 811+ // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
 812+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteDest' => 1 ),
 813+ // Does nothing
 814+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
 815+ // Does nothing
 816+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteDest' => 1 ),
 817+ // Does nothing
 818+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
 819+ // Does nothing
 820+ ) );
 821+
 822+ $this->assertEquals( array(), $status->errors, "Operation batch succeeded" );
 823+ $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
 824+ $this->assertEquals( 12, count( $status->success ),
 825+ "Operation batch has correct success array" );
 826+
 827+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ),
 828+ "File does not exist at $fileA" );
 829+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
 830+ "File does not exist at $fileB" );
 831+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
 832+ "File does not exist at $fileD" );
 833+
 834+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
 835+ "File exists at $fileC" );
 836+ $this->assertEquals( $fileBContents,
 837+ $this->backend->getFileContents( array( 'src' => $fileC ) ),
 838+ "Correct file contents of $fileC" );
 839+ $this->assertEquals( strlen( $fileBContents ),
 840+ $this->backend->getFileSize( array( 'src' => $fileC ) ),
 841+ "Correct file size of $fileC" );
 842+ $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
 843+ $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ),
 844+ "Correct file SHA-1 of $fileC" );
 845+
 846+ // @TODO: test some cases where the ops should fail
 847+ }
 848+
766849 public function testGetFileList() {
767850 $this->backend = $this->singleBackend;
768851 $this->doTestGetFileList();
Index: trunk/phase3/includes/filerepo/backend/FileOp.php
@@ -17,25 +17,22 @@
1818 * @since 1.19
1919 */
2020 abstract class FileOp {
21 - /** $var Array */
 21+ /** @var Array */
2222 protected $params = array();
23 - /** $var FileBackendBase */
 23+ /** @var FileBackendBase */
2424 protected $backend;
25 - /** @var TempFSFile|null */
26 - protected $tmpSourceFile, $tmpDestFile;
2725
2826 protected $state = self::STATE_NEW; // integer
2927 protected $failed = false; // boolean
30 - protected $useBackups = true; // boolean
3128 protected $useLatest = true; // boolean
32 - protected $destSameAsSource = false; // boolean
33 - protected $destAlreadyExists = false; // boolean
3429
 30+ protected $sourceSha1; // string
 31+ protected $destSameAsSource; // boolean
 32+
3533 /* Object life-cycle */
3634 const STATE_NEW = 1;
3735 const STATE_CHECKED = 2;
3836 const STATE_ATTEMPTED = 3;
39 - const STATE_DONE = 4;
4037
4138 /**
4239 * Build a new file operation transaction
@@ -54,18 +51,7 @@
5552 }
5653
5754 /**
58 - * Disable file backups for this operation
59 - *
60 - * @return void
61 - */
62 - final protected function disableBackups() {
63 - $this->useBackups = false;
64 - }
65 -
66 - /**
6755 * Allow stale data for file reads and existence checks.
68 - * If this is called, then disableBackups() should also be called
69 - * unless the affected files are known to have not changed recently.
7056 *
7157 * @return void
7258 */
@@ -91,81 +77,57 @@
9278 final public static function attemptBatch( array $performOps, array $opts ) {
9379 $status = Status::newGood();
9480
95 - $allowStale = isset( $opts['allowStale'] ) && $opts['allowStale'];
96 - $ignoreErrors = isset( $opts['force'] ) && $opts['force'];
 81+ $allowStale = !empty( $opts['allowStale'] );
 82+ $ignoreErrors = !empty( $opts['force'] );
 83+
9784 $predicates = FileOp::newPredicates(); // account for previous op in prechecks
9885 // Do pre-checks for each operation; abort on failure...
9986 foreach ( $performOps as $index => $fileOp ) {
10087 if ( $allowStale ) {
10188 $fileOp->allowStaleReads(); // allow potentially stale reads
10289 }
103 - $status->merge( $fileOp->precheck( $predicates ) );
104 - if ( !$status->isOK() ) { // operation failed?
105 - if ( $ignoreErrors ) {
106 - ++$status->failCount;
107 - $status->success[$index] = false;
108 - } else {
109 - return $status;
 90+ $subStatus = $fileOp->precheck( $predicates );
 91+ $status->merge( $subStatus );
 92+ if ( !$subStatus->isOK() ) { // operation failed?
 93+ $status->success[$index] = false;
 94+ ++$status->failCount;
 95+ if ( !$ignoreErrors ) {
 96+ return $status; // abort
11097 }
11198 }
11299 }
113100
114 - // Attempt each operation; abort on failure...
 101+ // Attempt each operation...
115102 foreach ( $performOps as $index => $fileOp ) {
116103 if ( $fileOp->failed() ) {
117104 continue; // nothing to do
118 - } elseif ( $ignoreErrors ) {
119 - $fileOp->disableBackups(); // no chance of revert() calls
120105 }
121 - $status->merge( $fileOp->attempt() );
122 - if ( !$status->isOK() ) { // operation failed?
123 - if ( $ignoreErrors ) {
124 - ++$status->failCount;
125 - $status->success[$index] = false;
126 - } else {
127 - // Revert everything done so far and abort.
128 - // Do this by reverting each previous operation in reverse order.
129 - $pos = $index - 1; // last one failed; no need to revert()
130 - while ( $pos >= 0 ) {
131 - if ( !$performOps[$pos]->failed() ) {
132 - $status->merge( $performOps[$pos]->revert() );
133 - }
134 - $pos--;
135 - }
136 - return $status;
137 - }
138 - }
139 - }
140 -
141 - $wasOk = $status->isOK();
142 - // Finish each operation...
143 - foreach ( $performOps as $index => $fileOp ) {
144 - if ( $fileOp->failed() ) {
145 - continue; // nothing to do
146 - }
147 - $subStatus = $fileOp->finish();
 106+ $subStatus = $fileOp->attempt();
 107+ $status->merge( $subStatus );
148108 if ( $subStatus->isOK() ) {
149 - ++$status->successCount;
150109 $status->success[$index] = true;
 110+ ++$status->successCount;
151111 } else {
152 - ++$status->failCount;
153112 $status->success[$index] = false;
 113+ ++$status->failCount;
 114+ if ( !$ignoreErrors ) {
 115+ // Log remaining ops as failed for recovery...
 116+ for ( $i = ($index + 1); $i < count( $performOps ); $i++ ) {
 117+ $performOps[$i]->logFailure( 'attempt_aborted' );
 118+ }
 119+ return $status; // bail out
 120+ }
154121 }
155 - $status->merge( $subStatus );
156122 }
157123
158 - // Make sure status is OK, despite any finish() fatals
159 - $status->setResult( $wasOk, $status->value );
160 -
161124 return $status;
162125 }
163126
164127 /**
165 - * Get the value of the parameter with the given name.
166 - * Returns null if the parameter is not set.
 128+ * Get the value of the parameter with the given name
167129 *
168130 * @param $name string
169 - * @return mixed
 131+ * @return mixed Returns null if the parameter is not set
170132 */
171133 final public function getParam( $name ) {
172134 return isset( $this->params[$name] ) ? $this->params[$name] : null;
@@ -173,6 +135,7 @@
174136
175137 /**
176138 * Check if this operation failed precheck() or attempt()
 139+ *
177140 * @return type
178141 */
179142 final public function failed() {
@@ -185,7 +148,7 @@
186149 * @return Array
187150 */
188151 final public static function newPredicates() {
189 - return array( 'exists' => array() );
 152+ return array( 'exists' => array(), 'sha1' => array() );
190153 }
191154
192155 /**
@@ -227,45 +190,6 @@
228191 }
229192
230193 /**
231 - * Revert the operation; affected files are restored
232 - *
233 - * @return Status
234 - */
235 - final public function revert() {
236 - if ( $this->state !== self::STATE_ATTEMPTED ) {
237 - return Status::newFatal( 'fileop-fail-state', self::STATE_ATTEMPTED, $this->state );
238 - }
239 - $this->state = self::STATE_DONE;
240 - if ( $this->failed ) {
241 - $status = Status::newGood(); // nothing to revert
242 - } else {
243 - $status = $this->doRevert();
244 - if ( !$status->isOK() ) {
245 - $this->logFailure( 'revert' );
246 - }
247 - }
248 - return $status;
249 - }
250 -
251 - /**
252 - * Finish the operation; this may be irreversible
253 - *
254 - * @return Status
255 - */
256 - final public function finish() {
257 - if ( $this->state !== self::STATE_ATTEMPTED ) {
258 - return Status::newFatal( 'fileop-fail-state', self::STATE_ATTEMPTED, $this->state );
259 - }
260 - $this->state = self::STATE_DONE;
261 - if ( $this->failed ) {
262 - $status = Status::newGood(); // nothing to finish
263 - } else {
264 - $status = $this->doFinish();
265 - }
266 - return $status;
267 - }
268 -
269 - /**
270194 * Get a list of storage paths read from for this operation
271195 *
272196 * @return Array
@@ -303,189 +227,55 @@
304228 abstract protected function doAttempt();
305229
306230 /**
307 - * @return Status
308 - */
309 - abstract protected function doRevert();
310 -
311 - /**
312 - * @return Status
313 - */
314 - protected function doFinish() {
315 - return Status::newGood();
316 - }
317 -
318 - /**
319 - * Check if the destination file exists and update the
320 - * destAlreadyExists member variable. A bad status will
321 - * be returned if there is no chance it can be overwritten.
 231+ * Check for errors with regards to the destination file already existing.
 232+ * This also updates the destSameAsSource and sourceSha1 member variables.
 233+ * A bad status will be returned if there is no chance it can be overwritten.
322234 *
323235 * @param $predicates Array
324236 * @return Status
325237 */
326238 protected function precheckDestExistence( array $predicates ) {
327239 $status = Status::newGood();
328 - if ( $this->fileExists( $this->params['dst'], $predicates ) ) {
329 - $this->destAlreadyExists = true;
330 - if ( !$this->getParam( 'overwriteDest' ) && !$this->getParam( 'overwriteSame' ) ) {
331 - $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
332 - return $status;
333 - }
334 - } else {
335 - $this->destAlreadyExists = false;
 240+ // Get hash of source file/string and the destination file
 241+ $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string
 242+ if ( $this->sourceSha1 === null ) { // file in storage?
 243+ $this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
336244 }
337 - return $status;
338 - }
339 -
340 - /**
341 - * Backup any file at the source to a temporary file
342 - *
343 - * @return Status
344 - */
345 - protected function backupSource() {
346 - $status = Status::newGood();
347 - if ( $this->useBackups ) {
348 - // Check if a file already exists at the source...
349 - $params = array( 'src' => $this->params['src'], 'latest' => $this->useLatest );
350 - if ( $this->backend->fileExists( $params ) ) {
351 - // Create a temporary backup copy...
352 - $this->tmpSourcePath = $this->backend->getLocalCopy( $params );
353 - if ( $this->tmpSourcePath === null ) {
354 - $status->fatal( 'backend-fail-backup', $this->params['src'] );
355 - return $status;
356 - }
357 - }
358 - }
359 - return $status;
360 - }
361 -
362 - /**
363 - * Backup the file at the destination to a temporary file.
364 - * Don't bother backing it up unless we might overwrite the file.
365 - * This assumes that the destination is in the backend and that
366 - * the source is either in the backend or on the file system.
367 - * This also handles the 'overwriteSame' check logic and updates
368 - * the destSameAsSource member variable.
369 - *
370 - * @return Status
371 - */
372 - protected function checkAndBackupDest() {
373 - $status = Status::newGood();
374245 $this->destSameAsSource = false;
375 -
376 - if ( $this->getParam( 'overwriteDest' ) ) {
377 - if ( $this->useBackups ) {
378 - // Create a temporary backup copy...
379 - $params = array( 'src' => $this->params['dst'], 'latest' => $this->useLatest );
380 - $this->tmpDestFile = $this->backend->getLocalCopy( $params );
381 - if ( !$this->tmpDestFile ) {
382 - $status->fatal( 'backend-fail-backup', $this->params['dst'] );
383 - return $status;
384 - }
385 - }
386 - } elseif ( $this->getParam( 'overwriteSame' ) ) {
387 - $shash = $this->getSourceSha1Base36();
388 - // If there is a single source, then we can do some checks already.
389 - // For things like concatenate(), we would need to build a temp file
390 - // first and thus don't support 'overwriteSame' ($shash is null).
391 - if ( $shash !== null ) {
392 - $dhash = $this->getFileSha1Base36( $this->params['dst'] );
393 - if ( !strlen( $shash ) || !strlen( $dhash ) ) {
 246+ if ( $this->fileExists( $this->params['dst'], $predicates ) ) {
 247+ if ( $this->getParam( 'overwriteDest' ) ) {
 248+ return $status; // OK
 249+ } elseif ( $this->getParam( 'overwriteSame' ) ) {
 250+ $dhash = $this->fileSha1( $this->params['dst'], $predicates );
 251+ // Check if hashes are valid and match each other...
 252+ if ( !strlen( $this->sourceSha1 ) || !strlen( $dhash ) ) {
394253 $status->fatal( 'backend-fail-hashes' );
395 - } elseif ( $shash !== $dhash ) {
 254+ } elseif ( $this->sourceSha1 !== $dhash ) {
396255 // Give an error if the files are not identical
397256 $status->fatal( 'backend-fail-notsame', $this->params['dst'] );
398257 } else {
399 - $this->destSameAsSource = true;
 258+ $this->destSameAsSource = true; // OK
400259 }
401260 return $status; // do nothing; either OK or bad status
 261+ } else {
 262+ $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
 263+ return $status;
402264 }
403 - } else {
404 - $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
405 - return $status;
406265 }
407 -
408266 return $status;
409267 }
410268
411269 /**
412 - * checkAndBackupDest() helper function to get the source file Sha1.
413 - * Returns false on failure and null if there is no single source.
 270+ * precheckDestExistence() helper function to get the source file SHA-1.
 271+ * Subclasses should overwride this iff the source is not in storage.
414272 *
415 - * @return string|false|null
 273+ * @return string|false Returns false on failure
416274 */
417275 protected function getSourceSha1Base36() {
418276 return null; // N/A
419277 }
420278
421279 /**
422 - * checkAndBackupDest() helper function to get the Sha1 of a file.
423 - *
424 - * @return string|false False on failure
425 - */
426 - protected function getFileSha1Base36( $path ) {
427 - // Source file is in backend
428 - if ( FileBackend::isStoragePath( $path ) ) {
429 - // For some backends (e.g. Swift, Azure) we can get
430 - // standard hashes to use for this types of comparisons.
431 - $params = array( 'src' => $path, 'latest' => $this->useLatest );
432 - $hash = $this->backend->getFileSha1Base36( $params );
433 - // Source file is on file system
434 - } else {
435 - wfSuppressWarnings();
436 - $hash = sha1_file( $path );
437 - wfRestoreWarnings();
438 - if ( $hash !== false ) {
439 - $hash = wfBaseConvert( $hash, 16, 36, 31 );
440 - }
441 - }
442 - return $hash;
443 - }
444 -
445 - /**
446 - * Restore any temporary source backup file
447 - *
448 - * @return Status
449 - */
450 - protected function restoreSource() {
451 - $status = Status::newGood();
452 - // Restore any file that was at the destination
453 - if ( $this->tmpSourcePath !== null ) {
454 - $params = array(
455 - 'src' => $this->tmpSourcePath,
456 - 'dst' => $this->params['src'],
457 - 'overwriteDest' => true
458 - );
459 - $status = $this->backend->storeInternal( $params );
460 - if ( !$status->isOK() ) {
461 - return $status;
462 - }
463 - }
464 - return $status;
465 - }
466 -
467 - /**
468 - * Restore any temporary destination backup file
469 - *
470 - * @return Status
471 - */
472 - protected function restoreDest() {
473 - $status = Status::newGood();
474 - // Restore any file that was at the destination
475 - if ( $this->tmpDestFile ) {
476 - $params = array(
477 - 'src' => $this->tmpDestFile->getPath(),
478 - 'dst' => $this->params['dst'],
479 - 'overwriteDest' => true
480 - );
481 - $status = $this->backend->storeInternal( $params );
482 - if ( !$status->isOK() ) {
483 - return $status;
484 - }
485 - }
486 - return $status;
487 - }
488 -
489 - /**
490280 * Check if a file will exist in storage when this operation is attempted
491281 *
492282 * @param $source string Storage path
@@ -502,23 +292,30 @@
503293 }
504294
505295 /**
 296+ * Get the SHA-1 of a file in storage when this operation is attempted
 297+ *
 298+ * @param $source string Storage path
 299+ * @param $predicates Array
 300+ * @return string|false
 301+ */
 302+ final protected function fileSha1( $source, array $predicates ) {
 303+ if ( isset( $predicates['sha1'][$source] ) ) {
 304+ return $predicates['sha1'][$source]; // previous op assures this
 305+ } else {
 306+ $params = array( 'src' => $source, 'latest' => $this->useLatest );
 307+ return $this->backend->getFileSha1Base36( $params );
 308+ }
 309+ }
 310+
 311+ /**
506312 * Log a file operation failure and preserve any temp files
507313 *
508 - * @param $fileOp FileOp
 314+ * @param $action string
509315 * @return void
510316 */
511317 final protected function logFailure( $action ) {
512318 $params = $this->params;
513319 $params['failedAction'] = $action;
514 - // Preserve backup files just in case (for recovery)
515 - if ( $this->tmpSourceFile ) {
516 - $this->tmpSourceFile->preserve(); // don't purge
517 - $params['srcBackupPath'] = $this->tmpSourceFile->getPath();
518 - }
519 - if ( $this->tmpDestFile ) {
520 - $this->tmpDestFile->preserve(); // don't purge
521 - $params['dstBackupPath'] = $this->tmpDestFile->getPath();
522 - }
523320 try {
524321 wfDebugLog( 'FileOperation',
525322 get_class( $this ) . ' failed:' . serialize( $params ) );
@@ -543,30 +340,24 @@
544341
545342 protected function doPrecheck( array &$predicates ) {
546343 $status = Status::newGood();
547 - // Check if destination file exists
548 - $status->merge( $this->precheckDestExistence( $predicates ) );
549 - if ( !$status->isOK() ) {
550 - return $status;
551 - }
552344 // Check if the source file exists on the file system
553345 if ( !is_file( $this->params['src'] ) ) {
554346 $status->fatal( 'backend-fail-notexists', $this->params['src'] );
555347 return $status;
556348 }
 349+ // Check if destination file exists
 350+ $status->merge( $this->precheckDestExistence( $predicates ) );
 351+ if ( !$status->isOK() ) {
 352+ return $status;
 353+ }
557354 // Update file existence predicates
558355 $predicates['exists'][$this->params['dst']] = true;
559 - return $status;
 356+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 357+ return $status; // safe to call attempt()
560358 }
561359
562360 protected function doAttempt() {
563361 $status = Status::newGood();
564 - // Create a destination backup copy as needed
565 - if ( $this->destAlreadyExists ) {
566 - $status->merge( $this->checkAndBackupDest() );
567 - if ( !$status->isOK() ) {
568 - return $status;
569 - }
570 - }
571362 // Store the file at the destination
572363 if ( !$this->destSameAsSource ) {
573364 $status->merge( $this->backend->storeInternal( $this->params ) );
@@ -574,20 +365,16 @@
575366 return $status;
576367 }
577368
578 - protected function doRevert() {
579 - $status = Status::newGood();
580 - if ( !$this->destSameAsSource ) {
581 - // Restore any file that was at the destination,
582 - // overwritting what was put there in attempt()
583 - $status->merge( $this->restoreDest() );
 369+ protected function getSourceSha1Base36() {
 370+ wfSuppressWarnings();
 371+ $hash = sha1_file( $this->params['src'] );
 372+ wfRestoreWarnings();
 373+ if ( $hash !== false ) {
 374+ $hash = wfBaseConvert( $hash, 16, 36, 31 );
584375 }
585 - return $status;
 376+ return $hash;
586377 }
587378
588 - protected function getSourceSha1Base36() {
589 - return $this->getFileSha1Base36( $this->params['src'] );
590 - }
591 -
592379 public function storagePathsChanged() {
593380 return array( $this->params['dst'] );
594381 }
@@ -615,18 +402,12 @@
616403 }
617404 // Update file existence predicates
618405 $predicates['exists'][$this->params['dst']] = true;
619 - return $status;
 406+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 407+ return $status; // safe to call attempt()
620408 }
621409
622410 protected function doAttempt() {
623411 $status = Status::newGood();
624 - // Create a destination backup copy as needed
625 - if ( $this->destAlreadyExists ) {
626 - $status->merge( $this->checkAndBackupDest() );
627 - if ( !$status->isOK() ) {
628 - return $status;
629 - }
630 - }
631412 // Create the file at the destination
632413 if ( !$this->destSameAsSource ) {
633414 $status->merge( $this->backend->createInternal( $this->params ) );
@@ -634,16 +415,6 @@
635416 return $status;
636417 }
637418
638 - protected function doRevert() {
639 - $status = Status::newGood();
640 - if ( !$this->destSameAsSource ) {
641 - // Restore any file that was at the destination,
642 - // overwritting what was put there in attempt()
643 - $status->merge( $this->restoreDest() );
644 - }
645 - return $status;
646 - }
647 -
648419 protected function getSourceSha1Base36() {
649420 return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
650421 }
@@ -668,51 +439,34 @@
669440
670441 protected function doPrecheck( array &$predicates ) {
671442 $status = Status::newGood();
672 - // Check if destination file exists
673 - $status->merge( $this->precheckDestExistence( $predicates ) );
674 - if ( !$status->isOK() ) {
675 - return $status;
676 - }
677443 // Check if the source file exists
678444 if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
679445 $status->fatal( 'backend-fail-notexists', $this->params['src'] );
680446 return $status;
681447 }
 448+ // Check if destination file exists
 449+ $status->merge( $this->precheckDestExistence( $predicates ) );
 450+ if ( !$status->isOK() ) {
 451+ return $status;
 452+ }
682453 // Update file existence predicates
683454 $predicates['exists'][$this->params['dst']] = true;
684 - return $status;
 455+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 456+ return $status; // safe to call attempt()
685457 }
686458
687459 protected function doAttempt() {
688460 $status = Status::newGood();
689 - // Create a destination backup copy as needed
690 - if ( $this->destAlreadyExists ) {
691 - $status->merge( $this->checkAndBackupDest() );
692 - if ( !$status->isOK() ) {
693 - return $status;
 461+ // Do nothing if the src/dst paths are the same
 462+ if ( $this->params['src'] !== $this->params['dst'] ) {
 463+ // Copy the file into the destination
 464+ if ( !$this->destSameAsSource ) {
 465+ $status->merge( $this->backend->copyInternal( $this->params ) );
694466 }
695467 }
696 - // Copy the file into the destination
697 - if ( !$this->destSameAsSource ) {
698 - $status->merge( $this->backend->copyInternal( $this->params ) );
699 - }
700468 return $status;
701469 }
702470
703 - protected function doRevert() {
704 - $status = Status::newGood();
705 - if ( !$this->destSameAsSource ) {
706 - // Restore any file that was at the destination,
707 - // overwritting what was put there in attempt()
708 - $status->merge( $this->restoreDest() );
709 - }
710 - return $status;
711 - }
712 -
713 - protected function getSourceSha1Base36() {
714 - return $this->getFileSha1Base36( $this->params['src'] );
715 - }
716 -
717471 public function storagePathsRead() {
718472 return array( $this->params['src'] );
719473 }
@@ -737,76 +491,43 @@
738492
739493 protected function doPrecheck( array &$predicates ) {
740494 $status = Status::newGood();
741 - // Check if destination file exists
742 - $status->merge( $this->precheckDestExistence( $predicates ) );
743 - if ( !$status->isOK() ) {
744 - return $status;
745 - }
746495 // Check if the source file exists
747496 if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
748497 $status->fatal( 'backend-fail-notexists', $this->params['src'] );
749498 return $status;
750499 }
 500+ // Check if destination file exists
 501+ $status->merge( $this->precheckDestExistence( $predicates ) );
 502+ if ( !$status->isOK() ) {
 503+ return $status;
 504+ }
751505 // Update file existence predicates
752506 $predicates['exists'][$this->params['src']] = false;
 507+ $predicates['sha1'][$this->params['src']] = false;
753508 $predicates['exists'][$this->params['dst']] = true;
754 - return $status;
 509+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
 510+ return $status; // safe to call attempt()
755511 }
756512
757513 protected function doAttempt() {
758514 $status = Status::newGood();
759 - // Create a destination backup copy as needed
760 - if ( $this->destAlreadyExists ) {
761 - $status->merge( $this->checkAndBackupDest() );
762 - if ( !$status->isOK() ) {
763 - return $status;
 515+ // Do nothing if the src/dst paths are the same
 516+ if ( $this->params['src'] !== $this->params['dst'] ) {
 517+ if ( !$this->destSameAsSource ) {
 518+ // Move the file into the destination
 519+ $status->merge( $this->backend->moveInternal( $this->params ) );
 520+ } else {
 521+ // Just delete source as the destination needs no changes
 522+ $params = array( 'src' => $this->params['src'] );
 523+ $status->merge( $this->backend->deleteInternal( $params ) );
 524+ if ( !$status->isOK() ) {
 525+ return $status;
 526+ }
764527 }
765528 }
766 - if ( !$this->destSameAsSource ) {
767 - // Move the file into the destination
768 - $status->merge( $this->backend->moveInternal( $this->params ) );
769 - } else {
770 - // Create a source backup copy as needed
771 - $status->merge( $this->backupSource() );
772 - if ( !$status->isOK() ) {
773 - return $status;
774 - }
775 - // Just delete source as the destination needs no changes
776 - $params = array( 'src' => $this->params['src'] );
777 - $status->merge( $this->backend->deleteInternal( $params ) );
778 - if ( !$status->isOK() ) {
779 - return $status;
780 - }
781 - }
782529 return $status;
783530 }
784531
785 - protected function doRevert() {
786 - $status = Status::newGood();
787 - if ( !$this->destSameAsSource ) {
788 - // Move the file back to the source
789 - $params = array(
790 - 'src' => $this->params['dst'],
791 - 'dst' => $this->params['src']
792 - );
793 - $status->merge( $this->backend->moveInternal( $params ) );
794 - if ( !$status->isOK() ) {
795 - return $status; // also can't restore any dest file
796 - }
797 - // Restore any file that was at the destination
798 - $status->merge( $this->restoreDest() );
799 - } else {
800 - // Restore any source file
801 - return $this->restoreSource();
802 - }
803 -
804 - return $status;
805 - }
806 -
807 - protected function getSourceSha1Base36() {
808 - return $this->getFileSha1Base36( $this->params['src'] );
809 - }
810 -
811532 public function storagePathsRead() {
812533 return array( $this->params['src'] );
813534 }
@@ -841,17 +562,13 @@
842563 }
843564 // Update file existence predicates
844565 $predicates['exists'][$this->params['src']] = false;
845 - return $status;
 566+ $predicates['sha1'][$this->params['src']] = false;
 567+ return $status; // safe to call attempt()
846568 }
847569
848570 protected function doAttempt() {
849571 $status = Status::newGood();
850572 if ( $this->needsDelete ) {
851 - // Create a source backup copy as needed
852 - $status->merge( $this->backupSource() );
853 - if ( !$status->isOK() ) {
854 - return $status;
855 - }
856573 // Delete the source file
857574 $status->merge( $this->backend->deleteInternal( $this->params ) );
858575 if ( !$status->isOK() ) {
@@ -861,11 +578,6 @@
862579 return $status;
863580 }
864581
865 - protected function doRevert() {
866 - // Restore any source file that we deleted
867 - return $this->restoreSource();
868 - }
869 -
870582 public function storagePathsChanged() {
871583 return array( $this->params['src'] );
872584 }
@@ -878,8 +590,4 @@
879591 protected function doAttempt() {
880592 return Status::newGood();
881593 }
882 -
883 - protected function doRevert() {
884 - return Status::newGood();
885 - }
886594 }
Index: trunk/phase3/includes/filerepo/backend/FileBackend.php
@@ -155,6 +155,11 @@
156156 * This can increase performance for non-critical writes.
157157 * This has no effect unless the 'force' flag is set.
158158 *
 159+ * Remarks:
 160+ * File system paths given to operations should refer to files that are
 161+ * either locked or otherwise safe from modification from other processes.
 162+ * Normally these files will be new temp files, which should be adequate.
 163+ *
159164 * Return value:
160165 * This returns a Status, which contains all warnings and fatals that occured
161166 * during the operation. The 'failCount', 'successCount', and 'success' members

Follow-up revisions

RevisionCommit summaryAuthorDate
r109583* Follow-up r109009: Check that paths are usable in FileOp::doPrecheck(). Als...aaron23:18, 19 January 2012

Status & tagging log