Browse Source

[resources] Implement random bytes read on supported targets

pull/4559/head
Konstantin Tskhovrebov 2 months ago
parent
commit
9717742210
  1. 39
      components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.android.kt
  2. 11
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt
  3. 31
      components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt
  4. 41
      components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt
  5. 31
      components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt
  6. 51
      components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt
  7. 49
      components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt

39
components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.android.kt

@ -1,18 +1,33 @@
package org.jetbrains.compose.resources
import java.io.File
import java.io.InputStream
private object AndroidResourceReader
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
override suspend fun read(path: String): ByteArray {
val resource = getResourceAsStream(path)
return resource.readBytes()
}
@OptIn(ExperimentalResourceApi::class)
@InternalResourceApi
actual suspend fun readResourceBytes(path: String): ByteArray {
val classLoader = Thread.currentThread().contextClassLoader ?: AndroidResourceReader.javaClass.classLoader
val resource = classLoader.getResourceAsStream(path) ?: run {
//try to find a font in the android assets
if (File(path).parentFile?.name.orEmpty().startsWith("font")) {
classLoader.getResourceAsStream("assets/$path")
} else null
} ?: throw MissingResourceException(path)
return resource.readBytes()
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
val resource = getResourceAsStream(path)
val result = ByteArray(size.toInt())
resource.use { input ->
input.skip(offset)
input.read(result, 0, size.toInt())
}
return result
}
@OptIn(ExperimentalResourceApi::class)
private fun getResourceAsStream(path: String): InputStream {
val classLoader = Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader
val resource = classLoader.getResourceAsStream(path) ?: run {
//try to find a font in the android assets
if (File(path).parentFile?.name.orEmpty().startsWith("font")) {
classLoader.getResourceAsStream("assets/$path")
} else null
} ?: throw MissingResourceException(path)
return resource
}
}

11
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt

@ -12,21 +12,16 @@ class MissingResourceException(path: String) : Exception("Missing resource with
* @return The content of the file as a byte array.
*/
@InternalResourceApi
expect suspend fun readResourceBytes(path: String): ByteArray
suspend fun readResourceBytes(path: String): ByteArray = DefaultResourceReader.read(path)
internal interface ResourceReader {
suspend fun read(path: String): ByteArray
suspend fun readPart(path: String, offset: Long, size: Long): ByteArray
}
internal val DefaultResourceReader: ResourceReader = object : ResourceReader {
@OptIn(InternalResourceApi::class)
override suspend fun read(path: String): ByteArray = readResourceBytes(path)
internal expect fun getPlatformResourceReader(): ResourceReader
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
TODO("Not yet implemented")
}
}
internal val DefaultResourceReader = getPlatformResourceReader()
//ResourceReader provider will be overridden for tests
internal val LocalResourceReader = staticCompositionLocalOf { DefaultResourceReader }

31
components/resources/library/src/desktopMain/kotlin/org/jetbrains/compose/resources/ResourceReader.desktop.kt

@ -1,11 +1,26 @@
package org.jetbrains.compose.resources
private object JvmResourceReader
@OptIn(ExperimentalResourceApi::class)
@InternalResourceApi
actual suspend fun readResourceBytes(path: String): ByteArray {
val classLoader = Thread.currentThread().contextClassLoader ?: JvmResourceReader.javaClass.classLoader
val resource = classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)
return resource.readBytes()
import java.io.InputStream
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
override suspend fun read(path: String): ByteArray {
val resource = getResourceAsStream(path)
return resource.readBytes()
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
val resource = getResourceAsStream(path)
val result = ByteArray(size.toInt())
resource.use { input ->
input.skip(offset)
input.read(result, 0, size.toInt())
}
return result
}
@OptIn(ExperimentalResourceApi::class)
private fun getResourceAsStream(path: String): InputStream {
val classLoader = Thread.currentThread().contextClassLoader ?: this.javaClass.classLoader
return classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)
}
}

41
components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt

