diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
index f5a8c5142..c5e5e3144 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
@@ -10,22 +10,44 @@
package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
import java.io.File
/**
* 表示一个 mirai-console 插件.
*
- * @see JvmPlugin
* @see PluginDescription 插件描述
+ * @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件
+ * @see PluginFileExtensions 支持文件系统存储的扩展
+ *
+ * @see PluginLoader 插件加载器
*/
interface Plugin {
/**
- * 所属插件加载器实例
+ * 所属插件加载器实例, 此加载器必须能加载这个 [Plugin].
*/
val loader: PluginLoader<*, *>
+}
+@Suppress("UNCHECKED_CAST")
+inline val
P.safeLoader: PluginLoader
+ get() = this.loader as PluginLoader
+
+/**
+ * 支持文件系统存储的扩展.
+ *
+ * @see JvmPlugin
+ */
+@MiraiExperimentalAPI("classname is subject to change")
+interface PluginFileExtensions {
/**
- * 插件数据目录
+ * 数据目录
*/
val dataFolder: File
+
+ /**
+ * 从数据目录获取一个文件, 若不存在则创建文件.
+ */
+ @JvmDefault
+ fun file(relativePath: String): File = File(dataFolder, relativePath).apply { createNewFile() }
}
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
index d0e3fdb8b..2ccc8500c 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
-@file:Suppress("unused")
+@file:Suppress("unused", "INAPPLICABLE_JVM_NAME")
package net.mamoe.mirai.console.plugin
@@ -20,6 +20,8 @@ import java.io.File
* 插件加载器只实现寻找插件列表, 加载插件, 启用插件, 关闭插件这四个功能.
*
* 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护.
+ *
+ * @see JarPluginLoader Jar 插件加载器
*/
interface PluginLoader
{
/**
@@ -29,12 +31,15 @@ interface PluginLoader
{
/**
* 获取此插件的描述
+ *
+ * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
*/
- @Throws(PluginLoadException::class)
- fun getPluginDescription(plugin: P): D
+ @get:JvmName("getPluginDescription")
+ @get:Throws(PluginLoadException::class)
+ val P.description: D
/**
- * 加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的实例
+ * 加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例
*
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
*/
@@ -55,7 +60,7 @@ open class PluginLoadException : RuntimeException {
/**
* '/plugins' 目录中的插件的加载器. 每个加载器需绑定一个后缀.
*
- * @see AbstractFilePluginLoader
+ * @see AbstractFilePluginLoader 默认基础实现
* @see JarPluginLoader 内建的 Jar (JVM) 插件加载器.
*/
interface FilePluginLoader
: PluginLoader
{
@@ -65,6 +70,9 @@ interface FilePluginLoader
: PluginLoader
(
override val fileSuffix: String
) : FilePluginLoader
{
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt
similarity index 81%
rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginImpl.kt
rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt
index 48fa96095..e658e7baa 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginImpl.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/internal/JvmPluginInternal.kt
@@ -9,6 +9,7 @@
package net.mamoe.mirai.console.plugin.internal
+import kotlinx.atomicfu.AtomicLong
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
@@ -19,8 +20,10 @@ import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
+import net.mamoe.mirai.console.utils.asResourceContainer
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
+import java.io.InputStream
import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@@ -31,13 +34,17 @@ internal val T.job: Job where T : CoroutineScope, T : Plugin get() = this.co
* Hides implementations from [JvmPlugin]
*/
@PublishedApi
-internal abstract class JvmPluginImpl(
+internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext
) : JvmPlugin,
CoroutineScope {
+
+ private val resourceConsoleDelegate by lazy { this::class.asResourceContainer() }
+ override fun getResourceAsStream(name: String): InputStream = resourceConsoleDelegate.getResourceAsStream(name)
+
// region JvmPlugin
/**
- * Initialized immediately after construction of [JvmPluginImpl] instance
+ * Initialized immediately after construction of [JvmPluginInternal] instance
*/
@Suppress("PropertyName")
internal lateinit var _description: JvmPluginDescription
@@ -102,4 +109,16 @@ internal abstract class JvmPluginImpl(
?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() }
// endregion
+}
+
+internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update: (Long) -> Long): Boolean {
+ while (true) {
+ val current = value
+ if (condition(current)) {
+ if (compareAndSet(0, update(current))) {
+ return true
+ } else continue
+ }
+ return false
+ }
}
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
index 8f5a75ad9..ad910d908 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
@@ -11,7 +11,10 @@
package net.mamoe.mirai.console.plugin.jvm
-import net.mamoe.mirai.console.plugin.internal.JvmPluginImpl
+import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
+import net.mamoe.mirai.console.setting.Setting
+import net.mamoe.mirai.console.setting.getValue
+import net.mamoe.mirai.console.setting.value
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@@ -23,8 +26,16 @@ import kotlin.coroutines.EmptyCoroutineContext
*/
abstract class AbstractJvmPlugin @JvmOverloads constructor(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
-) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) {
- // TODO: 2020/6/24 添加 PluginSetting 继承 Setting, 实现 onValueChanged 并绑定自动保存.
+) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) {
+ final override val name: String get() = this.description.name
- abstract class PluginSetting
+ override fun getSetting(clazz: Class): T = loader.settingStorage.load(this, clazz)
+}
+
+
+object MyPlugin : KotlinPlugin()
+
+object TestSetting : Setting by MyPlugin.getSetting() {
+ val account by value("123456")
+ val password by value("123")
}
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
index 9c7eca155..27633a20f 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
@@ -13,8 +13,10 @@ import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.PluginLoadException
-import net.mamoe.mirai.console.plugin.internal.JvmPluginImpl
+import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
import net.mamoe.mirai.console.plugin.internal.PluginsLoader
+import net.mamoe.mirai.console.setting.SettingStorage
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.yamlkt.Yaml
import java.io.File
@@ -30,6 +32,9 @@ object JarPluginLoader : AbstractFilePluginLoader.mapToDescription(): List {
return this.associateWith { URL("jar:${it.absolutePath}!/plugin.yml") }.mapNotNull { (file, url) ->
@@ -82,7 +89,7 @@ object JarPluginLoader : AbstractFilePluginLoader) {
+ lastAutoSaveJob = launch {
+ currentFirstStartTime.updateWhen({ it == 0L }, { currentTimeMillis })
+
+ delay(autoSaveIntervalMillis.first.coerceAtLeast(1000)) // for safety
+
+ if (lastAutoSaveJob == job) {
+ doSave()
+ } else {
+ if (currentFirstStartTime.updateWhen(
+ { currentTimeMillis - it >= autoSaveIntervalMillis.last },
+ { 0 })
+ ) {
+ doSave()
+ }
+ }
+ }
+ }
+
+ private fun doSave() {
+ loader.settingStorage.store(this@AbstractJvmPlugin, this@PluginSetting)
+ }
+ }
+ */
+
/**
* Java API Scheduler
*/
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
index 243300905..8a629e0c9 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
@@ -13,7 +13,12 @@ package net.mamoe.mirai.console.plugin.jvm
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.plugin.Plugin
+import net.mamoe.mirai.console.plugin.PluginFileExtensions
+import net.mamoe.mirai.console.setting.Setting
+import net.mamoe.mirai.console.setting.SettingHolder
+import net.mamoe.mirai.console.utils.ResourceContainer
import net.mamoe.mirai.utils.MiraiLogger
+import kotlin.reflect.KClass
/**
@@ -23,8 +28,11 @@ import net.mamoe.mirai.utils.MiraiLogger
*
* @see JavaPlugin Java 插件
* @see KotlinPlugin Kotlin 插件
+ *
+ * @see JvmPlugin 支持文件系统扩展
+ * @see ResourceContainer 支持资源获取 (如 Jar 中的资源文件)
*/
-interface JvmPlugin : Plugin, CoroutineScope {
+interface JvmPlugin : Plugin, CoroutineScope, PluginFileExtensions, ResourceContainer, SettingHolder {
/** 日志 */
val logger: MiraiLogger
@@ -34,6 +42,11 @@ interface JvmPlugin : Plugin, CoroutineScope {
/** 所属插件加载器实例 */
override val loader: JarPluginLoader get() = JarPluginLoader
+ /**
+ * 获取一个 [Setting] 实例
+ */
+ fun getSetting(clazz: Class): T
+
@JvmDefault
fun onLoad() {
}
@@ -47,4 +60,5 @@ interface JvmPlugin : Plugin, CoroutineScope {
}
}
-
+fun JvmPlugin.getSetting(clazz: KClass) = this.getSetting(clazz.java)
+inline fun JvmPlugin.getSetting() = this.getSetting(T::class)
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt
index c79a836b6..01f8ecb86 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt
@@ -17,9 +17,23 @@ import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KProperty
import kotlin.reflect.KType
-// Shows public APIs such as deciding when to auto-save.
-abstract class Setting : SettingImpl() {
- operator fun SerializerAwareValue.provideDelegate(
+
+/**
+ * 序列化之后的名称.
+ *
+ * 例:
+ * ```
+ * class MySetting : Setting() {
+ *
+ * }
+ * ```
+ */
+// TODO: 2020/6/26 document
+typealias SerialName = kotlinx.serialization.SerialName
+
+// TODO: 2020/6/26 document
+abstract class AbstractSetting : Setting, SettingImpl() {
+ final override operator fun SerializerAwareValue.provideDelegate(
thisRef: Any?,
property: KProperty<*>
): SerializerAwareValue {
@@ -28,7 +42,19 @@ abstract class Setting : SettingImpl() {
return this
}
- public override val updaterSerializer: KSerializer get() = super.updaterSerializer
+ final override val updaterSerializer: KSerializer get() = super.updaterSerializer
+}
+
+// TODO: 2020/6/26 document
+interface Setting {
+ // TODO: 2020/6/26 document
+ operator fun SerializerAwareValue.provideDelegate(
+ thisRef: Any?,
+ property: KProperty<*>
+ ): SerializerAwareValue
+
+ // TODO: 2020/6/26 document
+ val updaterSerializer: KSerializer
}
//// region Setting_value_primitives CODEGEN ////
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt
index e578269b3..abe7424e3 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/SettingStorage.kt
@@ -1,3 +1,19 @@
package net.mamoe.mirai.console.setting
-interface SettingStorage
+import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import kotlin.reflect.KClass
+
+interface SettingStorage {
+ @JvmDefault
+ fun load(holder: SettingHolder, settingClass: KClass): T = this.load(holder, settingClass.java)
+
+ fun load(holder: SettingHolder, settingClass: Class): T
+
+ @MiraiExperimentalAPI
+ fun store(holder: SettingHolder, setting: Setting)
+}
+
+@MiraiExperimentalAPI
+interface SettingHolder {
+ val name: String
+}
\ No newline at end of file
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingImpl.kt
index 8569ec27e..44e4a10b4 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingImpl.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/SettingImpl.kt
@@ -14,6 +14,7 @@ package net.mamoe.mirai.console.setting.internal
import kotlinx.serialization.*
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
+import net.mamoe.mirai.console.setting.SerializerAwareValue
import net.mamoe.mirai.console.setting.Setting
import net.mamoe.mirai.console.setting.Value
import net.mamoe.yamlkt.YamlNullableDynamicSerializer
@@ -36,6 +37,14 @@ internal abstract class SettingImpl {
val updaterSerializer: KSerializer
)
+ internal fun SerializerAwareValue.provideDelegateImpl(
+ property: KProperty<*>
+ ): SerializerAwareValue {
+ val name = property.serialName
+ valueNodes.add(Node(name, this, this.serializer))
+ return this
+ }
+
internal val valueNodes: MutableList> = mutableListOf()
internal open val updaterSerializer: KSerializer = object : KSerializer {
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt
new file mode 100644
index 000000000..6f8e8b4c5
--- /dev/null
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/ResourceContainer.kt
@@ -0,0 +1,58 @@
+@file:Suppress("unused")
+
+package net.mamoe.mirai.console.utils
+
+import net.mamoe.mirai.console.encodeToString
+import java.io.InputStream
+import java.nio.charset.Charset
+import kotlin.reflect.KClass
+
+/**
+ * 资源容器.
+ */
+interface ResourceContainer {
+ /**
+ * 获取一个资源文件
+ */
+ fun getResourceAsStream(name: String): InputStream
+
+ /**
+ * 读取一个资源文件并以 [Charsets.UTF_8] 编码为 [String]
+ */
+ @JvmDefault
+ fun getResource(name: String): String = getResource(name, Charsets.UTF_8)
+
+ /**
+ * 读取一个资源文件并以 [charset] 编码为 [String]
+ */
+ @JvmDefault
+ fun getResource(name: String, charset: Charset): String =
+ this.getResourceAsStream(name).use { it.readBytes() }.encodeToString()
+
+ companion object {
+ /**
+ * 使用 [Class.getResourceAsStream] 读取资源文件
+ *
+ * @see asResourceContainer Kotlin 使用
+ */
+ @JvmStatic
+ @JavaFriendlyAPI
+ fun byClass(clazz: Class<*>): ResourceContainer = clazz.asResourceContainer()
+ }
+}
+
+internal class ClassAsResourceContainer(
+ private val clazz: Class<*>
+) : ResourceContainer {
+ override fun getResourceAsStream(name: String): InputStream = clazz.getResourceAsStream(name)
+}
+
+/**
+ * 使用 [Class.getResourceAsStream] 读取资源文件
+ */
+fun KClass<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this.java)
+
+/**
+ * 使用 [Class.getResourceAsStream] 读取资源文件
+ */
+fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this)
\ No newline at end of file
diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt
index b3b2e51ed..26fd38504 100644
--- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt
+++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/setting/SettingTest.kt
@@ -18,7 +18,7 @@ import kotlin.test.assertSame
internal class SettingTest {
- class MySetting : Setting() {
+ class MySetting : AbstractSetting() {
var int by value(1)
val map by value>()
val map2 by value>>()