2022-05-31 09:38:02 -07:00
|
|
|
@file:Suppress("TooManyFunctions")
|
|
|
|
|
|
|
|
package co.electriccoin.zcash.spackle.io
|
|
|
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
import java.io.File
|
|
|
|
import java.io.IOException
|
|
|
|
import java.util.UUID
|
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.existsSuspend() =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
exists()
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.mkdirsSuspend() =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
mkdirs()
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.isDirectorySuspend() =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
isDirectory
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.isFileSuspend() =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
isFile
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.canWriteSuspend() =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
canWrite()
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.deleteSuspend() =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
delete()
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2023-12-11 01:20:32 -08:00
|
|
|
suspend fun File.renameToSuspend(destination: File) =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
renameTo(destination)
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
2024-01-15 08:32:15 -08:00
|
|
|
suspend fun File.listFilesSuspend(): Array<out File>? =
|
2023-12-11 01:20:32 -08:00
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
listFiles()
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given an ultimate output file destination, this generates a temporary file that [action] can write to. After action
|
|
|
|
* is complete, the temp file is renamed to the expected output destination. Depending on the underlying filesystem,
|
|
|
|
* this should effectively ensure that the file is perceived as being written atomically.
|
|
|
|
*
|
|
|
|
* @receiver Ultimate file that we desire to write to. Must be a file and not a directory.
|
|
|
|
* @param action Action to perform on the file, specifically this should be writing data. This action should not
|
|
|
|
* delete, rename, or do other operations in the filesystem.
|
|
|
|
*/
|
|
|
|
suspend fun File.writeAtomically(action: (suspend (File) -> Unit)) {
|
2023-12-11 01:20:32 -08:00
|
|
|
val tempFile =
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
File(parentFile, name.newTempFileName()).also {
|
|
|
|
it.deleteOnExit()
|
|
|
|
}
|
2022-05-31 09:38:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
var isWriteSuccessful = false
|
|
|
|
|
|
|
|
try {
|
|
|
|
action(tempFile)
|
|
|
|
isWriteSuccessful = true
|
2023-12-11 01:20:32 -08:00
|
|
|
} catch (
|
|
|
|
@Suppress("TooGenericExceptionCaught") e: Exception
|
|
|
|
) {
|
2022-05-31 09:38:02 -07:00
|
|
|
tempFile.deleteSuspend()
|
|
|
|
throw e
|
|
|
|
} finally {
|
|
|
|
if (isWriteSuccessful) {
|
|
|
|
tempFile.moveTo(destination = this)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private suspend fun File.moveTo(destination: File) {
|
|
|
|
val isRenameSuccessful = renameToSuspend(destination)
|
|
|
|
|
|
|
|
if (!isRenameSuccessful) {
|
|
|
|
if (existsSuspend()) {
|
|
|
|
throw IOException("Couldn't move file $path to ${destination.path}")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise no data was written, so there's no file to rename.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that adding uuid and .tmp could theoretically go past file name length limits on some filesystems
|
|
|
|
private fun String.newTempFileName() = "$this-${UUID.randomUUID()}.tmp"
|