@ -2,20 +2,39 @@ package org.jetbrains.compose.resources
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.NSBundle
import platform.Foundation.NSFileManager
import platform.Foundation.*
import platform.posix.memcpy
@OptIn(ExperimentalResourceApi::class)
@InternalResourceApi
actual suspend fun readResourceBytes(path: String): ByteArray {
val fileManager = NSFileManager.defaultManager()
// todo: support fallback path at bundle root?
val composeResourcesPath = NSBundle.mainBundle.resourcePath + "/compose-resources/" + path
val contentsAtPath = fileManager.contentsAtPath(composeResourcesPath) ?: throw MissingResourceException(path)
return ByteArray(contentsAtPath.length.toInt()).apply {
usePinned {
memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length)
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
override suspend fun read(path: String): ByteArray {
val data = readData(getPathInBundle(path))
return ByteArray(data.length.toInt()).apply {
usePinned { memcpy(it.addressOf(0), data.bytes, data.length) }
}
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
val data = readData(getPathInBundle(path), offset, size)
return ByteArray(data.length.toInt()).apply {
usePinned { memcpy(it.addressOf(0), data.bytes, data.length) }
}
}
private fun readData(path: String): NSData {
return NSFileManager.defaultManager().contentsAtPath(path) ?: throw MissingResourceException(path)
}
private fun readData(path: String, offset: Long, size: Long): NSData {
val fileHandle = NSFileHandle.fileHandleForReadingAtPath(path) ?: throw MissingResourceException(path)
fileHandle.seekToOffset(offset.toULong(), null)
val result = fileHandle.readDataOfLength(size.toULong())
fileHandle.closeFile()
return result
}
private fun getPathInBundle(path: String): String {
// todo: support fallback path at bundle root?
return NSBundle.mainBundle.resourcePath + "/compose-resources/" + path
}
}

31
components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt

@ -5,16 +5,27 @@ import kotlinx.coroutines.await
import org.khronos.webgl.ArrayBuffer
import org.khronos.webgl.Int8Array
private fun ArrayBuffer.toByteArray(): ByteArray =
Int8Array(this, 0, byteLength).unsafeCast<ByteArray>()
@OptIn(ExperimentalResourceApi::class)
@InternalResourceApi
actual suspend fun readResourceBytes(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await()
if (!response.ok) {
throw MissingResourceException(resPath)
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readAsArrayBuffer(path).let { buffer ->
buffer.toByteArray(0, buffer.byteLength)
}
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
return readAsArrayBuffer(path).toByteArray(offset.toInt(), size.toInt())
}
return response.arrayBuffer().await().toByteArray()
private suspend fun readAsArrayBuffer(path: String): ArrayBuffer {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await()
if (!response.ok) {
throw MissingResourceException(resPath)
}
return response.arrayBuffer().await()
}
private fun ArrayBuffer.toByteArray(offset: Int, size: Int): ByteArray =
Int8Array(this, offset, size).unsafeCast<ByteArray>()
}

51
components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt

@ -2,23 +2,46 @@ package org.jetbrains.compose.resources
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.NSFileManager
import platform.Foundation.*
import platform.posix.memcpy
@OptIn(ExperimentalResourceApi::class)
@InternalResourceApi
actual suspend fun readResourceBytes(path: String): ByteArray {
val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath
val contentsAtPath = NSFileManager.defaultManager().run {
//todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain)
contentsAtPath("$currentDirectoryPath/src/macosMain/composeResources/$path")
?: contentsAtPath("$currentDirectoryPath/src/macosTest/composeResources/$path")
?: contentsAtPath("$currentDirectoryPath/src/commonMain/composeResources/$path")
?: contentsAtPath("$currentDirectoryPath/src/commonTest/composeResources/$path")
} ?: throw MissingResourceException(path)
return ByteArray(contentsAtPath.length.toInt()).apply {
usePinned {
memcpy(it.addressOf(0), contentsAtPath.bytes, contentsAtPath.length)
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
override suspend fun read(path: String): ByteArray {
val data = readData(getPathOnDisk(path))
return ByteArray(data.length.toInt()).apply {
usePinned { memcpy(it.addressOf(0), data.bytes, data.length) }
}
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
val data = readData(getPathOnDisk(path), offset, size)
return ByteArray(data.length.toInt()).apply {
usePinned { memcpy(it.addressOf(0), data.bytes, data.length) }
}
}
private fun readData(path: String): NSData {
return NSFileManager.defaultManager().contentsAtPath(path) ?: throw MissingResourceException(path)
}
private fun readData(path: String, offset: Long, size: Long): NSData {
val fileHandle = NSFileHandle.fileHandleForReadingAtPath(path) ?: throw MissingResourceException(path)
fileHandle.seekToOffset(offset.toULong(), null)
val result = fileHandle.readDataOfLength(size.toULong())
fileHandle.closeFile()
return result
}
private fun getPathOnDisk(path: String): String {
val fm = NSFileManager.defaultManager()
val currentDirectoryPath = fm.currentDirectoryPath
return listOf(
//todo in future bundle resources with app and use all sourceSets (skikoMain, nativeMain)
"$currentDirectoryPath/src/macosMain/composeResources/$path",
"$currentDirectoryPath/src/macosTest/composeResources/$path",
"$currentDirectoryPath/src/commonMain/composeResources/$path",
"$currentDirectoryPath/src/commonTest/composeResources/$path"
).firstOrNull { p -> fm.fileExistsAtPath(p) } ?: throw MissingResourceException(path)
}
}

49
components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt

@ -8,28 +8,6 @@ import org.w3c.fetch.Response
import kotlin.wasm.unsafe.UnsafeWasmMemoryApi
import kotlin.wasm.unsafe.withScopedMemoryAllocator
/**
* Reads the content of the resource file at the specified path and returns it as a byte array.
*
* @param path The path of the file to read in the resource's directory.
* @return The content of the file as a byte array.
*/
@OptIn(ExperimentalResourceApi::class)
@InternalResourceApi
actual suspend fun readResourceBytes(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await<Response>()
if (!response.ok) {
throw MissingResourceException(resPath)
}
return response.arrayBuffer().await<ArrayBuffer>().toByteArray()
}
private fun ArrayBuffer.toByteArray(): ByteArray {
val source = Int8Array(this, 0, byteLength)
return jsInt8ArrayToKotlinByteArray(source)
}
@JsFun(
""" (src, size, dstAddr) => {
const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size);
@ -49,4 +27,31 @@ internal fun jsInt8ArrayToKotlinByteArray(x: Int8Array): ByteArray {
jsExportInt8ArrayToWasm(x, size, dstAddress)
ByteArray(size) { i -> (memBuffer + i).loadByte() }
}
}
@OptIn(ExperimentalResourceApi::class)
internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
override suspend fun read(path: String): ByteArray {
return readAsArrayBuffer(path).let { buffer ->
buffer.toByteArray(0, buffer.byteLength)
}
}
override suspend fun readPart(path: String, offset: Long, size: Long): ByteArray {
return readAsArrayBuffer(path).toByteArray(offset.toInt(), size.toInt())
}
private suspend fun readAsArrayBuffer(path: String): ArrayBuffer {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await<Response>()
if (!response.ok) {
throw MissingResourceException(resPath)
}
return response.arrayBuffer().await()
}
private fun ArrayBuffer.toByteArray(offset: Int, size: Int): ByteArray {
val source = Int8Array(this, offset, size)
return jsInt8ArrayToKotlinByteArray(source)
}
}
Loading…
Cancel
Save