Skip to content

Commit

Permalink
fix(api): 修复 ResourceImage 序列化
Browse files Browse the repository at this point in the history
fix #500
  • Loading branch information
ForteScarlet committed Nov 22, 2022
1 parent b4a8155 commit 35f292c
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import love.forte.plugin.suspendtrans.annotation.JvmAsync
import love.forte.plugin.suspendtrans.annotation.JvmBlocking
import love.forte.simbot.ExperimentalSimbotApi
import love.forte.simbot.ID
import love.forte.simbot.definition.IDContainer
import love.forte.simbot.definition.ResourceContainer
Expand Down Expand Up @@ -173,15 +174,15 @@ public data class At @JvmOverloads constructor(

/**
* 这个at在原始数据中或者原始事件中的样子。默认情况下,是字符串 '@[target]'。
* 此值将不会参与 [equals] 于 [hashCode] 的计算。
* 此值不会参与 [equals] 于 [hashCode] 的计算。
*/
public val originContent: String = "@$target",

) : BaseStandardMessage<At>() {
override val key: Message.Key<At> get() = Key


@Deprecated("Please use type", ReplaceWith("type"), level = DeprecationLevel.ERROR)
@Deprecated("Please use 'type'", ReplaceWith("type"), level = DeprecationLevel.ERROR)
public val atType: String get() = type

override fun equals(other: Any?): Boolean {
Expand Down Expand Up @@ -282,11 +283,26 @@ public interface Image<E : Image<E>> : StandardMessage<E>, IDContainer, Resource
* [发送][love.forte.simbot.action.SendSupport.send] 的时候 [ResourceImage]
* 中的资源才会被进行验证,而在那之前 [ResourceImage] 仅为一种资源携带体,无法验证资源的有效性。
*
* ## 序列化
*
* [ResourceImage] 支持序列化,但是**不建议**使用其序列化,且对其进行序列化存在一定条件。
* [ResourceImage] 内部持有 [Resource], 当且仅当 [Resource] 类型为
* [StandardResource][love.forte.simbot.resources.StandardResource] 时才能序列化,
* 否则将会引发 [SerializationException][kotlinx.serialization.SerializationException]。
*
* 并且对 [Resource] 的序列化的不可靠的,具体描述参考 [Resource.AsStandardSerializer]。对于一个组件,
* 如果希望提供可靠的可序列化 [Image], 则考虑进行额外实现而不是直接使用 [ResourceImage]。
*
*/
@SerialName("m.std.img.resource")
@Serializable
public data class ResourceImage(override val id: ID, @SerialName("resource") private val _resource: Resource) :
Image<ResourceImage> {
public data class ResourceImage @OptIn(ExperimentalSimbotApi::class) constructor(
@Serializable(ID.AsCharSequenceIDSerializer::class)
override val id: ID,
@SerialName("resource")
@Serializable(Resource.AsStandardSerializer::class)
private val _resource: Resource,
) : Image<ResourceImage> {

@JvmSynthetic
override suspend fun resource(): Resource = _resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@

package love.forte.simbot.resources

import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import love.forte.simbot.ExperimentalSimbotApi
import love.forte.simbot.resources.Resource.Companion.toResource
import java.io.*
import java.net.URL
Expand All @@ -46,18 +45,18 @@ import kotlin.io.path.*
* @author ForteScarlet
*/
public interface Resource : Closeable {

/**
* 得到资源名称。
*/
public val name: String

/**
* 得到当前资源中所对应的数据流。
*/
@Throws(IOException::class)
public fun openStream(): InputStream

/**
* [StandardResource] 在使用的过程中可能会产生一些需要手动进行 [close] 的产物,
* 因此在不使用 [StandardResource] 的时候,使用 [close] 对其进行关闭。
Expand All @@ -68,42 +67,42 @@ public interface Resource : Closeable {
*/
@Throws(IOException::class)
override fun close()


public companion object {

/**
* 使用 [URL] 作为一个 [StandardResource].
*/
@JvmStatic
@JvmOverloads
@JvmName("of")
public fun URL.toResource(name: String = toString()): URLResource = URLResource(this, name)

/**
* 使用 [File] 作为一个 [StandardResource].
*/
@JvmStatic
@JvmOverloads
@JvmName("of")
public fun File.toResource(name: String = toString()): FileResource = FileResource(this, name)

/**
* 使用 [Path] 作为一个 [StandardResource].
*/
@JvmStatic
@JvmOverloads
@JvmName("of")
public fun Path.toResource(name: String = toString()): PathResource = PathResource(this, name)

/**
* 使用字节数组作为一个 [StandardResource].
*/
@JvmStatic
@JvmName("of")
public fun ByteArray.toResource(name: String): ByteArrayResource =
ByteArrayResource(name, this)

/**
* 拷贝提供的 [inputStream] 并作为 [StandardResource] 返回。
* 不会自动关闭 [inputStream], 需要由调用者处理。
Expand All @@ -121,23 +120,43 @@ public interface Resource : Closeable {
)
temp.outputStream(StandardOpenOption.CREATE).use(this::copyTo)
temp.toFile().deleteOnExit()

return PathResource(temp, name ?: temp.toString()) { temp.deleteIfExists() }
}
}

/**
* 尝试将 [Resource] 视作 [StandardResource] 的序列化器。
* 如果实际类型不是 [StandardResource], 则会引发异常。
*/
@ExperimentalSimbotApi
public object AsStandardSerializer : KSerializer<Resource> {
override fun deserialize(decoder: Decoder): Resource {
return StandardResource.serializer().deserialize(decoder)
}

override val descriptor: SerialDescriptor = StandardResource.serializer().descriptor

override fun serialize(encoder: Encoder, value: Resource) {
if (value !is StandardResource) throw SerializationException("Type of value must be StandardResource, but ${value::class}")

StandardResource.serializer().serialize(encoder, value)
}
}
}


/**
* 提供一个可以开启输入流的 [Resource] 实例,
*
* 通过 [openStream] 得到的数据流不会被管理,应当由使用者自行管理、关闭。
*
* [Resource] 的标准实现类型.
*
* [StandardResource] 支持序列化,但是序列化结果并不可靠。
* [StandardResource] 的实现类型大多数依靠 [File]、[Path] 等系统资源,
* 而这些系统资源无法保障其可用性和有效性(有些实现中可能还会用到临时资源),
* 因此序列化再反序列化的结果不一定仍然可用(例如对应的系统资源已被删除)。
*
* @see Resource.toResource
*/
@SerialName("simbot.resource.streamable")
@SerialName("simbot.resource.standard")
@Serializable
public sealed class StandardResource : Resource, Closeable

Expand All @@ -150,21 +169,21 @@ public sealed class StandardResource : Resource, Closeable
public class URLResource(
@Serializable(URLSerializer::class)
public val url: URL,
override val name: String = url.toString()
override val name: String = url.toString(),
) : StandardResource() {

/**
* @see URL.openStream
*/
@Throws(IOException::class)
override fun openStream(): InputStream {
return url.openStream()
}

override fun toString(): String {
return "Resource(url=$url, name=$name)"
}

override fun close() {
}
}
Expand All @@ -174,9 +193,9 @@ internal object URLSerializer : KSerializer<URL> {
val url = decoder.decodeString()
return URL(url)
}

override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.net.URL", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: URL) {
encoder.encodeString(value.toString())
}
Expand All @@ -192,24 +211,24 @@ public class FileResource(
public val file: File,
override val name: String = file.toString(),
@Transient
private val doClose: () -> Unit = {}
private val doClose: () -> Unit = {},
) : StandardResource() {

/**
* @see FileInputStream
*/
@Throws(FileNotFoundException::class)
override fun openStream(): FileInputStream {
return FileInputStream(file)
}

override fun toString(): String {
return "Resource(file=$file, name=$name)"
}

@JvmOverloads
public fun randomAccessFile(mode: String = "r"): RandomAccessFile = RandomAccessFile(file, mode)

override fun close() {
doClose()
}
Expand All @@ -220,13 +239,13 @@ internal object FileSerializer : KSerializer<File> {
val pathname = decoder.decodeString()
return File(pathname)
}

override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("file", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: File) {
encoder.encodeString(value.path)
}

}


Expand All @@ -242,30 +261,30 @@ public class PathResource(
public val path: Path,
override val name: String = path.toString(),
@Transient
private val doClose: () -> Unit = {}
private val doClose: () -> Unit = {},
) : StandardResource() {

@Throws(IOException::class)
override fun openStream(): InputStream = path.inputStream(StandardOpenOption.READ)

override fun toString(): String {
return "Resource(path=$path, name=$name)"
}

@Suppress("MemberVisibilityCanBePrivate")
@Throws(IOException::class)
public fun openStream(vararg options: OpenOption): InputStream = path.inputStream(*options)

override fun close() {
doClose()
}
}

internal object PathSerializer : KSerializer<Path> {
override fun deserialize(decoder: Decoder): Path = Path(decoder.decodeString())

override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.nio.Path", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: Path) {
encoder.encodeString(value.pathString)
}
Expand All @@ -282,7 +301,7 @@ public class ByteArrayResource(override val name: String, private val byteArray:
* 得到当前资源中字节数组的**副本**。
*/
public val bytes: ByteArray get() = byteArray.copyOf()

/**
* 将当前资源中字节数组拷贝到目标数组中。
*/
Expand All @@ -291,33 +310,33 @@ public class ByteArrayResource(override val name: String, private val byteArray:
destination: ByteArray,
destinationOffset: Int = 0,
startIndex: Int = 0,
endIndex: Int = byteArray.size
endIndex: Int = byteArray.size,
) {
byteArray.copyInto(destination, destinationOffset, startIndex, endIndex)
}

/**
* 字节数组的大小。
*/
@JvmField
public val size: Int = byteArray.size

/**
* 获取指定索引位的字节。
*/
public operator fun get(index: Int): Byte = byteArray[index]

/**
* 得到字节数组输入流。
*/
override fun openStream(): ByteArrayInputStream {
return byteArray.inputStream()
}

override fun toString(): String {
return "Resource(size of bytes=${size}, name=$name)"
}

override fun close() {
// Nothing
}
Expand Down

0 comments on commit 35f292c

Please sign in to comment.