From 4e25ff0a9657ee9bb75059b0d11fd169646fee1a Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Wed, 20 Nov 2024 23:06:46 +0800 Subject: [PATCH] deps: Update kotlinx-io to 0.6.0 --- buildSrc/src/main/kotlin/P.kt | 2 +- gradle/libs.versions.toml | 2 +- .../forte/simbot/message/StandardMessages.kt | 8 ++- .../love/forte/simbot/resource/IOResources.kt | 52 +++++++++++-------- .../love/forte/simbot/resource/Resource.kt | 5 ++ simbot-api/src/jvmMain/java/module-info.java | 6 +-- .../forte/simbot/resource/Resource.jvm.kt | 46 ++++++++++++---- .../resource/SourceResourceTypeTests.kt | 50 ++++++++++++++++++ 8 files changed, 129 insertions(+), 42 deletions(-) create mode 100644 simbot-api/src/jvmTest/kotlin/resource/SourceResourceTypeTests.kt diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt index 2c19ab023..99c1b1879 100644 --- a/buildSrc/src/main/kotlin/P.kt +++ b/buildSrc/src/main/kotlin/P.kt @@ -48,7 +48,7 @@ fun isSnapshot(): Boolean = _isSnapshot sealed class P(override val group: String) : ProjectDetail() { companion object { const val VERSION = "4.7.0" - const val NEXT_VERSION = "4.7.1" + const val NEXT_VERSION = "4.8.0" const val SNAPSHOT_VERSION = "$VERSION-SNAPSHOT" const val NEXT_SNAPSHOT_VERSION = "$NEXT_VERSION-SNAPSHOT" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bfec9e243..0e369ec71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.0.20" dokka = "1.9.20" kotlinx-coroutines = "1.9.0" kotlinx-serialization = "1.7.3" -kotlinx-io = "0.5.4" +kotlinx-io = "0.6.0" spring-boot-v2 = "2.7.18" spring-boot-v3 = "3.2.1" openjdk-jmh = "1.36" diff --git a/simbot-api/src/commonMain/kotlin/love/forte/simbot/message/StandardMessages.kt b/simbot-api/src/commonMain/kotlin/love/forte/simbot/message/StandardMessages.kt index 4066f592a..ef24a1a25 100644 --- a/simbot-api/src/commonMain/kotlin/love/forte/simbot/message/StandardMessages.kt +++ b/simbot-api/src/commonMain/kotlin/love/forte/simbot/message/StandardMessages.kt @@ -30,8 +30,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import love.forte.simbot.common.id.ID import love.forte.simbot.common.id.IDContainer -import love.forte.simbot.message.At.Companion.equals -import love.forte.simbot.message.At.Companion.hashCode import love.forte.simbot.message.OfflineImage.Companion.toOfflineImage import love.forte.simbot.message.Text.Companion.of import love.forte.simbot.resource.* @@ -443,6 +441,12 @@ public data class OfflineByteArrayImage(private val data: ByteArray) : OfflineIm * 远程图片通常是通过事件推送、主动上传等手段得到的、有与某个远程服务器互相对应的唯一标识的图片。 * 这个标识可能是一个ID,或一个访问链接。 * + * 对于一个远程图片的数据获取方式(比如它的二进制数据流), + * 通常取决于其对应的来源(比如bot或事件)而不是 [RemoteImage] 类型自身。 + * + * 例如一个仅包含ID的远程图片,需要拥有对应bot的认证信息才可以获取连接, + * 那么就无法仅通过此图片对象本身来获取下载连接。 + * * @see RemoteIDImage */ public interface RemoteImage : Image, IDAwareImage { diff --git a/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/IOResources.kt b/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/IOResources.kt index 2b44b4037..63246cd23 100644 --- a/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/IOResources.kt +++ b/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/IOResources.kt @@ -66,48 +66,53 @@ public annotation class ExperimentalIOResourceAPI * 如果不确定文件系统使用的路径分隔符,或可能在多个使用不同路径分隔符的系统上使用, * 则考虑使用 [fileResource(base, ...parts)][fileResource]。 * + * 对文件存在性的校验和错误报告可能不会立即报告,而是被推迟到真正读取数据时, + * 参考 [SourceResource.source] 的可能异常。 + * * @param filePath 文件路径,是使用 _路径分隔符_ 的多个片段。 * 其中, _路径分隔符_ 在不同的文件系统中可能是不同的,例如在 Unit 中的 `/` * 和在 Windows 的 `\`。 * - * @throws kotlinx.io.files.FileNotFoundException see [kotlinx.io.files.FileSystem.source]. - * @throws kotlinx.io.IOException see [kotlinx.io.files.FileSystem.source]. + * @see SourceResource * * @since 4.7.0 */ @JvmName("valueOfPath") @ExperimentalIOResourceAPI -@Throws(Exception::class) -public fun fileResource(filePath: String): Resource { - val path = Path(filePath) - return FilePathResource(path) +public fun fileResource(filePath: String): SourceResource { + return Path(filePath).toResource() } /** * 根据文件路径片段集得到一个基于对应文件的 [Resource]。 * - * 文件会先在初始化时构造 [RawSource], 而后在读取 [Resource.data] - * 时使用 [Source]. 因此对文件存在性的校验和错误报告可能不会立即报告, - * 而是被推迟到真正读取数据时。 - * - * 文件会在通过 [Resource.data] 读取数据时才会校验存在性。届时如果文件不存在, - * 则会得到 [IllegalStateException] 异常。 - * 此异常的 [IllegalStateException.cause] 可能是: - * - [kotlinx.io.files.FileNotFoundException] - * - [kotlinx.io.IOException] - * 如果是这两个类型,则成因参考 [kotlinx.io.files.FileSystem.source]。 + * 对文件存在性的校验和错误报告可能不会立即报告,而是被推迟到真正读取数据时, + * 参考 [SourceResource.source] 的可能异常。 * - * @throws kotlinx.io.files.FileNotFoundException see [kotlinx.io.files.FileSystem.source]. - * @throws kotlinx.io.IOException see [kotlinx.io.files.FileSystem.source]. + * @see SourceResource * * @since 4.7.0 */ @JvmName("valueOfPath") @ExperimentalIOResourceAPI -@Throws(Exception::class) -public fun fileResource(base: String, vararg parts: String): Resource { - val path = Path(base, *parts) - return FilePathResource(path) +public fun fileResource(base: String, vararg parts: String): SourceResource { + return Path(base, *parts).toResource() +} + +/** + * 使用 [Path] 直接构建一个 [FilePathResource] + * + * 对文件存在性的校验和错误报告可能不会立即报告,而是被推迟到真正读取数据时, + * 参考 [SourceResource.source] 的可能异常。 + * + * @see SourceResource + * + * @since 4.8.0 + */ +@JvmName("valueOf") +@ExperimentalIOResourceAPI +public fun Path.toResource(): SourceResource { + return FilePathResource(this) } /** @@ -121,6 +126,9 @@ public fun fileResource(base: String, vararg parts: String): Resource { public interface SourceResource : Resource { /** * 得到一个用于本次数据读取的 [Source]. + * + * 每次都将构建一个新的 [Source], 你需要自行按需[关闭][Source.close]它们。 + * * @throws kotlinx.io.files.FileNotFoundException * see [kotlinx.io.files.FileSystem.source], [RawSource.buffered] * @throws kotlinx.io.IOException diff --git a/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/Resource.kt b/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/Resource.kt index 32cce4019..bd4a44652 100644 --- a/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/Resource.kt +++ b/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/Resource.kt @@ -54,9 +54,14 @@ import kotlin.jvm.JvmName * * [Resource] 主要由内部实现,不保证对第三方实现的稳定与兼容 * + * @see ByteArrayResource + * @see SourceResource + * * @author ForteScarlet */ public interface Resource { + // TODO become `sealed` for ByteArrayResource and SourceResource. + /** * 读取此资源的字节数据。 * diff --git a/simbot-api/src/jvmMain/java/module-info.java b/simbot-api/src/jvmMain/java/module-info.java index 30e5ac317..6b26db56c 100644 --- a/simbot-api/src/jvmMain/java/module-info.java +++ b/simbot-api/src/jvmMain/java/module-info.java @@ -21,11 +21,6 @@ * */ -import love.forte.simbot.component.ComponentFactoryConfigurerProvider; -import love.forte.simbot.component.ComponentFactoryProvider; -import love.forte.simbot.plugin.PluginFactoryConfigurerProvider; -import love.forte.simbot.plugin.PluginFactoryProvider; - module simbot.api { requires kotlin.stdlib; requires simbot.logger; @@ -38,6 +33,7 @@ requires kotlinx.coroutines.core; requires kotlinx.serialization.core; requires kotlinx.serialization.json; + requires kotlinx.io.core; requires static kotlinx.coroutines.reactive; requires static kotlinx.coroutines.reactor; requires static kotlinx.coroutines.rx2; diff --git a/simbot-api/src/jvmMain/kotlin/love/forte/simbot/resource/Resource.jvm.kt b/simbot-api/src/jvmMain/kotlin/love/forte/simbot/resource/Resource.jvm.kt index 278e492b9..91e5a1958 100644 --- a/simbot-api/src/jvmMain/kotlin/love/forte/simbot/resource/Resource.jvm.kt +++ b/simbot-api/src/jvmMain/kotlin/love/forte/simbot/resource/Resource.jvm.kt @@ -26,6 +26,10 @@ package love.forte.simbot.resource +import kotlinx.io.Source +import kotlinx.io.asSource +import kotlinx.io.buffered +import love.forte.simbot.resource.JvmStringReadableResource.Companion.DEFAULT_CHARSET import java.io.* import java.net.MalformedURLException import java.net.URI @@ -34,6 +38,7 @@ import java.net.URL import java.nio.charset.Charset import java.nio.file.OpenOption import java.nio.file.Path +import kotlin.Throws import kotlin.io.path.inputStream import kotlin.io.path.readBytes import kotlin.io.path.reader @@ -43,8 +48,8 @@ import kotlin.io.path.reader * * @author forte */ +@OptIn(ExperimentalIOResourceAPI::class) public interface InputStreamResource : Resource { - /** * 读取当前资源的所有字节数据。 * 默认通过 [inputStream] 读取。 @@ -106,7 +111,7 @@ public interface ReaderResource : JvmStringReadableResource { * 默认使用 [JvmStringReadableResource.DEFAULT_CHARSET] 编码。 */ @Throws(IOException::class) - public fun reader(): Reader = reader(JvmStringReadableResource.DEFAULT_CHARSET) + public fun reader(): Reader = reader(DEFAULT_CHARSET) /** * 获取可用于读取当前资源数据的读取流。 @@ -140,7 +145,7 @@ public interface FileResource : InputStreamResource, ReaderResource { * @throws FileNotFoundException 如果文件不存在 */ @Throws(FileNotFoundException::class) - override fun reader(): Reader = reader(JvmStringReadableResource.DEFAULT_CHARSET) + override fun reader(): Reader = reader(DEFAULT_CHARSET) /** * 从与此资源关联的 [File] 创建新的 [Reader] @@ -162,7 +167,7 @@ public interface FileResource : InputStreamResource, ReaderResource { * @throws IOException 如果文件无法读取 */ @Throws(IOException::class) - override fun string(): String = string(JvmStringReadableResource.DEFAULT_CHARSET) + override fun string(): String = string(DEFAULT_CHARSET) /** * 将与此资源关联的 [File] 读取为字符串 @@ -181,15 +186,22 @@ public interface FileResource : InputStreamResource, ReaderResource { */ @JvmName("valueOf") @JvmOverloads -public fun File.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET): FileResource = +public fun File.toResource(charset: Charset = DEFAULT_CHARSET): FileResource = FileResourceImpl(this, charset) -private data class FileResourceImpl(override val file: File, private val charset: Charset) : FileResource { +@OptIn(ExperimentalIOResourceAPI::class) +private data class FileResourceImpl(override val file: File, private val charset: Charset) : + FileResource, SourceResource { override fun string(): String = string(charset) override fun reader(): Reader = reader(charset) override fun string(charset: Charset): String = file.readText(charset) override fun reader(charset: Charset): Reader = file.reader(charset) + + override fun data(): ByteArray = file.readBytes() + + override fun source(): Source = inputStream().asSource().buffered() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is FileResourceImpl) return false @@ -261,16 +273,17 @@ public interface PathResource : InputStreamResource, ReaderResource { @JvmName("valueOf") @JvmOverloads public fun Path.toResource( - charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET, + charset: Charset = DEFAULT_CHARSET, vararg options: OpenOption ): PathResource = PathResourceImpl(this, charset, options) +@OptIn(ExperimentalIOResourceAPI::class) private data class PathResourceImpl( override val path: Path, private val charset: Charset, private val openOptions: Array -) : PathResource { +) : PathResource, SourceResource { override fun inputStream(): InputStream = path.inputStream(options = openOptions) override fun reader(): Reader = reader(charset) @@ -279,6 +292,10 @@ private data class PathResourceImpl( override fun reader(charset: Charset): Reader = path.reader(charset, options = openOptions) override fun string(charset: Charset): String = reader(charset).use(Reader::readText) + override fun data(): ByteArray = path.readBytes() + + override fun source(): Source = inputStream().asSource().buffered() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is PathResourceImpl) return false @@ -369,7 +386,7 @@ public interface URIResource : InputStreamResource, JvmStringReadableResource { @kotlin.jvm.Throws(URISyntaxException::class) @JvmName("valueOf") @JvmOverloads -public fun URL.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET): URIResource = +public fun URL.toResource(charset: Charset = DEFAULT_CHARSET): URIResource = URIResourceImpl(toURI(), charset, this) /** @@ -380,10 +397,13 @@ public fun URL.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_C */ @JvmName("valueOf") @JvmOverloads -public fun URI.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET): URIResource = +public fun URI.toResource(charset: Charset = DEFAULT_CHARSET): URIResource = URIResourceImpl(this, charset, null) -private class URIResourceImpl(override val uri: URI, val charset: Charset, private var url: URL? = null) : URIResource { +@OptIn(ExperimentalIOResourceAPI::class) +private class URIResourceImpl(override val uri: URI, val charset: Charset, private var url: URL? = null) : + URIResource, + SourceResource { private val urlValue: URL get() = url ?: run { uri.toURL().also { url = it } @@ -393,5 +413,9 @@ private class URIResourceImpl(override val uri: URI, val charset: Charset, priva override fun string(): String = string(charset) override fun string(charset: Charset): String = urlValue.readText(charset) + override fun data(): ByteArray = inputStream().use { stream -> stream.readAllBytes() } + + override fun source(): Source = inputStream().asSource().buffered() + override fun toString(): String = "URIResource(uri=$uri, charset=$charset)" } diff --git a/simbot-api/src/jvmTest/kotlin/resource/SourceResourceTypeTests.kt b/simbot-api/src/jvmTest/kotlin/resource/SourceResourceTypeTests.kt new file mode 100644 index 000000000..0a074f92d --- /dev/null +++ b/simbot-api/src/jvmTest/kotlin/resource/SourceResourceTypeTests.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * Project https://github.com/simple-robot/simpler-robot + * Email ForteScarlet@163.com + * + * This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + * + */ + +package resource + +import love.forte.simbot.resource.ExperimentalIOResourceAPI +import love.forte.simbot.resource.SourceResource +import love.forte.simbot.resource.toResource +import java.io.File +import java.net.URI +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertIs + + +/** + * + * @author ForteScarlet + */ +@OptIn(ExperimentalIOResourceAPI::class) +class SourceResourceTypeTests { + + @Test + fun testSourceResourceInheritance() { + assertIs(File(".").toResource()) + assertIs(Path(".").toResource()) + assertIs(URI.create("https://localhost:8080").toResource()) + } + +}