internal.plugin.JvmPluginInternal.kt Maven / Gradle / Ivy
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.plugin
import kotlinx.atomicfu.AtomicLong
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.runCatchingLog
import net.mamoe.mirai.console.extension.PluginComponentStorage
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
import net.mamoe.mirai.console.util.NamedSupervisorJob
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.CoroutineContext
internal val T.job: Job where T : CoroutineScope, T : Plugin get() = this.coroutineContext[Job]!!
/**
* Hides implementations from [JvmPlugin]
*/
@PublishedApi
internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext,
) : JvmPlugin, CoroutineScope {
final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register(
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"),
"The base permission"
)
}
final override var isEnabled: Boolean = false
internal set
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
final override fun getResourceAsStream(path: String): InputStream? =
resourceContainerDelegate.getResourceAsStream(path)
// region JvmPlugin
final override val logger: MiraiLogger by lazy {
BuiltInJvmPluginLoaderImpl.logger.runCatchingLog {
MiraiConsole.createLogger(this.description.name)
}.getOrThrow()
}
private var firstRun = true
final override val dataFolderPath: Path by lazy {
PluginManager.pluginsDataPath.resolve(description.name).apply { mkdir() }
}
final override val dataFolder: File by lazy {
dataFolderPath.toFile()
}
final override val configFolderPath: Path by lazy {
PluginManager.pluginsConfigPath.resolve(description.name).apply { mkdir() }
}
final override val configFolder: File by lazy {
configFolderPath.toFile()
}
internal fun internalOnDisable() {
firstRun = false
kotlin.runCatching {
onDisable()
}.fold(
onSuccess = {
cancel(CancellationException("plugin disabled"))
},
onFailure = {
cancel(CancellationException("Exception while enabling plugin", it))
}
)
isEnabled = false
}
@Throws(Throwable::class)
internal fun internalOnLoad() {
val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage)
GlobalComponentStorage.mergeWith(componentStorage)
}
internal fun internalOnEnable(): Boolean {
parentPermission
if (!firstRun) refreshCoroutineContext()
kotlin.runCatching {
onEnable()
}.fold(
onSuccess = {
isEnabled = true
return true
},
onFailure = {
cancel(CancellationException("Exception while enabling plugin", it))
logger.error(it)
return false
}
)
}
// endregion
// region CoroutineScope
// for future use
@Suppress("PropertyName")
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
this as AbstractJvmPlugin
CoroutineName("Plugin $dataHolderName")
}
@JvmField
internal val coroutineContextInitializer = {
CoroutineExceptionHandler { context, throwable ->
if (throwable.rootCauseOrSelf !is CancellationException) logger.error(
"Exception in coroutine ${context[CoroutineName]?.name ?: ""} of ${description.name}",
throwable
)
}
.plus(parentCoroutineContext)
.plus(
NamedSupervisorJob(
"Plugin ${(this as AbstractJvmPlugin).dataHolderName}",
parentCoroutineContext[Job] ?: BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!
)
)
.also {
if (!MiraiConsole.isActive) return@also
BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion {
this.cancel()
}
}
.plus(_intrinsicCoroutineContext)
}
private fun refreshCoroutineContext(): CoroutineContext {
return coroutineContextInitializer().also { _coroutineContext = it }.also {
job.invokeOnCompletion { e ->
if (e != null) {
if (e !is CancellationException) logger.error(e)
if (this.isEnabled) safeLoader.disable(this)
}
}
}
}
private val contextUpdateLock: ReentrantLock =
ReentrantLock()
private var _coroutineContext: CoroutineContext? = null
final override val coroutineContext: CoroutineContext
get() = _coroutineContext
?: 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(current, update(current))) {
return true
} else continue
}
return false
}
}
internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this