Skip to content

Commit 66a8c84

Browse files
authored
Merge pull request #955 from simple-robot/support-ktx-io
增加部分基于文件系统的 Resource、Image API 的实验性支持
2 parents d2538b8 + dc96be2 commit 66a8c84

File tree

8 files changed

+185
-4
lines changed

8 files changed

+185
-4
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ apiValidation {
159159
listOf(
160160
"love.forte.simbot.annotations.ExperimentalSimbotAPI",
161161
"love.forte.simbot.annotations.InternalSimbotAPI",
162+
"love.forte.simbot.resource.ExperimentalIOResourceAPI",
162163
),
163164
)
164165

buildSrc/src/main/kotlin/P.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ fun isSnapshot(): Boolean = _isSnapshot
4848
sealed class P(override val group: String) : ProjectDetail() {
4949
companion object {
5050
const val VERSION = "4.6.1"
51-
const val NEXT_VERSION = "4.6.2"
51+
const val NEXT_VERSION = "4.7.0"
5252
const val SNAPSHOT_VERSION = "$VERSION-SNAPSHOT"
5353
const val NEXT_SNAPSHOT_VERSION = "$NEXT_VERSION-SNAPSHOT"
5454

gradle/libs.versions.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ kotlin = "2.0.20"
33
dokka = "1.9.20"
44
kotlinx-coroutines = "1.9.0"
55
kotlinx-serialization = "1.7.3"
6+
kotlinx-io = "0.5.4"
67
spring-boot-v2 = "2.7.18"
78
spring-boot-v3 = "3.2.1"
89
openjdk-jmh = "1.36"
@@ -58,6 +59,9 @@ kotlinx-serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotl
5859
kotlinx-serialization-cbor = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" }
5960
kotlinx-serialization-properties = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-properties", version.ref = "kotlinx-serialization" }
6061

62+
# kotlinx-io
63+
kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" }
64+
kotlinx-io-bytestring = { module = "org.jetbrains.kotlinx:kotlinx-io-bytestring", version.ref = "kotlinx-io" }
6165

6266
# slf4j
6367
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }

simbot-api/api/simbot-api.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,6 +2532,9 @@ public abstract interface class love/forte/simbot/resource/ByteArrayResource : l
25322532
public abstract fun data ()[B
25332533
}
25342534

2535+
public abstract interface annotation class love/forte/simbot/resource/ExperimentalIOResourceAPI : java/lang/annotation/Annotation {
2536+
}
2537+
25352538
public abstract interface class love/forte/simbot/resource/FileResource : love/forte/simbot/resource/InputStreamResource, love/forte/simbot/resource/ReaderResource {
25362539
public fun data ()[B
25372540
public abstract fun getFile ()Ljava/io/File;

simbot-api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ kotlin {
8383
api(project(":simbot-commons:simbot-common-collection"))
8484
api(libs.kotlinx.coroutines.core)
8585
api(libs.kotlinx.serialization.core)
86+
api(libs.kotlinx.io.core)
8687
implementation(libs.kotlinx.serialization.json)
8788
// suspend reversal annotations
8889

simbot-api/src/commonMain/kotlin/love/forte/simbot/message/StandardMessages.kt

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ import love.forte.simbot.message.At.Companion.equals
3434
import love.forte.simbot.message.At.Companion.hashCode
3535
import love.forte.simbot.message.OfflineImage.Companion.toOfflineImage
3636
import love.forte.simbot.message.Text.Companion.of
37-
import love.forte.simbot.resource.ByteArrayResource
38-
import love.forte.simbot.resource.Resource
39-
import love.forte.simbot.resource.ResourceBase64Serializer
37+
import love.forte.simbot.resource.*
4038
import kotlin.io.encoding.ExperimentalEncodingApi
4139
import kotlin.js.JsName
4240
import kotlin.jvm.*
@@ -340,6 +338,37 @@ public interface OfflineImage : Image {
340338
else -> toOfflineResourceImage()
341339
}
342340

341+
/**
342+
* 使用文件路径读取文件 `Path` 作为 [OfflineImage]。
343+
* 相当于通过 [fileResource] 产物使用 [toOfflineImage]。
344+
*
345+
* 更多说明和注意事项参考 [fileResource]。
346+
*
347+
* @see fileResource
348+
*
349+
* @since 4.7.0
350+
*/
351+
@JvmStatic
352+
@JvmName("ofFilePath")
353+
@ExperimentalIOResourceAPI
354+
public fun fileOfflineImage(filePath: String): OfflineImage =
355+
fileResource(filePath).toOfflineResourceImage()
356+
357+
/**
358+
* 使用文件路径读取文件 `Path` 作为 [OfflineImage]。
359+
* 相当于通过 [fileResource] 产物使用 [toOfflineImage]。
360+
*
361+
* 更多说明和注意事项参考 [fileResource]。
362+
*
363+
* @see fileResource
364+
*
365+
* @since 4.7.0
366+
*/
367+
@JvmStatic
368+
@ExperimentalIOResourceAPI
369+
@JvmName("ofFilePath")
370+
public fun fileOfflineImage(base: String, vararg parts: String): OfflineImage =
371+
fileResource(base, *parts).toOfflineResourceImage()
343372
}
344373
}
345374

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2024. ForteScarlet.
3+
*
4+
* Project https://github.com/simple-robot/simpler-robot
5+
6+
*
7+
* This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* Lesser GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the Lesser GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
@file:JvmName("Resources")
24+
@file:JvmMultifileClass
25+
26+
package love.forte.simbot.resource
27+
28+
import kotlinx.io.*
29+
import kotlinx.io.files.FileNotFoundException
30+
import kotlinx.io.files.Path
31+
import kotlinx.io.files.SystemFileSystem
32+
import kotlin.annotation.AnnotationTarget.*
33+
import kotlin.jvm.JvmMultifileClass
34+
import kotlin.jvm.JvmName
35+
36+
/**
37+
* 一些尚处于实验阶段的、基于IO(主要指文件系统相关)的 [Resource] 相关API。
38+
*
39+
* 可能会在未来发生变更、或被删除,且不保证兼容性与稳定性。
40+
*
41+
* @since 4.7.0
42+
*/
43+
@RequiresOptIn("Experimental IO Resource API")
44+
@Retention(AnnotationRetention.BINARY)
45+
@MustBeDocumented
46+
@Target(
47+
CLASS,
48+
ANNOTATION_CLASS,
49+
PROPERTY,
50+
FIELD,
51+
LOCAL_VARIABLE,
52+
VALUE_PARAMETER,
53+
CONSTRUCTOR,
54+
FUNCTION,
55+
PROPERTY_GETTER,
56+
PROPERTY_SETTER,
57+
TYPEALIAS
58+
)
59+
public annotation class ExperimentalIOResourceAPI
60+
61+
/**
62+
* 根据完整的文件路径 [filePath] 得到一个基于对应文件的 [Resource]。
63+
*
64+
* 文件会在通过 [Resource.data] 读取数据时才会校验存在性。届时如果文件不存在,
65+
* 则会得到 [IllegalStateException] 异常。
66+
*
67+
* 如果不确定文件系统使用的路径分隔符,或可能在多个使用不同路径分隔符的系统上使用,
68+
* 则考虑使用 [fileResource(base, ...parts)][fileResource]。
69+
*
70+
* @param filePath 文件路径,是使用 _路径分隔符_ 的多个片段。
71+
* 其中, _路径分隔符_ 在不同的文件系统中可能是不同的,例如在 Unit 中的 `/`
72+
* 和在 Windows 的 `\`。
73+
*
74+
* @since 4.7.0
75+
*/
76+
@JvmName("valueOfPath")
77+
@ExperimentalIOResourceAPI
78+
public fun fileResource(filePath: String): Resource {
79+
val path = Path(filePath)
80+
return FilePathResource(path)
81+
}
82+
83+
/**
84+
* 根据文件路径片段集得到一个基于对应文件的 [Resource]。
85+
*
86+
* 文件会在通过 [Resource.data] 读取数据时才会校验存在性。届时如果文件不存在,
87+
* 则会得到 [IllegalStateException] 异常。
88+
* 此异常的 [IllegalStateException.cause] 可能是:
89+
* - [kotlinx.io.files.FileNotFoundException]
90+
* - [kotlinx.io.IOException]
91+
* 如果是这两个类型,则成因参考 [kotlinx.io.files.FileSystem.source]。
92+
*
93+
* @since 4.7.0
94+
*/
95+
@JvmName("valueOfPath")
96+
@ExperimentalIOResourceAPI
97+
public fun fileResource(base: String, vararg parts: String): Resource {
98+
val path = Path(base, *parts)
99+
return FilePathResource(path)
100+
}
101+
102+
/**
103+
* 一个可以得到 [kotlinx.io.RawSource] 的 [Resource]。
104+
*
105+
* @since 4.7.0
106+
*/
107+
@ExperimentalIOResourceAPI
108+
public interface RawSourceResource : Resource {
109+
public fun source(): RawSource
110+
111+
override fun data(): ByteArray {
112+
return source().buffered().use { it.readByteArray() }
113+
}
114+
}
115+
116+
/**
117+
* 一个可以得到 [kotlinx.io.Source] 的 [Resource]。
118+
*
119+
* @since 4.7.0
120+
*/
121+
@ExperimentalIOResourceAPI
122+
public interface SourceResource : RawSourceResource {
123+
override fun source(): Source
124+
}
125+
126+
@ExperimentalIOResourceAPI
127+
private data class FilePathResource(val path: Path) : RawSourceResource {
128+
override fun source(): RawSource = try {
129+
SystemFileSystem.source(path)
130+
} catch (fnf: FileNotFoundException) {
131+
throw IllegalStateException(fnf.message, fnf)
132+
} catch (io: IOException) {
133+
throw IllegalStateException(io.message, io)
134+
}
135+
}
136+

simbot-api/src/commonMain/kotlin/love/forte/simbot/resource/Resource.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ public interface ByteArrayResource : Resource {
8787

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

0 commit comments

Comments
 (0)