Skip to content

Commit 8a8cfaa

Browse files
authored
Merge pull request #971 from simple-robot/update-ktxio
更新kotlinx-io到0.6.0并迁移所有 *内部* 与IO有关的Resource类型到 SourceResource
2 parents d1a7b66 + 5818b3b commit 8a8cfaa

File tree

8 files changed

+133
-46
lines changed

8 files changed

+133
-46
lines changed

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.7.0"
51-
const val NEXT_VERSION = "4.7.1"
51+
const val NEXT_VERSION = "4.8.0"
5252
const val SNAPSHOT_VERSION = "$VERSION-SNAPSHOT"
5353
const val NEXT_SNAPSHOT_VERSION = "$NEXT_VERSION-SNAPSHOT"
5454

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +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"
6+
kotlinx-io = "0.6.0"
77
spring-boot-v2 = "2.7.18"
88
spring-boot-v3 = "3.2.1"
99
openjdk-jmh = "1.36"

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ import kotlinx.serialization.SerialName
3030
import kotlinx.serialization.Serializable
3131
import love.forte.simbot.common.id.ID
3232
import love.forte.simbot.common.id.IDContainer
33-
import love.forte.simbot.message.At.Companion.equals
34-
import love.forte.simbot.message.At.Companion.hashCode
3533
import love.forte.simbot.message.OfflineImage.Companion.toOfflineImage
3634
import love.forte.simbot.message.Text.Companion.of
3735
import love.forte.simbot.resource.*
@@ -443,6 +441,12 @@ public data class OfflineByteArrayImage(private val data: ByteArray) : OfflineIm
443441
* 远程图片通常是通过事件推送、主动上传等手段得到的、有与某个远程服务器互相对应的唯一标识的图片。
444442
* 这个标识可能是一个ID,或一个访问链接。
445443
*
444+
* 对于一个远程图片的数据获取方式(比如它的二进制数据流),
445+
* 通常取决于其对应的来源(比如bot或事件)而不是 [RemoteImage] 类型自身。
446+
*
447+
* 例如一个仅包含ID的远程图片,需要拥有对应bot的认证信息才可以获取连接,
448+
* 那么就无法仅通过此图片对象本身来获取下载连接。
449+
*
446450
* @see RemoteIDImage
447451
*/
448452
public interface RemoteImage : Image, IDAwareImage {

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

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,48 +66,53 @@ public annotation class ExperimentalIOResourceAPI
6666
* 如果不确定文件系统使用的路径分隔符,或可能在多个使用不同路径分隔符的系统上使用,
6767
* 则考虑使用 [fileResource(base, ...parts)][fileResource]。
6868
*
69+
* 对文件存在性的校验和错误报告可能不会立即报告,而是被推迟到真正读取数据时,
70+
* 参考 [SourceResource.source] 的可能异常。
71+
*
6972
* @param filePath 文件路径,是使用 _路径分隔符_ 的多个片段。
7073
* 其中, _路径分隔符_ 在不同的文件系统中可能是不同的,例如在 Unit 中的 `/`
7174
* 和在 Windows 的 `\`。
7275
*
73-
* @throws kotlinx.io.files.FileNotFoundException see [kotlinx.io.files.FileSystem.source].
74-
* @throws kotlinx.io.IOException see [kotlinx.io.files.FileSystem.source].
76+
* @see SourceResource
7577
*
7678
* @since 4.7.0
7779
*/
7880
@JvmName("valueOfPath")
7981
@ExperimentalIOResourceAPI
80-
@Throws(Exception::class)
81-
public fun fileResource(filePath: String): Resource {
82-
val path = Path(filePath)
83-
return FilePathResource(path)
82+
public fun fileResource(filePath: String): SourceResource {
83+
return Path(filePath).toResource()
8484
}
8585

8686
/**
8787
* 根据文件路径片段集得到一个基于对应文件的 [Resource]。
8888
*
89-
* 文件会先在初始化时构造 [RawSource], 而后在读取 [Resource.data]
90-
* 时使用 [Source]. 因此对文件存在性的校验和错误报告可能不会立即报告,
91-
* 而是被推迟到真正读取数据时。
92-
*
93-
* 文件会在通过 [Resource.data] 读取数据时才会校验存在性。届时如果文件不存在,
94-
* 则会得到 [IllegalStateException] 异常。
95-
* 此异常的 [IllegalStateException.cause] 可能是:
96-
* - [kotlinx.io.files.FileNotFoundException]
97-
* - [kotlinx.io.IOException]
98-
* 如果是这两个类型,则成因参考 [kotlinx.io.files.FileSystem.source]。
89+
* 对文件存在性的校验和错误报告可能不会立即报告,而是被推迟到真正读取数据时,
90+
* 参考 [SourceResource.source] 的可能异常。
9991
*
100-
* @throws kotlinx.io.files.FileNotFoundException see [kotlinx.io.files.FileSystem.source].
101-
* @throws kotlinx.io.IOException see [kotlinx.io.files.FileSystem.source].
92+
* @see SourceResource
10293
*
10394
* @since 4.7.0
10495
*/
10596
@JvmName("valueOfPath")
10697
@ExperimentalIOResourceAPI
107-
@Throws(Exception::class)
108-
public fun fileResource(base: String, vararg parts: String): Resource {
109-
val path = Path(base, *parts)
110-
return FilePathResource(path)
98+
public fun fileResource(base: String, vararg parts: String): SourceResource {
99+
return Path(base, *parts).toResource()
100+
}
101+
102+
/**
103+
* 使用 [Path] 直接构建一个 [FilePathResource]
104+
*
105+
* 对文件存在性的校验和错误报告可能不会立即报告,而是被推迟到真正读取数据时,
106+
* 参考 [SourceResource.source] 的可能异常。
107+
*
108+
* @see SourceResource
109+
*
110+
* @since 4.8.0
111+
*/
112+
@JvmName("valueOf")
113+
@ExperimentalIOResourceAPI
114+
public fun Path.toResource(): SourceResource {
115+
return FilePathResource(this)
111116
}
112117

113118
/**
@@ -121,6 +126,9 @@ public fun fileResource(base: String, vararg parts: String): Resource {
121126
public interface SourceResource : Resource {
122127
/**
123128
* 得到一个用于本次数据读取的 [Source].
129+
*
130+
* 每次都将构建一个新的 [Source], 你需要自行按需[关闭][Source.close]它们。
131+
*
124132
* @throws kotlinx.io.files.FileNotFoundException
125133
* see [kotlinx.io.files.FileSystem.source], [RawSource.buffered]
126134
* @throws kotlinx.io.IOException

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,14 @@ import kotlin.jvm.JvmName
5454
*
5555
* [Resource] 主要由内部实现,不保证对第三方实现的稳定与兼容
5656
*
57+
* @see ByteArrayResource
58+
* @see SourceResource
59+
*
5760
* @author ForteScarlet
5861
*/
5962
public interface Resource {
63+
// TODO become `sealed` for ByteArrayResource and SourceResource.
64+
6065
/**
6166
* 读取此资源的字节数据。
6267
*

simbot-api/src/jvmMain/java/module-info.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@
2121
*
2222
*/
2323

24-
import love.forte.simbot.component.ComponentFactoryConfigurerProvider;
25-
import love.forte.simbot.component.ComponentFactoryProvider;
26-
import love.forte.simbot.plugin.PluginFactoryConfigurerProvider;
27-
import love.forte.simbot.plugin.PluginFactoryProvider;
28-
2924
module simbot.api {
3025
requires kotlin.stdlib;
3126
requires simbot.logger;
@@ -38,6 +33,7 @@
3833
requires kotlinx.coroutines.core;
3934
requires kotlinx.serialization.core;
4035
requires kotlinx.serialization.json;
36+
requires kotlinx.io.core;
4137
requires static kotlinx.coroutines.reactive;
4238
requires static kotlinx.coroutines.reactor;
4339
requires static kotlinx.coroutines.rx2;
@@ -60,9 +56,9 @@
6056
exports love.forte.simbot.plugin;
6157
exports love.forte.simbot.resource;
6258

63-
uses ComponentFactoryProvider;
64-
uses ComponentFactoryConfigurerProvider;
65-
uses PluginFactoryProvider;
66-
uses PluginFactoryConfigurerProvider;
59+
uses love.forte.simbot.component.ComponentFactoryProvider;
60+
uses love.forte.simbot.component.ComponentFactoryConfigurerProvider;
61+
uses love.forte.simbot.plugin.PluginFactoryProvider;
62+
uses love.forte.simbot.plugin.PluginFactoryConfigurerProvider;
6763

6864
}

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

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626

2727
package love.forte.simbot.resource
2828

29+
import kotlinx.io.Source
30+
import kotlinx.io.asSource
31+
import kotlinx.io.buffered
32+
import love.forte.simbot.resource.JvmStringReadableResource.Companion.DEFAULT_CHARSET
2933
import java.io.*
3034
import java.net.MalformedURLException
3135
import java.net.URI
@@ -34,6 +38,7 @@ import java.net.URL
3438
import java.nio.charset.Charset
3539
import java.nio.file.OpenOption
3640
import java.nio.file.Path
41+
import kotlin.Throws
3742
import kotlin.io.path.inputStream
3843
import kotlin.io.path.readBytes
3944
import kotlin.io.path.reader
@@ -43,8 +48,8 @@ import kotlin.io.path.reader
4348
*
4449
* @author forte
4550
*/
51+
@OptIn(ExperimentalIOResourceAPI::class)
4652
public interface InputStreamResource : Resource {
47-
4853
/**
4954
* 读取当前资源的所有字节数据。
5055
* 默认通过 [inputStream] 读取。
@@ -106,7 +111,7 @@ public interface ReaderResource : JvmStringReadableResource {
106111
* 默认使用 [JvmStringReadableResource.DEFAULT_CHARSET] 编码。
107112
*/
108113
@Throws(IOException::class)
109-
public fun reader(): Reader = reader(JvmStringReadableResource.DEFAULT_CHARSET)
114+
public fun reader(): Reader = reader(DEFAULT_CHARSET)
110115

111116
/**
112117
* 获取可用于读取当前资源数据的读取流。
@@ -140,7 +145,7 @@ public interface FileResource : InputStreamResource, ReaderResource {
140145
* @throws FileNotFoundException 如果文件不存在
141146
*/
142147
@Throws(FileNotFoundException::class)
143-
override fun reader(): Reader = reader(JvmStringReadableResource.DEFAULT_CHARSET)
148+
override fun reader(): Reader = reader(DEFAULT_CHARSET)
144149

145150
/**
146151
* 从与此资源关联的 [File] 创建新的 [Reader]
@@ -162,7 +167,7 @@ public interface FileResource : InputStreamResource, ReaderResource {
162167
* @throws IOException 如果文件无法读取
163168
*/
164169
@Throws(IOException::class)
165-
override fun string(): String = string(JvmStringReadableResource.DEFAULT_CHARSET)
170+
override fun string(): String = string(DEFAULT_CHARSET)
166171

167172
/**
168173
* 将与此资源关联的 [File] 读取为字符串
@@ -181,15 +186,22 @@ public interface FileResource : InputStreamResource, ReaderResource {
181186
*/
182187
@JvmName("valueOf")
183188
@JvmOverloads
184-
public fun File.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET): FileResource =
189+
public fun File.toResource(charset: Charset = DEFAULT_CHARSET): FileResource =
185190
FileResourceImpl(this, charset)
186191

187-
private data class FileResourceImpl(override val file: File, private val charset: Charset) : FileResource {
192+
@OptIn(ExperimentalIOResourceAPI::class)
193+
private data class FileResourceImpl(override val file: File, private val charset: Charset) :
194+
FileResource, SourceResource {
188195
override fun string(): String = string(charset)
189196
override fun reader(): Reader = reader(charset)
190197

191198
override fun string(charset: Charset): String = file.readText(charset)
192199
override fun reader(charset: Charset): Reader = file.reader(charset)
200+
201+
override fun data(): ByteArray = file.readBytes()
202+
203+
override fun source(): Source = inputStream().asSource().buffered()
204+
193205
override fun equals(other: Any?): Boolean {
194206
if (this === other) return true
195207
if (other !is FileResourceImpl) return false
@@ -261,16 +273,17 @@ public interface PathResource : InputStreamResource, ReaderResource {
261273
@JvmName("valueOf")
262274
@JvmOverloads
263275
public fun Path.toResource(
264-
charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET,
276+
charset: Charset = DEFAULT_CHARSET,
265277
vararg options: OpenOption
266278
): PathResource =
267279
PathResourceImpl(this, charset, options)
268280

281+
@OptIn(ExperimentalIOResourceAPI::class)
269282
private data class PathResourceImpl(
270283
override val path: Path,
271284
private val charset: Charset,
272285
private val openOptions: Array<out OpenOption>
273-
) : PathResource {
286+
) : PathResource, SourceResource {
274287
override fun inputStream(): InputStream = path.inputStream(options = openOptions)
275288

276289
override fun reader(): Reader = reader(charset)
@@ -279,6 +292,10 @@ private data class PathResourceImpl(
279292
override fun reader(charset: Charset): Reader = path.reader(charset, options = openOptions)
280293
override fun string(charset: Charset): String = reader(charset).use(Reader::readText)
281294

295+
override fun data(): ByteArray = path.readBytes()
296+
297+
override fun source(): Source = inputStream().asSource().buffered()
298+
282299
override fun equals(other: Any?): Boolean {
283300
if (this === other) return true
284301
if (other !is PathResourceImpl) return false
@@ -369,7 +386,7 @@ public interface URIResource : InputStreamResource, JvmStringReadableResource {
369386
@kotlin.jvm.Throws(URISyntaxException::class)
370387
@JvmName("valueOf")
371388
@JvmOverloads
372-
public fun URL.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET): URIResource =
389+
public fun URL.toResource(charset: Charset = DEFAULT_CHARSET): URIResource =
373390
URIResourceImpl(toURI(), charset, this)
374391

375392
/**
@@ -380,10 +397,13 @@ public fun URL.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_C
380397
*/
381398
@JvmName("valueOf")
382399
@JvmOverloads
383-
public fun URI.toResource(charset: Charset = JvmStringReadableResource.DEFAULT_CHARSET): URIResource =
400+
public fun URI.toResource(charset: Charset = DEFAULT_CHARSET): URIResource =
384401
URIResourceImpl(this, charset, null)
385402

386-
private class URIResourceImpl(override val uri: URI, val charset: Charset, private var url: URL? = null) : URIResource {
403+
@OptIn(ExperimentalIOResourceAPI::class)
404+
private class URIResourceImpl(override val uri: URI, val charset: Charset, private var url: URL? = null) :
405+
URIResource,
406+
SourceResource {
387407
private val urlValue: URL
388408
get() = url ?: run {
389409
uri.toURL().also { url = it }
@@ -393,5 +413,9 @@ private class URIResourceImpl(override val uri: URI, val charset: Charset, priva
393413
override fun string(): String = string(charset)
394414
override fun string(charset: Charset): String = urlValue.readText(charset)
395415

416+
override fun data(): ByteArray = inputStream().use { stream -> stream.readAllBytes() }
417+
418+
override fun source(): Source = inputStream().asSource().buffered()
419+
396420
override fun toString(): String = "URIResource(uri=$uri, charset=$charset)"
397421
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
24+
package resource
25+
26+
import love.forte.simbot.resource.ExperimentalIOResourceAPI
27+
import love.forte.simbot.resource.SourceResource
28+
import love.forte.simbot.resource.toResource
29+
import java.io.File
30+
import java.net.URI
31+
import kotlin.io.path.Path
32+
import kotlin.test.Test
33+
import kotlin.test.assertIs
34+
35+
36+
/**
37+
*
38+
* @author ForteScarlet
39+
*/
40+
@OptIn(ExperimentalIOResourceAPI::class)
41+
class SourceResourceTypeTests {
42+
43+
@Test
44+
fun testSourceResourceInheritance() {
45+
assertIs<SourceResource>(File(".").toResource())
46+
assertIs<SourceResource>(Path(".").toResource())
47+
assertIs<SourceResource>(URI.create("https://localhost:8080").toResource())
48+
}
49+
50+
}

0 commit comments

Comments
 (0)