Voice serialization (#1368)

* Voice serialization

* Implement `hashCode` and `equals` for `Voice`

* Update test

* Update test

* Add note of `PttMessage.pttInternalInstance`

* Fix logic of `Voice.equals`
This commit is contained in:
Karlatemp 2021-07-02 12:10:20 +08:00 committed by GitHub
parent 1ad00134fa
commit a4b62b0909
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 172 additions and 25 deletions

View File

@ -5064,31 +5064,29 @@ public final class net/mamoe/mirai/message/data/VipFace$Kind$Companion {
public final class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage {
public static final field Key Lnet/mamoe/mirai/message/data/Voice$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILjava/lang/String;[BJILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;[BJILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun contentToString ()Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public final fun getCodec ()I
public fun getFileName ()Ljava/lang/String;
public fun getFileSize ()J
public fun getMd5 ()[B
public final fun getUrl ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/Voice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$$serializer;
public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$Serializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Voice;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Voice;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder {

View File

@ -5064,31 +5064,29 @@ public final class net/mamoe/mirai/message/data/VipFace$Kind$Companion {
public final class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage {
public static final field Key Lnet/mamoe/mirai/message/data/Voice$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILjava/lang/String;[BJILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;[BJILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun contentToString ()Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public final fun getCodec ()I
public fun getFileName ()Ljava/lang/String;
public fun getFileSize ()J
public fun getMd5 ()[B
public final fun getUrl ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/Voice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$$serializer;
public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$Serializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Voice;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Voice;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder {

View File

@ -15,8 +15,10 @@ import kotlinx.coroutines.Job
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.WeakRef
import kotlin.annotation.AnnotationTarget.*
@ -293,4 +295,17 @@ public interface LowLevelApiAccessor {
seconds: Int,
)
/**
* 序列化 [Voice.pttInternalInstance]
*/
@LowLevelApi
@MiraiInternalApi // For Voice serialize
public fun serializePttElem(ptt: Any?): String
/**
* 反序列化 [Voice.pttInternalInstance]
*/
@LowLevelApi
@MiraiInternalApi // For Voice serialize
public fun deserializePttElem(ptt: String): Any?
}

View File

@ -9,15 +9,15 @@
package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.internal.message.map
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsVoice
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
/**
@ -41,9 +41,30 @@ public abstract class PttMessage : MessageContent {
@MiraiExperimentalApi
public abstract val fileSize: Long
/*
* **internal impl note**
* 用于中转 ImMsgBody.Ptt, 在接受到其他用户发送的语音时能按照原样发回,
* 并且便于未来修改 ( api 修改最小化)
*/
@MiraiInternalApi
@Transient
public var pttInternalInstance: Any? = null
set(value) {
field = value
_pttInternalInstanceSerializeCache = null
}
@MiraiInternalApi
protected val pttInternalInstanceSerializeCache: String
get() {
_pttInternalInstanceSerializeCache?.let { return it }
return Mirai.serializePttElem(pttInternalInstance).also {
_pttInternalInstanceSerializeCache = it
}
}
@Transient
private var _pttInternalInstanceSerializeCache: String? = null
}
/**
@ -58,7 +79,8 @@ public abstract class PttMessage : MessageContent {
*
* [Voice] 实例可以通过序列化方式保存. 下次可以用它发送因而不需要上传. 但可能由于未来服务器更新, 这项功能就不稳定. 因此建议总是上传音频文件而不要保存 [Voice].
*/
@Serializable // experimental
@Suppress("DuplicatedCode")
@Serializable(Voice.Serializer::class) // experimental
@SerialName(Voice.SERIAL_NAME)
public class Voice @MiraiInternalApi constructor(
@MiraiExperimentalApi public override val fileName: String,
@ -92,4 +114,70 @@ public class Voice @MiraiInternalApi constructor(
public override fun toString(): String = _stringValue!!
public override fun contentToString(): String = "[语音消息]"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Voice) return false
if (this.pttInternalInstance != null && other.pttInternalInstance != null) {
if (this.pttInternalInstance == other.pttInternalInstance)
return true
// strict
return this.pttInternalInstanceSerializeCache == other.pttInternalInstanceSerializeCache
}
if (fileName != other.fileName) return false
if (!md5.contentEquals(other.md5)) return false
if (fileSize != other.fileSize) return false
if (codec != other.codec) return false
if (_url != other._url) return false
return true
}
override fun hashCode(): Int {
if (pttInternalInstance != null)
return pttInternalInstanceSerializeCache.hashCode()
var result = fileName.hashCode()
result = 12 * result + md5.contentHashCode()
result = 54 * result + fileSize.hashCode()
result = 33 * result + codec
result = 15 * result + _url.hashCode()
return result
}
public object Serializer : KSerializer<Voice> by VoiceS.serializer().map(
resultantDescriptor = VoiceS.serializer().descriptor.copy(SERIAL_NAME),
deserialize = {
Voice(
fileName = it.fileName,
md5 = it.md5,
fileSize = it.fileSize,
codec = it.codec,
_url = it._url,
).also { v -> v.pttInternalInstance = Mirai.deserializePttElem(it.ptt) }
},
serialize = {
VoiceS(
fileName = it.fileName,
md5 = it.md5,
fileSize = it.fileSize,
_url = it._url,
codec = it.codec,
ptt = Mirai.serializePttElem(it.pttInternalInstance)
)
}
) {
@Serializable
@SerialName(SERIAL_NAME)
private class VoiceS(
val fileName: String,
val md5: ByteArray,
val fileSize: Long,
val codec: Int,
val _url: String,
val ptt: String = "",
)
}
}

View File

@ -1182,4 +1182,14 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
}
}
}
override fun serializePttElem(ptt: Any?): String {
if (ptt !is ImMsgBody.Ptt) return ""
return ptt.toByteArray(ImMsgBody.Ptt.serializer()).toUHexString()
}
override fun deserializePttElem(ptt: String): Any? {
if (ptt.isBlank()) return null
return ptt.hexToBytes().loadAs(ImMsgBody.Ptt.serializer())
}
}

View File

@ -217,4 +217,42 @@ internal class MessageSerializationTest {
actual = source
)
}
@Serializable
data class V(
val msg: Voice
)
@Test
fun `test Voice serialization`() {
val v = V(Voice("4517", byteArrayOf(14), 50, 3, "https://github.com"))
println(v.serialize(V.serializer()))
assertEquals(
v.serialize(V.serializer()),
v.serialize(V.serializer())
.deserialize(V.serializer())
.serialize(V.serializer())
)
assertEquals(
v,
v.serialize(V.serializer()).deserialize(V.serializer())
)
v.msg.pttInternalInstance = ImMsgBody.Ptt(
srcUin = 1234567890,
fileMd5 = byteArrayOf(14, 81, 37, 14),
boolValid = true,
format = 90,
)
println(v.serialize(V.serializer()))
assertEquals(
v.serialize(V.serializer()),
v.serialize(V.serializer())
.deserialize(V.serializer())
.serialize(V.serializer())
)
assertEquals(
v,
v.serialize(V.serializer()).deserialize(V.serializer())
)
}
}