Cache BdhSession and ServerList for next login

This commit is contained in:
Karlatemp 2021-02-09 13:51:08 +08:00
parent 7d108d3222
commit c9f56175af
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
11 changed files with 162 additions and 20 deletions

View File

@ -5364,6 +5364,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
public final fun getAutoReconnectOnForceOffline ()Z
public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
public final fun getCacheDirSupplier ()Lkotlin/jvm/functions/Function0;
public static final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration;
public final fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
public final fun getFirstReconnectDelayMillis ()J
@ -5405,6 +5406,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun setAutoReconnectOnForceOffline (Z)V
public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
public final fun setCacheDirSupplier (Lkotlin/jvm/functions/Function0;)V
public final fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
public final fun setFirstReconnectDelayMillis (J)V
public final fun setFriendListCache (Lnet/mamoe/mirai/utils/BotConfiguration$FriendListCache;)V

View File

@ -56,6 +56,11 @@ public open class BotConfiguration { // open for Java
*/
public var workingDir: File = File(".")
/**
* 缓存数据目录
*/
public var cacheDirSupplier: (() -> File) = { workingDir.resolve("cache") }
/**
* Json 序列化器, 使用 'kotlinx.serialization'
*/
@ -396,11 +401,11 @@ public open class BotConfiguration { // open for Java
*/
public class FriendListCache @JvmOverloads constructor(
/**
* 缓存文件位置, 相对于 [workingDir] 的路径.
* 缓存文件位置, 相对于 [cacheDirSupplier] 的路径.
*
* 注意: 保存的文件仅供内部使用, 将来可能会变化.
*/
public val cacheFile: File = File("cache/friends.json"),
public val cacheFile: File = File("friends.json"),
/**
* 在有好友列表修改时自动保存间隔
*/
@ -432,11 +437,11 @@ public open class BotConfiguration { // open for Java
*/
public class GroupMemberListCache @JvmOverloads constructor(
/**
* 缓存目录位置, 相对于 [workingDir] 的路径.
* 缓存目录位置, 相对于 [cacheDirSupplier] 的路径.
*
* 注意: 保存的文件仅供内部使用, 将来可能会变化.
*/
public val cacheDir: File = File("cache/groups"),
public val cacheDir: File = File("groups"),
/**
* 在有成员列表修改时自动保存间隔
*/
@ -470,6 +475,7 @@ public open class BotConfiguration { // open for Java
return BotConfiguration().also { new ->
// To structural order
new.workingDir = workingDir
new.cacheDirSupplier = cacheDirSupplier
new.json = json
new.parentCoroutineContext = parentCoroutineContext
new.heartbeatPeriodMillis = heartbeatPeriodMillis

View File

@ -68,7 +68,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
}
// region network
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
internal val serverList: MutableList<Pair<String, Int>> = mutableListOf()
val network: N get() = _network
@ -149,7 +149,10 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
bot.asQQAndroidBot().client.run {
if (serverList.isEmpty()) {
serverList.addAll(DefaultServerList)
bot.asQQAndroidBot().bdhSyncer.loadServerListFromCache()
if (serverList.isEmpty()) {
serverList.addAll(DefaultServerList)
} else Unit
} else serverList.removeAt(0)
}

View File

@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.contact.uin
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.handler.BdhSessionSyncer
import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
@ -57,6 +58,8 @@ internal class QQAndroidBot constructor(
private val account: BotAccount,
configuration: BotConfiguration
) : AbstractBot<QQAndroidBotNetworkHandler>(configuration, account.id) {
val bdhSyncer: BdhSessionSyncer = BdhSessionSyncer(this)
var client: QQAndroidClient = initClient()
private set
@ -79,7 +82,7 @@ internal class QQAndroidBot constructor(
val friendListCache: FriendListCache? by lazy {
configuration.friendListCache?.cacheFile?.let { cacheFile ->
val ret = configuration.workingDir.resolve(cacheFile).loadAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache()
val ret = configuration.cacheDirSupplier().resolve(cacheFile).loadAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache()
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.eventChannel.parentScope(this@QQAndroidBot)
@ -130,6 +133,7 @@ internal class QQAndroidBot constructor(
@ThisApiMustBeUsedInWithConnectionLockBlock
@Throws(LoginFailedException::class) // only
override suspend fun relogin(cause: Throwable?) {
bdhSyncer.loadFromCache()
client.useNextServers { host, port ->
network.closeEverythingAndRelogin(host, port, cause, 0)
}

View File

@ -84,7 +84,7 @@ internal class GroupMemberListCaches(
}
private val cacheDir by lazy {
bot.configuration.groupMemberListCache!!.cacheDir.let { bot.configuration.workingDir.resolve(it) }
bot.configuration.groupMemberListCache!!.cacheDir.let { bot.configuration.cacheDirSupplier().resolve(it) }
}
private fun resolveCacheFile(groupCode: Long): File {

View File

@ -19,6 +19,7 @@ import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.String
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.OnlineStatus
import net.mamoe.mirai.internal.BotAccount
import net.mamoe.mirai.internal.QQAndroidBot
@ -281,11 +282,6 @@ internal open class QQAndroidClient(
lateinit var t104: ByteArray
/**
* from ConfigPush.PushReq
*/
@JvmField
val bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
}
internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraData) {
@ -298,6 +294,7 @@ internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraDat
}
}
@Serializable
internal class BdhSession(
val sigSession: ByteArray,
val sessionKey: ByteArray,

View File

@ -0,0 +1,120 @@
/*
* Copyright 2019-2021 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/master/LICENSE
*/
package net.mamoe.mirai.internal.network.handler
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.network.BdhSession
import net.mamoe.mirai.internal.network.JsonForCache
import java.io.File
private val ServerListSerializer: KSerializer<List<Pair<String, Int>>> =
ListSerializer(PairSerializer(String.serializer(), Int.serializer()))
@OptIn(ExperimentalCoroutinesApi::class)
internal class BdhSessionSyncer(
private val bot: QQAndroidBot
) {
var bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
val hasSession: Boolean get() = bdhSession.isCompleted
fun overrideSession(
session: BdhSession,
doSave: Boolean = true
) {
bdhSession.complete(session)
bdhSession = CompletableDeferred(session)
if (doSave) {
saveToCache()
}
}
private val sessionCacheFile: File
get() = bot.configuration.cacheDirSupplier().resolve("session.json")
private val serverListCacheFile: File
get() = bot.configuration.cacheDirSupplier().resolve("serverlist.json")
fun loadServerListFromCache() {
val serverListCacheFile = this.serverListCacheFile
if (serverListCacheFile.isFile) {
bot.network.logger.verbose("Loading server list from cache.")
kotlin.runCatching {
val list = JsonForCache.decodeFromString(ServerListSerializer, serverListCacheFile.readText())
bot.serverList.clear()
bot.serverList.addAll(list)
}.onFailure {
bot.network.logger.warning("Error in loading server list from cache", it)
}
} else {
bot.network.logger.verbose("No server list cached.")
}
}
fun loadFromCache() {
val sessionCacheFile = this.sessionCacheFile
if (sessionCacheFile.isFile) {
bot.network.logger.verbose("Loading BdhSession from cache file")
kotlin.runCatching {
overrideSession(
JsonForCache.decodeFromString(BdhSession.serializer(), sessionCacheFile.readText()),
doSave = false
)
}.onFailure {
bot.network.logger.warning("Error in loading BdhSession from cache", it)
}
} else {
bot.network.logger.verbose("No BdhSession cache")
}
}
fun saveServerListToCache() {
val serverListCacheFile = this.serverListCacheFile
serverListCacheFile.parentFile?.mkdirs()
bot.network.logger.verbose("Saving server list to cache")
kotlin.runCatching {
serverListCacheFile.writeText(
JsonForCache.encodeToString(
ServerListSerializer,
bot.serverList
)
)
}.onFailure {
bot.network.logger.warning("Error in saving ServerList to cache.", it)
}
}
fun saveToCache() {
val sessionCacheFile = this.sessionCacheFile
sessionCacheFile.parentFile?.mkdirs()
if (bdhSession.isCompleted) {
bot.network.logger.verbose("Saving bdh session to cache")
kotlin.runCatching {
sessionCacheFile.writeText(
JsonForCache.encodeToString(
BdhSession.serializer(),
bdhSession.getCompleted()
)
)
}.onFailure {
bot.network.logger.warning("Error in saving BdhSession to cache.", it)
}
} else {
sessionCacheFile.delete()
bot.network.logger.verbose("No BdhSession to save to cache")
}
}
}

View File

@ -359,8 +359,13 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
logger.info { "Awaiting ConfigPushSvc.PushReq." }
when (val resp: ConfigPushSvc.PushReq.PushReqResponse? = nextEventOrNull(20_000)) {
null -> {
kotlin.runCatching { bot.client.bdhSession.completeExceptionally(CancellationException("Timeout waiting for ConfigPushSvc.PushReq")) }
logger.warning { "Missing ConfigPushSvc.PushReq. File uploading may be affected." }
val hasSession = bot.bdhSyncer.hasSession
kotlin.runCatching { bot.bdhSyncer.bdhSession.completeExceptionally(CancellationException("Timeout waiting for ConfigPushSvc.PushReq")) }
if (hasSession) {
logger.warning { "Missing ConfigPushSvc.PushReq. File uploading may be affected." }
} else {
logger.warning { "Missing ConfigPushSvc.PushReq. Using latest response. File uploading may be affected." }
}
}
is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
logger.info { "ConfigPushSvc.PushReq: Success." }
@ -375,6 +380,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
bot.serverList.add(it.host to it.port)
}
}
bot.bdhSyncer.saveToCache()
bot.bdhSyncer.saveServerListToCache()
bot.launch { BotOfflineEvent.RequireReconnect(bot).broadcast() }
return@launch

View File

@ -59,7 +59,7 @@ internal object Highway {
fallbackSession: (Throwable) -> BdhSession = { throw IllegalStateException("Failed to get bdh session", it) }
): BdhUploadResponse {
val bdhSession = kotlin.runCatching {
val deferred = bot.client.bdhSession
val deferred = bot.bdhSyncer.bdhSession
// no need to care about timeout. proceed by bot init
@OptIn(ExperimentalCoroutinesApi::class)
if (noBdhAwait) deferred.getCompleted() else deferred.await()

View File

@ -200,7 +200,10 @@ internal open class KeyWithCreationTime(
internal suspend inline fun QQAndroidClient.useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
if (bot.serverList.isEmpty()) {
bot.serverList.addAll(DefaultServerList)
bot.bdhSyncer.loadServerListFromCache()
if (bot.serverList.isEmpty()) {
bot.serverList.addAll(DefaultServerList)
}
}
retryCatchingExceptions(bot.serverList.size, except = LoginFailedException::class) l@{
val pair = bot.serverList[0]

View File

@ -110,7 +110,7 @@ internal class ConfigPushSvc {
val bigDataChannel = fileStoragePushFSSvcList.bigDataChannel
if (bigDataChannel?.vBigdataPbBuf == null) {
client.bdhSession.completeExceptionally(IllegalStateException("BdhSession not received."))
bot.bdhSyncer.bdhSession.completeExceptionally(IllegalStateException("BdhSession not received."))
return
}
@ -134,11 +134,11 @@ internal class ConfigPushSvc {
session
}.fold(
onSuccess = {
client.bdhSession.complete(it)
bdhSyncer.overrideSession(it)
},
onFailure = { cause ->
val e = IllegalStateException("Failed to decode BdhSession", cause)
client.bdhSession.completeExceptionally(e)
bdhSyncer.bdhSession.completeExceptionally(e)
logger.error(e)
}
)