All Downloads are FREE. Search and download functionalities are using the official Maven repository.

appleMain.SourcesApple.kt Maven / Gradle / Ivy

There is a newer version: 0.6.0
Show newest version
/*
 * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file.
 */

@file:OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class)

package kotlinx.io

import kotlinx.cinterop.*
import platform.Foundation.*
import platform.darwin.NSInteger
import platform.darwin.NSUInteger
import platform.darwin.NSUIntegerVar
import platform.posix.uint8_tVar
import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.ref.WeakReference

/**
 * Returns an input stream that reads from this source. Closing the stream will also close this source.
 *
 * The stream supports both polling and run-loop scheduling, please check
 * [Apple's documentation](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Streams/Articles/PollingVersusRunloop.html)
 * for information about stream events handling.
 *
 * The stream does not implement initializers
 * ([NSInputStream.initWithURL](https://developer.apple.com/documentation/foundation/nsinputstream/1417891-initwithurl),
 * [NSInputStream.initWithData](https://developer.apple.com/documentation/foundation/nsinputstream/1412470-initwithdata),
 * [NSInputStream.initWithFileAtPath](https://developer.apple.com/documentation/foundation/nsinputstream/1408976-initwithfileatpath)),
 * their use will result in a runtime error.
 *
 * @sample kotlinx.io.samples.KotlinxIoSamplesApple.asStream
 */
public fun Source.asNSInputStream(): NSInputStream = SourceNSInputStream(this)

@OptIn(InternalIoApi::class, UnsafeNumber::class)
private class SourceNSInputStream(
    private val source: Source
) : NSInputStream(NSData()), NSStreamDelegateProtocol {

    private val isClosed: () -> Boolean = when (source) {
        is RealSource -> source::closed
        is Buffer -> {
            { false }
        }
    }

    private var status = NSStreamStatusNotOpen
    private var error: NSError? = null
        set(value) {
            status = NSStreamStatusError
            field = value
            source.close()
        }

    override fun streamStatus() = if (status != NSStreamStatusError && isClosed()) NSStreamStatusClosed else status

    override fun streamError() = error

    override fun open() {
        if (status == NSStreamStatusNotOpen) {
            status = NSStreamStatusOpening
            status = NSStreamStatusOpen
            postEvent(NSStreamEventOpenCompleted)
            checkBytes()
        }
    }

    override fun close() {
        if (status == NSStreamStatusError || status == NSStreamStatusNotOpen) return
        status = NSStreamStatusClosed
        runLoop = null
        runLoopModes = listOf()
        source.close()
    }

    override fun read(buffer: CPointer?, maxLength: NSUInteger): NSInteger {
        if (streamStatus != NSStreamStatusOpen && streamStatus != NSStreamStatusAtEnd || buffer == null) return -1
        status = NSStreamStatusReading
        try {
            if (source.exhausted()) {
                status = NSStreamStatusAtEnd
                return 0
            }
            val toRead = minOf(maxLength.toLong(), source.buffer.size, Int.MAX_VALUE.toLong()).toInt()
            val read = source.buffer.readAtMostTo(buffer, toRead).convert()
            status = NSStreamStatusOpen
            checkBytes()
            return read
        } catch (e: Exception) {
            error = e.toNSError()
            postEvent(NSStreamEventErrorOccurred)
            return -1
        }
    }

    override fun getBuffer(buffer: CPointer>?, length: CPointer?) = false

    override fun hasBytesAvailable() = !isFinished

    private val isFinished
        get() = when (streamStatus) {
            NSStreamStatusClosed, NSStreamStatusError -> true
            else -> false
        }

    override fun propertyForKey(key: NSStreamPropertyKey): Any? = null

    override fun setProperty(property: Any?, forKey: NSStreamPropertyKey) = false

    // WeakReference as delegate should not be retained
    // https://developer.apple.com/documentation/foundation/nsstream/1418423-delegate
    private var _delegate: WeakReference? = null
    private var runLoop: NSRunLoop? = null
    private var runLoopModes = listOf()

    private fun postEvent(event: NSStreamEvent) {
        val runLoop = runLoop ?: return
        runLoop.performInModes(runLoopModes) {
            if (runLoop == this.runLoop) {
                delegateOrSelf.stream(this, event)
            }
        }
    }

    private fun checkBytes() {
        val runLoop = runLoop ?: return
        runLoop.performInModes(runLoopModes) {
            if (runLoop != this.runLoop || isFinished) return@performInModes
            val event = try {
                if (source.exhausted()) {
                    status = NSStreamStatusAtEnd
                    NSStreamEventEndEncountered
                } else {
                    NSStreamEventHasBytesAvailable
                }
            } catch (e: Exception) {
                error = e.toNSError()
                NSStreamEventErrorOccurred
            }
            delegateOrSelf.stream(this, event)
        }
    }

    override fun delegate() = _delegate?.value

    private val delegateOrSelf get() = delegate ?: this

    override fun setDelegate(delegate: NSStreamDelegateProtocol?) {
        _delegate = delegate?.let { WeakReference(it) }
    }

    override fun stream(aStream: NSStream, handleEvent: NSStreamEvent) {
        // no-op
    }

    override fun scheduleInRunLoop(aRunLoop: NSRunLoop, forMode: NSRunLoopMode) {
        if (runLoop == null) {
            runLoop = aRunLoop
        }
        if (runLoop == aRunLoop) {
            runLoopModes += forMode
        }
        if (status == NSStreamStatusOpen) {
            checkBytes()
        }
    }

    override fun removeFromRunLoop(aRunLoop: NSRunLoop, forMode: NSRunLoopMode) {
        if (aRunLoop == runLoop) {
            runLoopModes -= forMode
            if (runLoopModes.isEmpty()) {
                runLoop = null
            }
        }
    }

    override fun description() = "$source.asNSInputStream()"
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy