From dc96be2a52f2a598f364ea3b410bb34fd4ac1278 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Mon, 14 Oct 2024 21:26:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E5=A2=9E=E5=8A=A0=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=9F=BA=E4=BA=8E=E6=96=87=E4=BB=B6=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=9A=84=20Resource=E3=80=81Image=20API=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + buildSrc/src/main/kotlin/P.kt | 2 +- simbot-api/api/simbot-api.api | 3 + .../forte/simbot/message/StandardMessages.kt | 35 ++++- .../love/forte/simbot/resource/IOResources.kt | 136 ++++++++++++++++++ .../love/forte/simbot/resource/Resource.kt | 7 + 6 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/IOResources.kt diff --git a/build.gradle.kts b/build.gradle.kts index 21d0ce3d0..9084d89a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -159,6 +159,7 @@ apiValidation { listOf( "love.forte.simbot.annotations.ExperimentalSimbotAPI", "love.forte.simbot.annotations.InternalSimbotAPI", + "love.forte.simbot.resource.ExperimentalIOResourceAPI", ), ) diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt index f8ce566b4..191debc30 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.6.1" - const val NEXT_VERSION = "4.6.2" + const val NEXT_VERSION = "4.7.0" const val SNAPSHOT_VERSION = "$VERSION-SNAPSHOT" const val NEXT_SNAPSHOT_VERSION = "$NEXT_VERSION-SNAPSHOT" diff --git a/simbot-api/api/simbot-api.api b/simbot-api/api/simbot-api.api index 33103eb53..2126040f4 100644 --- a/simbot-api/api/simbot-api.api +++ b/simbot-api/api/simbot-api.api @@ -2532,6 +2532,9 @@ public abstract interface class love/forte/simbot/resource/ByteArrayResource : l public abstract fun data ()[B } +public abstract interface annotation class love/forte/simbot/resource/ExperimentalIOResourceAPI : java/lang/annotation/Annotation { +} + public abstract interface class love/forte/simbot/resource/FileResource : love/forte/simbot/resource/InputStreamResource, love/forte/simbot/resource/ReaderResource { public fun data ()[B public abstract fun getFile ()Ljava/io/File; 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 f53a05877..4066f592a 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 @@ -34,9 +34,7 @@ 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.ByteArrayResource -import love.forte.simbot.resource.Resource -import love.forte.simbot.resource.ResourceBase64Serializer +import love.forte.simbot.resource.* import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.js.JsName import kotlin.jvm.* @@ -340,6 +338,37 @@ public interface OfflineImage : Image { else -> toOfflineResourceImage() } + /** + * 使用文件路径读取文件 `Path` 作为 [OfflineImage]。 + * 相当于通过 [fileResource] 产物使用 [toOfflineImage]。 + * + * 更多说明和注意事项参考 [fileResource]。 + * + * @see fileResource + * + * @since 4.7.0 + */ + @JvmStatic + @JvmName("ofFilePath") + @ExperimentalIOResourceAPI + public fun fileOfflineImage(filePath: String): OfflineImage = + fileResource(filePath).toOfflineResourceImage() + + /** + * 使用文件路径读取文件 `Path` 作为 [OfflineImage]。 + * 相当于通过 [fileResource] 产物使用 [toOfflineImage]。 + * + * 更多说明和注意事项参考 [fileResource]。 + * + * @see fileResource + * + * @since 4.7.0 + */ + @JvmStatic + @ExperimentalIOResourceAPI + @JvmName("ofFilePath") + public fun fileOfflineImage(base: String, vararg parts: String): OfflineImage = + fileResource(base, *parts).toOfflineResourceImage() } } 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 new file mode 100644 index 000000000..385b2a19f --- /dev/null +++ b/simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/IOResources.kt @@ -0,0 +1,136 @@ +/* + * 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 . + * + */ +@file:JvmName("Resources") +@file:JvmMultifileClass + +package love.forte.simbot.resource + +import kotlinx.io.* +import kotlinx.io.files.FileNotFoundException +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlin.annotation.AnnotationTarget.* +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + +/** + * 一些尚处于实验阶段的、基于IO(主要指文件系统相关)的 [Resource] 相关API。 + * + * 可能会在未来发生变更、或被删除,且不保证兼容性与稳定性。 + * + * @since 4.7.0 + */ +@RequiresOptIn("Experimental IO Resource API") +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +@Target( + CLASS, + ANNOTATION_CLASS, + PROPERTY, + FIELD, + LOCAL_VARIABLE, + VALUE_PARAMETER, + CONSTRUCTOR, + FUNCTION, + PROPERTY_GETTER, + PROPERTY_SETTER, + TYPEALIAS +) +public annotation class ExperimentalIOResourceAPI + +/** + * 根据完整的文件路径 [filePath] 得到一个基于对应文件的 [Resource]。 + * + * 文件会在通过 [Resource.data] 读取数据时才会校验存在性。届时如果文件不存在, + * 则会得到 [IllegalStateException] 异常。 + * + * 如果不确定文件系统使用的路径分隔符,或可能在多个使用不同路径分隔符的系统上使用, + * 则考虑使用 [fileResource(base, ...parts)][fileResource]。 + * + * @param filePath 文件路径,是使用 _路径分隔符_ 的多个片段。 + * 其中, _路径分隔符_ 在不同的文件系统中可能是不同的,例如在 Unit 中的 `/` + * 和在 Windows 的 `\`。 + * + * @since 4.7.0 + */ +@JvmName("valueOfPath") +@ExperimentalIOResourceAPI +public fun fileResource(filePath: String): Resource { + val path = Path(filePath) + return FilePathResource(path) +} + +/** + * 根据文件路径片段集得到一个基于对应文件的 [Resource]。 + * + * 文件会在通过 [Resource.data] 读取数据时才会校验存在性。届时如果文件不存在, + * 则会得到 [IllegalStateException] 异常。 + * 此异常的 [IllegalStateException.cause] 可能是: + * - [kotlinx.io.files.FileNotFoundException] + * - [kotlinx.io.IOException] + * 如果是这两个类型,则成因参考 [kotlinx.io.files.FileSystem.source]。 + * + * @since 4.7.0 + */ +@JvmName("valueOfPath") +@ExperimentalIOResourceAPI +public fun fileResource(base: String, vararg parts: String): Resource { + val path = Path(base, *parts) + return FilePathResource(path) +} + +/** + * 一个可以得到 [kotlinx.io.RawSource] 的 [Resource]。 + * + * @since 4.7.0 + */ +@ExperimentalIOResourceAPI +public interface RawSourceResource : Resource { + public fun source(): RawSource + + override fun data(): ByteArray { + return source().buffered().use { it.readByteArray() } + } +} + +/** + * 一个可以得到 [kotlinx.io.Source] 的 [Resource]。 + * + * @since 4.7.0 + */ +@ExperimentalIOResourceAPI +public interface SourceResource : RawSourceResource { + override fun source(): Source +} + +@ExperimentalIOResourceAPI +private data class FilePathResource(val path: Path) : RawSourceResource { + override fun source(): RawSource = try { + SystemFileSystem.source(path) + } catch (fnf: FileNotFoundException) { + throw IllegalStateException(fnf.message, fnf) + } catch (io: IOException) { + throw IllegalStateException(io.message, io) + } +} + 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 7a3522399..32cce4019 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 @@ -87,6 +87,13 @@ public interface ByteArrayResource : Resource { /** * 基于 [Base64] 的 [Resource] 序列化器。 + * + * 它会将任何 [Resource] 都根据 [Resource.data] 序列化为 Base64 数据, + * 并将任意序列化后的数据反序列化为 [ByteArrayResource]。 + * + * 也因此,这会导致: + * - 序列化时会读取数据、产生读取开销。 + * - 反序列化后的类型可能与原本的类型不一致。 */ @ExperimentalEncodingApi public object ResourceBase64Serializer : KSerializer {