mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-13 06:30:13 +08:00
Cache BdhSession and ServerList for next login
This commit is contained in:
parent
7d108d3222
commit
c9f56175af
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user