* same contents as the source
*/
function storeBatch( $triplets, $flags = 0 ) {
+ wfDebug( __METHOD__ . ': Storing ' . count( $triplets ) .
+ " triplets; flags: {$flags}\n" );
+
+ // Try creating directories
if ( !wfMkdirParents( $this->directory ) ) {
return $this->newFatal( 'upload_directory_missing', $this->directory );
}
if ( !is_writable( $this->directory ) ) {
return $this->newFatal( 'upload_directory_read_only', $this->directory );
}
+
+ // Validate each triplet
$status = $this->newGood();
foreach ( $triplets as $i => $triplet ) {
list( $srcPath, $dstZone, $dstRel ) = $triplet;
+ // Resolve destination path
$root = $this->getZonePath( $dstZone );
if ( !$root ) {
throw new MWException( "Invalid zone: $dstZone" );
$dstPath = "$root/$dstRel";
$dstDir = dirname( $dstPath );
+ // Create destination directories for this triplet
if ( !is_dir( $dstDir ) ) {
if ( !wfMkdirParents( $dstDir ) ) {
return $this->newFatal( 'directorycreateerror', $dstDir );
}
}
+ // Resolve source
if ( self::isVirtualUrl( $srcPath ) ) {
$srcPath = $triplets[$i][0] = $this->resolveVirtualUrl( $srcPath );
}
$status->fatal( 'filenotfound', $srcPath );
continue;
}
+
+ // Check overwriting
if ( !( $flags & self::OVERWRITE ) && file_exists( $dstPath ) ) {
if ( $flags & self::OVERWRITE_SAME ) {
$hashSource = sha1_file( $srcPath );
}
}
+ // Windows does not support moving over existing files, so explicitly delete them
$deleteDest = wfIsWindows() && ( $flags & self::OVERWRITE );
// Abort now on failure
return $status;
}
- foreach ( $triplets as $triplet ) {
+ // Execute the store operation for each triplet
+ foreach ( $triplets as $i => $triplet ) {
list( $srcPath, $dstZone, $dstRel ) = $triplet;
$root = $this->getZonePath( $dstZone );
$dstPath = "$root/$dstRel";
$status->error( 'filecopyerror', $srcPath, $dstPath );
$good = false;
}
+ if ( !( $flags & self::SKIP_VALIDATION ) ) {
+ wfSuppressWarnings();
+ $hashSource = sha1_file( $srcPath );
+ $hashDest = sha1_file( $dstPath );
+ wfRestoreWarnings();
+
+ if ( $hashDest === false || $hashSource !== $hashDest ) {
+ wfDebug( __METHOD__ . ': File copy validation failed: ' .
+ "$srcPath ($hashSource) to $dstPath ($hashDest)\n" );
+
+ $status->error( 'filecopyerror', $srcPath, $dstPath );
+ $good = false;
+ }
+ }
}
if ( $good ) {
$this->chmod( $dstPath );
} else {
$status->failCount++;
}
+ $status->success[$i] = $good;
}
return $status;
}
+
+ /**
+ * Deletes a batch of (zone, rel) pairs. It will try to delete each pair,
+ * but ignores any errors doing so.
+ *
+ * @param $pairs array Pair of (zone, rel) pairs to delete
+ */
+ function cleanupBatch( $pairs ) {
+ foreach ( $pairs as $i => $pair ) {
+ list( $zone, $rel ) = $pair;
+ $root = $this->getZonePath( $zone );
+ $path = "$root/$rel";
+
+ wfSuppressWarnings();
+ unlink( $path );
+ wfRestoreWarnings();
+ }
+ }
function append( $srcPath, $toAppendPath, $flags = 0 ) {
$status = $this->newGood();
$status = $batch->execute();
- if ( !$status->ok ) {
+ if ( !$status->isGood() ) {
return $status;
}
$storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
$status->merge( $storeStatus );
- if ( !$status->ok ) {
- // Store batch returned a critical error -- this usually means nothing was stored
- // Stop now and return an error
+ if ( !$status->isGood() ) {
+ // Even if some files could be copied, fail entirely as that is the
+ // easiest thing to do without data loss
+ $this->cleanupFailedBatch( $storeStatus, $storeBatch );
+ $status->ok = false;
$this->file->unlock();
return $status;
return $status;
}
+
+ function cleanupFailedBatch( $storeStatus, $storeBatch ) {
+ $cleanupBatch = array();
+
+ foreach ( $storeStatus->success as $i => $success ) {
+ if ( $success ) {
+ $cleanupBatch[] = array( $storeBatch[$i][1], $storeBatch[$i][1] );
+ }
+ }
+ $this->file->repo->cleanupBatch( $cleanupBatch );
+ }
}
# ------------------------------------------------------------------------------