Skip to content

Instantly share code, notes, and snippets.

@Skeptick
Created September 19, 2022 15:38
Show Gist options
  • Save Skeptick/0daa7c215fb8b1ddea71e4197c023676 to your computer and use it in GitHub Desktop.
Save Skeptick/0daa7c215fb8b1ddea71e4197c023676 to your computer and use it in GitHub Desktop.
iOS ByteWriteChannel
import io.ktor.client.utils.*
import io.ktor.utils.io.*
import io.ktor.utils.io.bits.*
import kotlinx.cinterop.*
import kotlinx.coroutines.*
import platform.CoreFoundation.*
import platform.Foundation.*
import platform.UIKit.*
import platform.posix.memcpy
import platform.posix.uint8_tVar
actual fun ByteWriteChannel(fileName: String): ByteWriteChannel {
val tempDirUrl = NSFileManager.defaultManager.temporaryDirectory
val fileUrl = tempDirUrl.URLByAppendingPathComponent(fileName) ?: error("Cannot create temp file")
val output = NSOutputStream.outputStreamWithURL(fileUrl, false) ?: error("Cannot create temp file")
output.open()
return output.toByteWriteChannel(fileName)
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun NSOutputStream.toByteWriteChannel(fileName: String): ByteWriteChannel {
val outputStream = this
val coroutineContext = newSingleThreadContext("write-channel-$fileName")
return CoroutineScope(coroutineContext).reader(autoFlush = true) {
memScoped {
try {
val buffer = allocArray<uint8_tVar>(DEFAULT_HTTP_BUFFER_SIZE.convert())
copyChannel(channel, outputStream, buffer)
} catch (cause: Throwable) {
cause.printStackTrace()
channel.cancel(cause)
} finally {
outputStream.close()
coroutineContext.close()
}
}
}.channel
}
private suspend fun copyChannel(input: ByteReadChannel, output: NSOutputStream, buffer: CArrayPointer<uint8_tVar>) {
while (!input.isClosedForRead) {
val memoryBuffer = Memory.of(buffer, DEFAULT_HTTP_BUFFER_SIZE)
input.read { source, start, end ->
val size = end - start
source.copyTo(memoryBuffer, start, size, 0)
memcpy(buffer, source.pointer, DEFAULT_HTTP_BUFFER_SIZE.convert())
output.write(buffer, size.convert())
size.toInt()
}
}
}
@SpectralDragon
Copy link

SpectralDragon commented Oct 16, 2022

This code works for me

@OptIn(ExperimentalCoroutinesApi::class)
private fun NSOutputStream.toByteWriteChannel(path: String): ByteWriteChannel {
    val outputStream = this
    val coroutineContext = newSingleThreadContext("write-channel-$path")
    return CoroutineScope(coroutineContext).reader(autoFlush = true) {
        try {
            copyForByteWriteChannel(channel, outputStream)
        } catch (cause: Throwable) {
            cause.printStackTrace()
            channel.cancel(cause)
        } finally {
            outputStream.close()
            coroutineContext.close()
        }
    }.channel
}

private suspend fun copyForByteWriteChannel(
    input: ByteReadChannel,
    output: NSOutputStream
) {
    memScoped {
        val buffer = allocArray<uint8_tVar>(DEFAULT_HTTP_BUFFER_SIZE.convert())
        val memoryBuffer = Memory.of(buffer, DEFAULT_HTTP_BUFFER_SIZE.convert())
        while (!input.isClosedForRead) {
            input.read { source, start, end ->
                val size = end - start
                source.copyTo(memoryBuffer.pointer, start, size, 0)
                output.write(buffer, size.convert())
                size.toInt()
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment