From 0bab855df7b8d03999f486ea0557df401a3808c0 Mon Sep 17 00:00:00 2001 From: Laila Becker Date: Thu, 19 Sep 2024 09:36:29 +0200 Subject: [PATCH] [file-writing] Add helpers for writing files --- .../dreipol/dreimultiplatform/FileManager.kt | 58 ++++++++++++++++++- .../dreipol/dreimultiplatform/FileManager.kt | 4 ++ .../dreipol/dreimultiplatform/FileManager.kt | 15 +++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/androidMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt b/src/androidMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt index e306257..5002b28 100644 --- a/src/androidMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt +++ b/src/androidMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt @@ -2,10 +2,14 @@ package ch.dreipol.dreimultiplatform import android.os.Build import androidx.annotation.RequiresApi +import androidx.core.util.AtomicFile +import java.io.DataOutputStream import java.io.File +import java.io.FileOutputStream import java.io.IOException +import java.io.OutputStreamWriter import java.nio.file.Files -import kotlin.streams.toList + actual typealias FileIdentifier = File @@ -45,4 +49,56 @@ actual object FileManager { actual fun byteArrayFrom(file: FileIdentifier): ByteArray? = file.readBytes() actual fun fileIdentifierFromPath(path: String): FileIdentifier? = File(path) + + actual fun write(data: ByteArray, toFile: FileIdentifier, atomically: Boolean): Boolean = + if (atomically) { + AtomicFile(toFile).write(data) + } else { + runCatching { + toFile.writeBytes(data) + }.isSuccess + } + + actual fun write(string: String, toFile: FileIdentifier, atomically: Boolean): Boolean = + if (atomically) { + AtomicFile(toFile).write(string) + } else { + runCatching { + toFile.writeText(string) + }.isSuccess + } + + private fun AtomicFile.write(data: ByteArray): Boolean { + var dataOutput: DataOutputStream? = null + var outputStream: FileOutputStream? = null + try { + outputStream = startWrite() + dataOutput = DataOutputStream(outputStream) // Wrapper stream + dataOutput.write(data) + finishWrite(outputStream) // Pass wrapper stream + return true + } catch (_: Throwable) { + outputStream?.let { failWrite(it) } + return false + } finally { + dataOutput?.close() + } + } + + private fun AtomicFile.write(string: String): Boolean { + var dataOutput: OutputStreamWriter? = null + var outputStream: FileOutputStream? = null + try { + outputStream = startWrite() + dataOutput = OutputStreamWriter(outputStream) // Wrapper stream + dataOutput.write(string) + finishWrite(outputStream) // Pass wrapper stream + return true + } catch (_: Throwable) { + outputStream?.let { failWrite(it) } + return false + } finally { + dataOutput?.close() + } + } } \ No newline at end of file diff --git a/src/commonMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt b/src/commonMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt index 4e2dedc..4ba5e9b 100644 --- a/src/commonMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt +++ b/src/commonMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt @@ -23,4 +23,8 @@ expect object FileManager { fun byteArrayFrom(file: FileIdentifier): ByteArray? fun fileIdentifierFromPath(path: String): FileIdentifier? + + fun write(data: ByteArray, toFile: FileIdentifier, atomically: Boolean): Boolean + + fun write(string: String, toFile: FileIdentifier, atomically: Boolean): Boolean } \ No newline at end of file diff --git a/src/iosMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt b/src/iosMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt index a8d4b66..0fb2d52 100644 --- a/src/iosMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt +++ b/src/iosMain/kotlin/ch/dreipol/dreimultiplatform/FileManager.kt @@ -6,16 +6,22 @@ import kotlinx.cinterop.alloc import kotlinx.cinterop.memScoped import kotlinx.cinterop.ptr import kotlinx.cinterop.value +import platform.Foundation.NSCachesDirectory import platform.Foundation.NSData import platform.Foundation.NSError import platform.Foundation.NSFileManager +import platform.Foundation.NSSearchPathDirectory +import platform.Foundation.NSSearchPathForDirectoriesInDomains import platform.Foundation.NSString import platform.Foundation.NSURL import platform.Foundation.NSUTF8StringEncoding +import platform.Foundation.NSUserDomainMask import platform.Foundation.URLByAppendingPathComponent import platform.Foundation.dataWithContentsOfURL import platform.Foundation.lastPathComponent import platform.Foundation.stringWithContentsOfURL +import platform.Foundation.stringWithString +import platform.Foundation.writeToFile @kotlinx.cinterop.ExperimentalForeignApi typealias ErrorPointer = CPointer> @@ -70,6 +76,15 @@ actual object FileManager { actual fun byteArrayFrom(file: FileIdentifier): ByteArray? = NSData.dataWithContentsOfURL(file.url)?.toByteArray() actual fun fileIdentifierFromPath(path: String): FileIdentifier? = NSURL.fileURLWithPath(path).toFileIdentifier() + + actual fun write(data: ByteArray, toFile: FileIdentifier, atomically: Boolean): Boolean { + val path = toFile.filePath ?: return false + return data.toNSData().writeToFile(path, atomically) + } + + @OptIn(ExperimentalStdlibApi::class) + actual fun write(string: String, toFile: FileIdentifier, atomically: Boolean): Boolean = + write(string.hexToByteArray(), toFile, atomically) } @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)