diff --git a/libraries/stdlib/src/kotlin/io/files/Utils.kt b/libraries/stdlib/src/kotlin/io/files/Utils.kt index a9bf98ea2ed..dcc02c83e24 100644 --- a/libraries/stdlib/src/kotlin/io/files/Utils.kt +++ b/libraries/stdlib/src/kotlin/io/files/Utils.kt @@ -218,7 +218,7 @@ public enum class OnErrorAction { private class TerminateException(file: File) : FileSystemException(file) {} /** - * Copies this file with all its children to the specified destination [dst] path. + * Copies this file with all its children to the specified destination [target] path. * If some directories on the way to the destination are missing, then they will be created. * * If any errors occur during the copying, then further actions will depend on the result of the call @@ -231,11 +231,13 @@ private class TerminateException(file: File) : FileSystemException(file) {} * AccessDeniedException - if there was an attempt to open a directory that didn't succeed. * IOException - if some problems occur when copying. * + * @param overwrite `true` if it is allowed to overwrite existing destination files and directories. * @return `false` if the copying was terminated, `true` otherwise. * * Note that if this function fails, then partial copying may have taken place. */ -public fun File.copyRecursively(dst: File, +public fun File.copyRecursively(target: File, + overwrite: Boolean = false, onError: (File, IOException) -> OnErrorAction = { file, exception -> throw exception } ): Boolean { @@ -252,16 +254,29 @@ public fun File.copyRecursively(dst: File, return false } else { val relPath = src.toRelativeString(this) - val dstFile = File(dst, relPath) + val dstFile = File(target, relPath) if (dstFile.exists() && !(src.isDirectory && dstFile.isDirectory)) { - if (onError(dstFile, FileAlreadyExistsException(file = src, - other = dstFile, - reason = "The destination file already exists")) == OnErrorAction.TERMINATE) - return false - } else if (src.isDirectory) { + val stillExists = if (!overwrite) true else { + if (dstFile.isDirectory) + !dstFile.deleteRecursively() + else + !dstFile.delete() + } + + if (stillExists) { + if (onError(dstFile, FileAlreadyExistsException(file = src, + other = dstFile, + reason = "The destination file already exists")) == OnErrorAction.TERMINATE) + return false + + continue + } + } + + if (src.isDirectory) { dstFile.mkdirs() } else { - if (src.copyTo(dstFile, true) != src.length()) { + if (src.copyTo(dstFile, overwrite) != src.length()) { if (onError(src, IOException("src.length() != dst.length()")) == OnErrorAction.TERMINATE) return false } diff --git a/libraries/stdlib/test/io/Files.kt b/libraries/stdlib/test/io/Files.kt index 184dd0dcf8c..be7cd3c2b71 100644 --- a/libraries/stdlib/test/io/Files.kt +++ b/libraries/stdlib/test/io/Files.kt @@ -451,20 +451,22 @@ class FilesTest { } } + fun compareDirectories(src: File, dst: File) { + for (file in src.walkTopDown()) { + val dstFile = dst.resolve(file.relativeTo(src)) + assertTrue(dstFile.exists()) + assertEquals(file.isFile, dstFile.isFile) + if (dstFile.isFile) { + assertEquals(file.readText(), dstFile.readText()) + } + } + } + @test fun copyRecursively() { val src = createTempDir() val dst = createTempDir() dst.delete() - fun check() { - for (file in src.walkTopDown()) { - val dstFile = dst.resolve(file.relativeTo(src)) - assertTrue(dstFile.exists()) - if (dstFile.isFile) { - assertEquals(file.readText(), dstFile.readText()) - } - - } - } + fun check() = compareDirectories(src, dst) try { val subDir1 = createTempDir(prefix = "d1_", directory = src) @@ -531,6 +533,40 @@ class FilesTest { } } + @test fun copyRecursivelyWithOverwrite() { + val src = createTempDir() + val dst = createTempDir() + fun check() = compareDirectories(src, dst) + + try { + val srcFile = src.resolve("test") + val dstFile = dst.resolve("test") + srcFile.writeText("text1") + + src.copyRecursively(dst) + + srcFile.writeText("text1 modified") + src.copyRecursively(dst, overwrite = true) + check() + + dstFile.delete() + dstFile.mkdir() + dstFile.resolve("subFile").writeText("subfile") + src.copyRecursively(dst, overwrite = true) + check() + + srcFile.delete() + srcFile.mkdir() + srcFile.resolve("subFile").writeText("text2") + src.copyRecursively(dst, overwrite = true) + check() + } + finally { + src.deleteRecursively() + dst.deleteRecursively() + } + } + @test fun helpers1() { val str = "123456789\n" System.setIn(str.byteInputStream())