Skip to content

增加部分基于文件系统的 Resource、Image API 的实验性支持 #955

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

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ apiValidation {
listOf(
"love.forte.simbot.annotations.ExperimentalSimbotAPI",
"love.forte.simbot.annotations.InternalSimbotAPI",
"love.forte.simbot.resource.ExperimentalIOResourceAPI",
),
)

Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/P.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +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"
spring-boot-v2 = "2.7.18"
spring-boot-v3 = "3.2.1"
openjdk-jmh = "1.36"
Expand Down Expand Up @@ -58,6 +59,9 @@ kotlinx-serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotl
kotlinx-serialization-cbor = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" }
kotlinx-serialization-properties = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-properties", version.ref = "kotlinx-serialization" }

# kotlinx-io
kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" }
kotlinx-io-bytestring = { module = "org.jetbrains.kotlinx:kotlinx-io-bytestring", version.ref = "kotlinx-io" }

# slf4j
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
Expand Down
3 changes: 3 additions & 0 deletions simbot-api/api/simbot-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions simbot-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ kotlin {
api(project(":simbot-commons:simbot-common-collection"))
api(libs.kotlinx.coroutines.core)
api(libs.kotlinx.serialization.core)
api(libs.kotlinx.io.core)
implementation(libs.kotlinx.serialization.json)
// suspend reversal annotations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
*/
@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)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ public interface ByteArrayResource : Resource {

/**
* 基于 [Base64] 的 [Resource] 序列化器。
*
* 它会将任何 [Resource] 都根据 [Resource.data] 序列化为 Base64 数据,
* 并将任意序列化后的数据反序列化为 [ByteArrayResource]。
*
* 也因此,这会导致:
* - 序列化时会读取数据、产生读取开销。
* - 反序列化后的类型可能与原本的类型不一致。
*/
@ExperimentalEncodingApi
public object ResourceBase64Serializer : KSerializer<Resource> {
Expand Down
Loading