HalR
HalR

Reputation: 11083

Kotlin Multiplatform (KMP) save to file is not saving on iOS

We are unable to successfully write data on an iOS app using this KMP code. We would like to know what we can change to make it write successfully:

suspend fun HttpResponse.saveBodyToFile(fileSystem: FileSystem, outputFile: Path): Boolean {
    var success = false
    try {
        // if the target file already exists, remove it.
        fileSystem.delete(outputFile)

        val parentDir = outputFile.parent ?: return false
        if (!fileSystem.exists(parentDir)) {
            fileSystem.createDirectories(parentDir)
        }

        val byteReadChannel: ByteReadChannel = body()
        fileSystem.sink(outputFile).buffer().use {
            byteReadChannel.readFully(it)
        }

        success = fileSystem.exists(outputFile)
    } catch (e: KtorIoException) {
        Logger.e(e) { "Failed to save response stream to ${outputFile.name}" }
        Logger.e { "I/O Exception ${e.message}"}
       fileSystem.delete(outputFile)
    } catch (e: OkioIOException) {
        Logger.e { "I/O Exception ${e.message}"}
        Logger.e(e) { "Failed to save response stream to ${outputFile.name}" }
        Logger.e { "I/O Exception ${e.message}"}
        fileSystem.delete(outputFile)
    }
    return success
}

Strangely enough it creates a directory, but throws this error: šŸ”“ I/O Exception Read-only file system

If the directory already exists, it still evaluates this as true:

(!fileSystem.exists(parentDir))

and tries to create it again.

If the directory exists, and this section of code is commented out:

 if (!fileSystem.exists(parentDir)) {
    fileSystem.createDirectories(parentDir)
 }

then this section of code:

fileSystem.sink(outputFile).buffer().use {
    byteReadChannel.readFully(it)
}

throws this error: šŸ”“ I/O Exception Read-only file system

We are passing in a subdirectory of the Cache directory:

file:/Users/myname/Library/Developer/CoreSimulator/Devices/85FF4164-D3CF-446C-AF42-EC64B5473BCA/data/Containers/Data/Application/7F890ECD-83DB-4D91-8BBD-102F631594A6/Library/Caches/syncFiles/sync-proxy.json

Upvotes: 2

Views: 1245

Answers (1)

Ge3ng
Ge3ng

Reputation: 2430

So we are using Okio for KMP I/O operations. To access the appropriate directory

guard let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("syncFiles")

Than passing cacheDirectory.absoluteString as the parent directory to okio's filesystem. absoluteString returns a string in the form of file:<path>

Okio uses lstat to determine if a path exists. lstat does not support url formated paths, whereas okio does support url formatted file paths.

When createDirectories is called it uses lstat which returns false to okio and it will try to create all the parent directories which already exist and are read only causing the crash.

The fix is quite simple instead of calling cacheDirectory.absoluteString change it to cacheDirectory.path() which removes the file: and lstat reports back correctly.

Upvotes: 3

Related Questions