mirror of https://github.com/sbt/sbt.git
in IO.copyFile, limit maximum size transferred via NIO. fixes #491
This commit is contained in:
parent
5a93605d75
commit
b6b78618f5
|
|
@ -525,11 +525,21 @@ object IO
|
||||||
|
|
||||||
def copyFile(sourceFile: File, targetFile: File, preserveLastModified: Boolean = false)
|
def copyFile(sourceFile: File, targetFile: File, preserveLastModified: Boolean = false)
|
||||||
{
|
{
|
||||||
|
// NOTE: when modifying this code, test with larger values of CopySpec.MaxFileSizeBits than default
|
||||||
|
|
||||||
require(sourceFile.exists, "Source file '" + sourceFile.getAbsolutePath + "' does not exist.")
|
require(sourceFile.exists, "Source file '" + sourceFile.getAbsolutePath + "' does not exist.")
|
||||||
require(!sourceFile.isDirectory, "Source file '" + sourceFile.getAbsolutePath + "' is a directory.")
|
require(!sourceFile.isDirectory, "Source file '" + sourceFile.getAbsolutePath + "' is a directory.")
|
||||||
fileInputChannel(sourceFile) { in =>
|
fileInputChannel(sourceFile) { in =>
|
||||||
fileOutputChannel(targetFile) { out =>
|
fileOutputChannel(targetFile) { out =>
|
||||||
val copied = out.transferFrom(in, 0, in.size)
|
// maximum bytes per transfer according to from http://dzone.com/snippets/java-filecopy-using-nio
|
||||||
|
val max = (64 * 1024 * 1024) - (32 * 1024)
|
||||||
|
val total = in.size
|
||||||
|
def loop(offset: Long): Long =
|
||||||
|
if(offset < total)
|
||||||
|
loop( offset + out.transferFrom(in, offset, max) )
|
||||||
|
else
|
||||||
|
offset
|
||||||
|
val copied = loop(0)
|
||||||
if(copied != in.size)
|
if(copied != in.size)
|
||||||
error("Could not copy '" + sourceFile + "' to '" + targetFile + "' (" + copied + "/" + in.size + " bytes copied)")
|
error("Could not copy '" + sourceFile + "' to '" + targetFile + "' (" + copied + "/" + in.size + " bytes copied)")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Arrays
|
||||||
|
import org.scalacheck._
|
||||||
|
import Prop._
|
||||||
|
import Arbitrary.arbLong
|
||||||
|
|
||||||
|
object CopySpec extends Properties("Copy")
|
||||||
|
{
|
||||||
|
// set to 0.25 GB by default for success on most systems without running out of space.
|
||||||
|
// when modifying IO.copyFile, verify against 1 GB or higher, preferably > 4 GB
|
||||||
|
final val MaxFileSizeBits = 28
|
||||||
|
final val BufferSize = 1*1024*1024
|
||||||
|
|
||||||
|
val randomSize = Gen.choose(0, MaxFileSizeBits).map( 1L << _ )
|
||||||
|
val pow2Size = (0 to (MaxFileSizeBits - 1)).toList.map( 1L << _ )
|
||||||
|
val derivedSize = pow2Size.map(_ - 1) ::: pow2Size.map(_ + 1) ::: pow2Size
|
||||||
|
|
||||||
|
val fileSizeGen: Gen[Long] =
|
||||||
|
Gen.frequency(
|
||||||
|
80 -> Gen.oneOf(derivedSize),
|
||||||
|
8 -> randomSize,
|
||||||
|
1 -> Gen.value(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
property("same contents") = forAll(fileSizeGen, arbLong.arbitrary) { (size: Long, seed: Long) =>
|
||||||
|
IO.withTemporaryDirectory { dir =>
|
||||||
|
val f1 = new File(dir, "source")
|
||||||
|
val f2 = new File(dir, "dest")
|
||||||
|
generate(seed = seed, size = size, file = f1)
|
||||||
|
IO.copyFile(f1, f2)
|
||||||
|
checkContentsSame(f1, f2)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate(seed: Long, size: Long, file: File) {
|
||||||
|
val rnd = new java.util.Random(seed)
|
||||||
|
|
||||||
|
val buffer = new Array[Byte](BufferSize)
|
||||||
|
def loop(offset: Long) {
|
||||||
|
val len = math.min(size - offset, BufferSize)
|
||||||
|
if(len > 0) {
|
||||||
|
rnd.nextBytes(buffer)
|
||||||
|
IO.append(file, buffer)
|
||||||
|
loop(offset + len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(size == 0L) IO.touch(file) else loop(0)
|
||||||
|
}
|
||||||
|
def checkContentsSame(f1: File, f2: File) {
|
||||||
|
val len = f1.length
|
||||||
|
assert(len == f2.length, "File lengths differ: " + (len, f2.length).toString + " for " + (f1, f2).toString)
|
||||||
|
Using.fileInputStream(f1) { in1 =>
|
||||||
|
Using.fileInputStream(f2) { in2 =>
|
||||||
|
val buffer1 = new Array[Byte](BufferSize)
|
||||||
|
val buffer2 = new Array[Byte](BufferSize)
|
||||||
|
def loop(offset: Long): Unit = if(offset < len) {
|
||||||
|
val read1 = in1.read(buffer1)
|
||||||
|
val read2 = in2.read(buffer2)
|
||||||
|
assert(read1 == read2, "Read " + (read1, read2).toString + " bytes from " + (f1, f2).toString)
|
||||||
|
assert(Arrays.equals(buffer1, buffer2), "Contents differed.")
|
||||||
|
loop(offset + read1)
|
||||||
|
}
|
||||||
|
loop(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue