mirror of
https://github.com/mamoe/mirai.git
synced 2024-12-28 01:20:21 +08:00
[console] 支持使用 json 保存与读取 PluginData
与 PluginConfig
(#2498)
* Supports PluginData store with json format. * Reformat code.
This commit is contained in:
parent
baf9ee4bf7
commit
cb603adbfc
@ -1108,6 +1108,7 @@ public abstract interface class net/mamoe/mirai/console/data/PluginConfig : net/
|
||||
|
||||
public abstract interface class net/mamoe/mirai/console/data/PluginData {
|
||||
public abstract fun getSaveName ()Ljava/lang/String;
|
||||
public fun getSaveType ()Lnet/mamoe/mirai/console/data/PluginData$SaveType;
|
||||
public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
|
||||
public abstract fun getUpdaterSerializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
@ -122,6 +122,21 @@ public interface PluginData {
|
||||
@ConsoleExperimentalApi
|
||||
public val saveName: String
|
||||
|
||||
/**
|
||||
* [PluginData] 序列化时使用的格式的枚举.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public enum class SaveType(@ConsoleExperimentalApi public val extension: String) {
|
||||
YAML("yml"), JSON("json")
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定这个 [PluginData] 序列化时使用的格式, 默认为 YAML.
|
||||
* 具体实现格式由 [PluginDataStorage] 决定.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val saveType: SaveType get() = SaveType.YAML
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public val updaterSerializer: KSerializer<Unit>
|
||||
|
||||
|
@ -40,9 +40,18 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
val file = getPluginDataFile(holder, instance)
|
||||
val text = file.readText().removePrefix("\uFEFF")
|
||||
if (text.isNotBlank()) {
|
||||
val yaml = createYaml(instance)
|
||||
try {
|
||||
yaml.decodeFromString(instance.updaterSerializer, text)
|
||||
when (instance.saveType) {
|
||||
PluginData.SaveType.YAML -> {
|
||||
val yaml = createYaml(instance)
|
||||
yaml.decodeFromString(instance.updaterSerializer, text)
|
||||
}
|
||||
|
||||
PluginData.SaveType.JSON -> {
|
||||
val json = createJson(instance)
|
||||
json.decodeFromString(instance.updaterSerializer, text)
|
||||
}
|
||||
}
|
||||
} catch (cause: Throwable) {
|
||||
// backup data file
|
||||
file.copyTo(file.resolveSibling("${file.name}.${currentTimeMillis()}.bak"))
|
||||
@ -67,7 +76,7 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
}
|
||||
dir.mkdir()
|
||||
|
||||
val file = dir.resolve("$name.yml")
|
||||
val file = dir.resolve("$name.${instance.saveType.extension}")
|
||||
if (file.isDirectory) {
|
||||
error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
|
||||
}
|
||||
@ -82,27 +91,41 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
||||
getPluginDataFile(holder, instance).writeText(
|
||||
kotlin.runCatching {
|
||||
createYaml(instance).encodeToString(instance.updaterSerializer, Unit).also {
|
||||
Yaml.decodeAnyFromString(it) // test yaml
|
||||
when (instance.saveType) {
|
||||
PluginData.SaveType.YAML -> {
|
||||
val yaml = createYaml(instance)
|
||||
yaml.encodeToString(instance.updaterSerializer, Unit).also {
|
||||
yaml.decodeAnyFromString(it) // test yaml
|
||||
}
|
||||
}
|
||||
|
||||
PluginData.SaveType.JSON -> {
|
||||
val json = createJson(instance)
|
||||
json.encodeToString(instance.updaterSerializer, Unit).also {
|
||||
json.decodeFromString(instance.updaterSerializer, it) // test json
|
||||
}
|
||||
}
|
||||
}
|
||||
}.recoverCatching {
|
||||
logger.warning(
|
||||
"Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
|
||||
"Could not save ${instance.saveName} in ${instance.saveType.name} format due to exception in ${instance.saveType.name} encoder. " +
|
||||
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai/issues/new/choose",
|
||||
it
|
||||
)
|
||||
@Suppress("JSON_FORMAT_REDUNDANT")
|
||||
Json {
|
||||
serializersModule = MessageSerializers.serializersModule + instance.serializersModule
|
||||
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowStructuredMapKeys = true
|
||||
encodeDefaults = true
|
||||
}.encodeToString(instance.updaterSerializer, Unit)
|
||||
if (instance.saveType == PluginData.SaveType.JSON) {
|
||||
throw it
|
||||
}
|
||||
|
||||
val json = createJson(instance)
|
||||
json.encodeToString(instance.updaterSerializer, Unit).also { string ->
|
||||
json.decodeFromString(instance.updaterSerializer, string) // test json
|
||||
}
|
||||
}.getOrElse {
|
||||
throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it)
|
||||
throw IllegalStateException(
|
||||
"Exception while saving $instance, saveName=${instance.saveName} in json format",
|
||||
it
|
||||
)
|
||||
}
|
||||
)
|
||||
// logger.verbose { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
@ -114,6 +137,21 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
MessageSerializers.serializersModule + instance.serializersModule // MessageSerializers.serializersModule is dynamic
|
||||
}
|
||||
}
|
||||
|
||||
private fun createJson(instance: PluginData): Json {
|
||||
return Json {
|
||||
serializersModule =
|
||||
MessageSerializers.serializersModule + instance.serializersModule // MessageSerializers.serializersModule is dynamic
|
||||
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowStructuredMapKeys = true
|
||||
encodeDefaults = true
|
||||
|
||||
classDiscriminator = "#class"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Path.mkdir(): Boolean = this.toFile().mkdir()
|
||||
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonClassDiscriminator
|
||||
import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl
|
||||
import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class MultiFilePluginDataStorageImplTests : AbstractConsoleInstanceTest() {
|
||||
@TempDir
|
||||
internal lateinit var storePath: Path
|
||||
|
||||
@Serializable
|
||||
@JsonClassDiscriminator("base_type")
|
||||
internal sealed class Base // not using interface, see https://github.com/Kotlin/kotlinx.serialization/issues/2181
|
||||
|
||||
@Serializable
|
||||
@SerialName("DerivedA")
|
||||
internal data class DerivedA(val valueA: Double) : Base()
|
||||
|
||||
@Serializable
|
||||
@SerialName("DerivedB")
|
||||
internal data class DerivedB(val valueB: String) : Base()
|
||||
|
||||
@Serializable
|
||||
@SerialName("DerivedC")
|
||||
internal object DerivedC : Base() {
|
||||
@Suppress("unused")
|
||||
const val valueC: Int = 42
|
||||
}
|
||||
|
||||
private class YamlPluginData : AutoSavePluginData("test_yaml") {
|
||||
var int by value(1)
|
||||
val map: MutableMap<String, String> by value()
|
||||
val map2: MutableMap<String, MutableMap<String, String>> by value()
|
||||
|
||||
companion object {
|
||||
val string = """
|
||||
int: 2
|
||||
map:
|
||||
key1: value1
|
||||
key2: value2
|
||||
map2:
|
||||
key1:
|
||||
key1: value1
|
||||
key2: value2
|
||||
key2:
|
||||
key1: value1
|
||||
key2: value2
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
private class JsonPluginData : AutoSavePluginData("test_json") {
|
||||
override val saveType = PluginData.SaveType.JSON
|
||||
|
||||
val baseMap: MutableMap<String, Base> by value()
|
||||
|
||||
companion object {
|
||||
val string = """
|
||||
{
|
||||
"baseMap": {
|
||||
"A": {
|
||||
"base_type": "DerivedA",
|
||||
"valueA": 11.4514
|
||||
},
|
||||
"B": {
|
||||
"base_type": "DerivedB",
|
||||
"valueB": "mamoe.mirai"
|
||||
},
|
||||
"C": {
|
||||
"base_type": "DerivedC"
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
private val dataStorage by lazy { MultiFilePluginDataStorageImpl(storePath) }
|
||||
|
||||
@Test
|
||||
fun testYamlLoad() {
|
||||
val data = YamlPluginData()
|
||||
dataStorage.load(mockPlugin, data)
|
||||
dataStorage.getPluginDataFileInternal(mockPlugin, data).writeText(YamlPluginData.string)
|
||||
dataStorage.load(mockPlugin, data)
|
||||
|
||||
assertEquals(2, data.int)
|
||||
assertEquals(mapOf("key1" to "value1", "key2" to "value2"), data.map)
|
||||
assertEquals(
|
||||
mapOf(
|
||||
"key1" to mapOf("key1" to "value1", "key2" to "value2"),
|
||||
"key2" to mapOf("key1" to "value1", "key2" to "value2")
|
||||
), data.map2
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testYamlStore() {
|
||||
val data = YamlPluginData()
|
||||
dataStorage.load(mockPlugin, data)
|
||||
|
||||
data.int = 2
|
||||
data.map["key1"] = "value1"
|
||||
data.map["key2"] = "value2"
|
||||
data.map2["key1"] = mutableMapOf("key1" to "value1", "key2" to "value2")
|
||||
data.map2["key2"] = mutableMapOf("key1" to "value1", "key2" to "value2")
|
||||
|
||||
dataStorage.store(mockPlugin, data)
|
||||
|
||||
val file = dataStorage.getPluginDataFileInternal(mockPlugin, data)
|
||||
assertEquals(YamlPluginData.string, file.readText())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJsonLoad() {
|
||||
val data = JsonPluginData()
|
||||
dataStorage.load(mockPlugin, data)
|
||||
dataStorage.getPluginDataFileInternal(mockPlugin, data).writeText(JsonPluginData.string)
|
||||
dataStorage.load(mockPlugin, data)
|
||||
|
||||
assertEquals(
|
||||
mapOf(
|
||||
"A" to DerivedA(11.4514),
|
||||
"B" to DerivedB("mamoe.mirai"),
|
||||
"C" to DerivedC
|
||||
), data.baseMap
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJsonStore() {
|
||||
val data = JsonPluginData()
|
||||
dataStorage.load(mockPlugin, data)
|
||||
|
||||
data.baseMap["A"] = DerivedA(11.4514)
|
||||
data.baseMap["B"] = DerivedB("mamoe.mirai")
|
||||
data.baseMap["C"] = DerivedC
|
||||
|
||||
dataStorage.store(mockPlugin, data)
|
||||
|
||||
val file = dataStorage.getPluginDataFileInternal(mockPlugin, data)
|
||||
assertEquals(JsonPluginData.string, file.readText())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user