Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NSInputStream.source() and BufferedSource.inputStream() functions for Apple's NSInputStream #1123

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e0548f2
NSInputStream.source() and BufferedSource.inputStream(): NSInputStream
jeffdgr8 Jun 21, 2022
5578fd8
Set streamError and return -1 on read failure
jeffdgr8 Jun 21, 2022
b95de57
Implement NSInputStream.getBuffer()
jeffdgr8 Jun 21, 2022
ddb66c3
convert() native types
jeffdgr8 Jun 21, 2022
a6c3e16
Test NSInputStream.close()
jeffdgr8 Jun 21, 2022
7a8c292
Additional test assertions
jeffdgr8 Jun 22, 2022
b8d1ba9
appleTest TestUtil
jeffdgr8 Jun 22, 2022
c319152
Add doc comments
jeffdgr8 Jun 22, 2022
506fa4f
Resolve checks
jeffdgr8 Jun 22, 2022
aa0b6d3
Avoid data copy & read source to buffer
jeffdgr8 Jun 25, 2022
9ac4e4d
Resolve checks
jeffdgr8 Jun 27, 2022
7e9cbfa
Merge branch 'master' into nsinputstream
jeffdgr8 Jul 22, 2022
72efc44
Override open() as no-op
jeffdgr8 Jul 22, 2022
0f74ac7
Rename variable to avoid ambiguity
jeffdgr8 Jul 22, 2022
3191c97
Move private functions in class
jeffdgr8 Jul 22, 2022
031a3a1
Code review feedback
jeffdgr8 Jul 25, 2022
3571012
Replace additional RealBufferedSource.commonExhausted() logic
jeffdgr8 Jul 25, 2022
41c38a5
Remove variable
jeffdgr8 Jul 25, 2022
c59ea79
Keep buffer pinned for getBuffer() caller
jeffdgr8 Jul 25, 2022
fb883f9
null pinnedBuffer after unpin()
jeffdgr8 Jul 25, 2022
e554a72
Merge branch 'master' into nsinputstream
jeffdgr8 Jul 27, 2022
f917c2e
Merge remote-tracking branch 'upstream/master' into nsinputstream
jeffdgr8 Mar 1, 2023
a40e1c7
Fix lint errors
jeffdgr8 Mar 1, 2023
97192bb
Merge remote-tracking branch 'upstream/master' into nsinputstream
jeffdgr8 Aug 9, 2023
bbddc69
Add NSOutputStream extensions
jeffdgr8 Aug 10, 2023
8b3fe7e
Fix from https://github.com/Kotlin/kotlinx-io/issues/215
jeffdgr8 Aug 24, 2023
f5d63c0
Remove comment
jeffdgr8 Aug 24, 2023
4b7600f
Merge branch 'master' into nsinputstream
jeffdgr8 Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ androidx-test-runner = { module = "androidx.test:runner", version = "1.5.2" }
binaryCompatibilityValidator = { module = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin", version = "0.16.3" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.3" }
kotlin-time = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" }
jmh-gradle-plugin = { module = "me.champeau.jmh:jmh-gradle-plugin", version = "0.7.2" }
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
Expand Down
5 changes: 5 additions & 0 deletions okio/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ kotlin {
nativeTest.dependsOn(nonWasmTest)
nativeTest.dependsOn(zlibTest)
createSourceSet("appleTest", parent = nativeTest, children = appleTargets)
.apply {
dependencies {
implementation(libs.kotlin.coroutines)
jeffdgr8 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions okio/src/appleMain/kotlin/okio/ApplePlatform.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package okio

import kotlinx.cinterop.UnsafeNumber
import platform.Foundation.NSError
import platform.Foundation.NSLocalizedDescriptionKey
import platform.Foundation.NSUnderlyingErrorKey

@OptIn(UnsafeNumber::class)
internal fun Exception.toNSError() = NSError(
domain = "Kotlin",
code = 0,
userInfo = mapOf(
NSLocalizedDescriptionKey to message,
NSUnderlyingErrorKey to this,
),
)
172 changes: 172 additions & 0 deletions okio/src/appleMain/kotlin/okio/BufferedSink.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio

import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.ref.WeakReference
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.convert
import platform.Foundation.NSError
import platform.Foundation.NSOutputStream
import platform.Foundation.NSRunLoop
import platform.Foundation.NSRunLoopMode
import platform.Foundation.NSStream
import platform.Foundation.NSStreamDataWrittenToMemoryStreamKey
import platform.Foundation.NSStreamDelegateProtocol
import platform.Foundation.NSStreamEvent
import platform.Foundation.NSStreamEventErrorOccurred
import platform.Foundation.NSStreamEventHasSpaceAvailable
import platform.Foundation.NSStreamEventOpenCompleted
import platform.Foundation.NSStreamPropertyKey
import platform.Foundation.NSStreamStatusClosed
import platform.Foundation.NSStreamStatusError
import platform.Foundation.NSStreamStatusNotOpen
import platform.Foundation.NSStreamStatusOpen
import platform.Foundation.NSStreamStatusOpening
import platform.Foundation.NSStreamStatusWriting
import platform.Foundation.performInModes
import platform.darwin.NSInteger
import platform.darwin.NSUInteger
import platform.posix.uint8_tVar

/**
* Returns an output stream that writes to this sink. Closing the stream will also close this sink.
*/
fun BufferedSink.outputStream(): NSOutputStream = BufferedSinkNSOutputStream(this)

@OptIn(UnsafeNumber::class, ExperimentalNativeApi::class)
private class BufferedSinkNSOutputStream(
private val sink: BufferedSink,
) : NSOutputStream(toMemory = Unit), NSStreamDelegateProtocol {

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

private var status = NSStreamStatusNotOpen
private var error: NSError? = null
set(value) {
status = NSStreamStatusError
field = value
postEvent(NSStreamEventErrorOccurred)
sink.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)
postEvent(NSStreamEventHasSpaceAvailable)
}
}

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

override fun write(buffer: CPointer<uint8_tVar>?, maxLength: NSUInteger): NSInteger {
if (streamStatus != NSStreamStatusOpen || buffer == null) return -1
status = NSStreamStatusWriting
val toWrite = minOf(maxLength, Int.MAX_VALUE.convert()).toInt()
return try {
sink.buffer.write(buffer, toWrite)
sink.emitCompleteSegments()
status = NSStreamStatusOpen
toWrite.convert()
} catch (e: Exception) {
error = e.toNSError()
-1
}
}

override fun hasSpaceAvailable() = !isFinished

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

override fun propertyForKey(key: NSStreamPropertyKey): Any? = when (key) {
NSStreamDataWrittenToMemoryStreamKey -> sink.buffer.snapshotAsNSData()
else -> 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<NSStreamDelegateProtocol>? = null
private var runLoop: NSRunLoop? = null
private var runLoopModes = listOf<NSRunLoopMode>()

private fun postEvent(event: NSStreamEvent) {
val runLoop = runLoop ?: return
runLoop.performInModes(runLoopModes) {
if (runLoop == this.runLoop) {
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) {
postEvent(NSStreamEventHasSpaceAvailable)
}
}

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

override fun description() = "$sink.outputStream()"
}
Loading