Skip to content
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

Release: v4.8.0 #972

Merged
merged 15 commits into from
Nov 22, 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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import io.gitlab.arturbosch.detekt.Detekt
plugins {
idea
id("simbot.dokka-multi-module")
id("com.github.gmazzo.buildconfig") version "5.5.0" apply false
id("com.github.gmazzo.buildconfig") version "5.5.1" apply false
alias(libs.plugins.detekt)
id("simbot.nexus-publish")
id("simbot.changelog-generator")
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/P.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ fun isSnapshot(): Boolean = _isSnapshot
@Suppress("MemberVisibilityCanBePrivate")
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 VERSION = "4.8.0"
const val NEXT_VERSION = "4.8.1"
const val SNAPSHOT_VERSION = "$VERSION-SNAPSHOT"
const val NEXT_SNAPSHOT_VERSION = "$NEXT_VERSION-SNAPSHOT"

Expand Down
8 changes: 4 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -13,12 +13,12 @@ slf4j = "2.0.16"
# https://github.com/google/ksp
ksp = "2.0.20-1.0.25"
# https://square.github.io/kotlinpoet/
kotlinPoet = "1.18.1"
kotlinPoet = "2.0.0"
# https://github.com/Kotlin/kotlinx-benchmark
#kotlinxBenchmark = "0.4.11"
reactor = "3.6.11"
reactor = "3.7.0"
# simbots
suspendTransform = "2.0.20-0.9.3"
suspendTransform = "2.0.20-0.9.4"
gradleCommon = "0.6.0"
# tests
mockk = "1.13.13"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

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

/**
* 读取此资源的字节数据。
*
Expand Down
14 changes: 5 additions & 9 deletions simbot-api/src/jvmMain/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -60,9 +56,9 @@
exports love.forte.simbot.plugin;
exports love.forte.simbot.resource;

uses ComponentFactoryProvider;
uses ComponentFactoryConfigurerProvider;
uses PluginFactoryProvider;
uses PluginFactoryConfigurerProvider;
uses love.forte.simbot.component.ComponentFactoryProvider;
uses love.forte.simbot.component.ComponentFactoryConfigurerProvider;
uses love.forte.simbot.plugin.PluginFactoryProvider;
uses love.forte.simbot.plugin.PluginFactoryConfigurerProvider;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -43,8 +48,8 @@ import kotlin.io.path.reader
*
* @author forte
*/
@OptIn(ExperimentalIOResourceAPI::class)
public interface InputStreamResource : Resource {

/**
* 读取当前资源的所有字节数据。
* 默认通过 [inputStream] 读取。
Expand Down Expand Up @@ -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)

/**
* 获取可用于读取当前资源数据的读取流。
Expand Down Expand Up @@ -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]
Expand All @@ -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] 读取为字符串
Expand All @@ -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
Expand Down Expand Up @@ -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<out OpenOption>
) : PathResource {
) : PathResource, SourceResource {
override fun inputStream(): InputStream = path.inputStream(options = openOptions)

override fun reader(): Reader = reader(charset)
Expand All @@ -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
Expand Down Expand Up @@ -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)

/**
Expand All @@ -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 }
Expand All @@ -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)"
}
50 changes: 50 additions & 0 deletions simbot-api/src/jvmTest/kotlin/resource/SourceResourceTypeTests.kt
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
*/

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<SourceResource>(File(".").toResource())
assertIs<SourceResource>(Path(".").toResource())
assertIs<SourceResource>(URI.create("https://localhost:8080").toResource())
}

}
Loading
Loading