Implement robust file downloading, uploading and opening

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2026-05-29 20:23:52 +03:00
Unverified
parent 76645e2633
commit 70a9f8635e
104 changed files with 5767 additions and 881 deletions
@@ -12,6 +12,17 @@ internal actual fun expectWriteBytes(path: String, bytes: ByteArray) {
file.writeBytes(bytes)
}
internal actual fun expectAppendBytes(path: String, bytes: ByteArray) {
val file = File(path)
file.parentFile?.mkdirs()
file.appendBytes(bytes)
}
internal actual fun expectFileSize(path: String): Long {
val file = File(path)
return if (file.isFile) file.length() else 0L
}
internal actual fun expectDelete(path: String) {
File(path).delete()
}
@@ -26,6 +37,12 @@ internal actual fun expectDeleteFilesWithPrefix(dirPath: String, namePrefix: Str
}
}
internal actual fun expectListFileNamesInDirectory(dirPath: String): List<String> {
val dir = File(dirPath)
if (!dir.isDirectory) return emptyList()
return dir.listFiles()?.map { it.name } ?: emptyList()
}
internal actual fun expectGetAppCacheDirectory(): String =
UtilsLibrary.context.cacheDir.absolutePath
@@ -18,6 +18,15 @@ object PlatformFileSystem {
expectWriteBytes(path, bytes)
}
/** Appends [bytes] to the file at [path], creating it when missing. */
fun appendBytes(path: String, bytes: ByteArray) {
if (bytes.isEmpty()) return
expectAppendBytes(path, bytes)
}
/** Size in bytes, or 0 when the file does not exist. */
fun fileSize(path: String): Long = expectFileSize(path)
/**
* Deletes the file at [path]. No-op if the file does not exist.
*/
@@ -32,6 +41,10 @@ object PlatformFileSystem {
expectDeleteFilesWithPrefix(dirPath, namePrefix)
}
/** Returns filenames (not paths) in [dirPath], or empty if the directory is missing. */
fun listFileNamesInDirectory(dirPath: String): List<String> =
expectListFileNamesInDirectory(dirPath)
/**
* Returns the platform-specific application cache directory path.
* On Android: context.cacheDir (requires UtilsLibrary.init to be called).
@@ -51,7 +64,10 @@ object PlatformFileSystem {
internal expect fun expectExists(path: String): Boolean
internal expect fun expectWriteBytes(path: String, bytes: ByteArray)
internal expect fun expectAppendBytes(path: String, bytes: ByteArray)
internal expect fun expectFileSize(path: String): Long
internal expect fun expectDelete(path: String)
internal expect fun expectDeleteFilesWithPrefix(dirPath: String, namePrefix: String)
internal expect fun expectListFileNamesInDirectory(dirPath: String): List<String>
internal expect fun expectGetAppCacheDirectory(): String
internal expect fun expectEnsureDirectory(path: String)
@@ -7,11 +7,18 @@ import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.NSData
import platform.Foundation.NSFileManager
import platform.Foundation.NSNumber
import platform.Foundation.NSCachesDirectory
import platform.Foundation.NSSearchPathForDirectoriesInDomains
import platform.Foundation.NSUserDomainMask
import platform.Foundation.create
import platform.Foundation.writeToFile
import platform.posix.fclose
import platform.posix.fopen
import platform.posix.fwrite
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.convert
import kotlinx.cinterop.usePinned
@OptIn(ExperimentalForeignApi::class)
internal actual fun expectExists(path: String): Boolean =
@@ -29,6 +36,32 @@ internal actual fun expectWriteBytes(path: String, bytes: ByteArray) {
nsData?.writeToFile(path, true)
}
@OptIn(ExperimentalForeignApi::class)
internal actual fun expectAppendBytes(path: String, bytes: ByteArray) {
val parent = path.substringBeforeLast('/', missingDelimiterValue = "")
if (parent.isNotEmpty()) {
NSFileManager.defaultManager.createDirectoryAtPath(parent, true, null, null)
}
val file = fopen(path, "ab") ?: error("Failed to open file for append")
try {
bytes.usePinned { pinned ->
val written = fwrite(pinned.addressOf(0), 1.convert(), bytes.size.convert(), file).toInt()
if (written != bytes.size) {
error("Short write")
}
}
} finally {
fclose(file)
}
}
@OptIn(ExperimentalForeignApi::class)
internal actual fun expectFileSize(path: String): Long {
if (!NSFileManager.defaultManager.fileExistsAtPath(path)) return 0L
val attrs = NSFileManager.defaultManager.attributesOfItemAtPath(path, null) ?: return 0L
return (attrs["NSFileSize"] as? NSNumber)?.longValue ?: 0L
}
internal actual fun expectDelete(path: String) {
NSFileManager.defaultManager.removeItemAtPath(path, null)
}
@@ -43,6 +76,12 @@ internal actual fun expectDeleteFilesWithPrefix(dirPath: String, namePrefix: Str
}
}
internal actual fun expectListFileNamesInDirectory(dirPath: String): List<String> {
val contents = NSFileManager.defaultManager.contentsOfDirectoryAtPath(dirPath, null)
?: return emptyList()
return (contents as List<*>).filterIsInstance<String>()
}
internal actual fun expectGetAppCacheDirectory(): String {
val paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true)
return (paths.firstOrNull() as? String) ?: ""