|
|
|
package org.openrs2.util.io
|
|
|
|
|
|
|
|
import java.io.BufferedWriter
|
|
|
|
import java.io.IOException
|
|
|
|
import java.io.OutputStream
|
|
|
|
import java.nio.channels.FileChannel
|
|
|
|
import java.nio.charset.Charset
|
|
|
|
import java.nio.file.CopyOption
|
|
|
|
import java.nio.file.FileVisitOption
|
|
|
|
import java.nio.file.Files
|
|
|
|
import java.nio.file.LinkOption
|
|
|
|
import java.nio.file.OpenOption
|
|
|
|
import java.nio.file.Path
|
|
|
|
import java.nio.file.StandardCopyOption
|
|
|
|
import java.nio.file.StandardOpenOption
|
|
|
|
import java.nio.file.attribute.FileAttribute
|
|
|
|
|
|
|
|
public fun Path.fsync() {
|
|
|
|
require(Files.isRegularFile(this) || Files.isDirectory(this))
|
|
|
|
|
|
|
|
FileChannel.open(this, StandardOpenOption.READ).use { channel ->
|
|
|
|
channel.force(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public fun Path.recursiveCopy(
|
|
|
|
destination: Path,
|
|
|
|
visitOptions: Array<FileVisitOption> = emptyArray(),
|
|
|
|
copyOptions: Array<CopyOption> = emptyArray()
|
|
|
|
) {
|
|
|
|
for (sourceFile in Files.walk(this, *visitOptions)) {
|
|
|
|
val destinationFile = destination.resolve(this.relativize(sourceFile).toString())
|
|
|
|
|
|
|
|
if (Files.isDirectory(sourceFile)) {
|
|
|
|
Files.createDirectory(destinationFile)
|
|
|
|
} else {
|
|
|
|
Files.copy(sourceFile, destinationFile, *copyOptions)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public fun Path.recursiveEquals(
|
|
|
|
other: Path,
|
|
|
|
linkOptions: Array<LinkOption> = emptyArray(),
|
|
|
|
openOptions: Array<OpenOption> = emptyArray(),
|
|
|
|
filter: (Path) -> Boolean = { true }
|
|
|
|
): Boolean {
|
|
|
|
val list1 = Files.newDirectoryStream(this).use { stream ->
|
|
|
|
stream.filter(filter).map { this.relativize(it).toString() }.sorted()
|
|
|
|
}
|
|
|
|
|
|
|
|
val list2 = Files.newDirectoryStream(other).use { stream ->
|
|
|
|
stream.filter(filter).map { other.relativize(it).toString() }.sorted()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (list1 != list2) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for (file in list1) {
|
|
|
|
val file1 = this.resolve(file)
|
|
|
|
val file2 = other.resolve(file)
|
|
|
|
|
|
|
|
if (Files.isDirectory(file1, *linkOptions)) {
|
|
|
|
if (!Files.isDirectory(file2, *linkOptions)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Files.newInputStream(file1, *openOptions).use { input1 ->
|
|
|
|
Files.newInputStream(file2, *openOptions).use { input2 ->
|
|
|
|
if (!input1.contentEquals(input2)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
public inline fun <T> useTempFile(
|
|
|
|
prefix: String? = null,
|
|
|
|
suffix: String? = null,
|
|
|
|
vararg attributes: FileAttribute<*>,
|
|
|
|
f: (Path) -> T
|
|
|
|
): T {
|
|
|
|
val tempFile = Files.createTempFile(prefix, suffix, *attributes)
|
|
|
|
try {
|
|
|
|
return f(tempFile)
|
|
|
|
} finally {
|
|
|
|
Files.deleteIfExists(tempFile)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public inline fun <T> Path.useTempFile(
|
|
|
|
prefix: String? = null,
|
|
|
|
suffix: String? = null,
|
|
|
|
vararg attributes: FileAttribute<*>,
|
|
|
|
f: (Path) -> T
|
|
|
|
): T {
|
|
|
|
val tempFile = Files.createTempFile(this, prefix, suffix, *attributes)
|
|
|
|
try {
|
|
|
|
return f(tempFile)
|
|
|
|
} finally {
|
|
|
|
Files.deleteIfExists(tempFile)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public inline fun <T> Path.atomicWrite(
|
|
|
|
sync: Boolean = true,
|
|
|
|
f: (Path) -> T,
|
|
|
|
): T {
|
|
|
|
parent.useTempFile(".$fileName", ".tmp") { tempFile ->
|
|
|
|
val result = f(tempFile)
|
|
|
|
if (sync) {
|
|
|
|
tempFile.fsync()
|
|
|
|
}
|
|
|
|
|
|
|
|
Files.move(tempFile, this, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING)
|
|
|
|
|
|
|
|
if (sync) {
|
|
|
|
try {
|
|
|
|
parent.fsync()
|
|
|
|
} catch (ex: IOException) {
|
|
|
|
// can't fsync directories on (at least) Windows and jimfs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public inline fun <T> Path.useAtomicBufferedWriter(
|
|
|
|
vararg options: OpenOption,
|
|
|
|
sync: Boolean = true,
|
|
|
|
f: (BufferedWriter) -> T,
|
|
|
|
): T {
|
|
|
|
return atomicWrite(sync) { path ->
|
|
|
|
Files.newBufferedWriter(path, *options).use { writer ->
|
|
|
|
f(writer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public inline fun <T> Path.useAtomicBufferedWriter(
|
|
|
|
cs: Charset,
|
|
|
|
vararg options: OpenOption,
|
|
|
|
sync: Boolean = true,
|
|
|
|
f: (BufferedWriter) -> T,
|
|
|
|
): T {
|
|
|
|
return atomicWrite(sync) { path ->
|
|
|
|
Files.newBufferedWriter(path, cs, *options).use { writer ->
|
|
|
|
f(writer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public inline fun <T> Path.useAtomicOutputStream(
|
|
|
|
vararg options: OpenOption,
|
|
|
|
sync: Boolean = true,
|
|
|
|
f: (OutputStream) -> T,
|
|
|
|
): T {
|
|
|
|
return atomicWrite(sync) { path ->
|
|
|
|
Files.newOutputStream(path, *options).use { output ->
|
|
|
|
f(output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|