Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign MultiMsg #1667

Merged
merged 4 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mirai-core-utils/src/commonMain/kotlin/Numbers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ package net.mamoe.mirai.utils
public fun Int.toLongUnsigned(): Long = this.toLong().and(0xFFFF_FFFF)
public fun Short.toIntUnsigned(): Int = this.toUShort().toInt()
public fun Byte.toIntUnsigned(): Int = toInt() and 0xFF
public fun Int.concatAsLong(i2: Int): Long = this.toLongUnsigned().shl(Int.SIZE_BITS) or i2.toLongUnsigned()
96 changes: 96 additions & 0 deletions mirai-core-utils/src/commonMain/kotlin/Resources.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,104 @@

package net.mamoe.mirai.utils

import java.util.concurrent.atomic.AtomicInteger


@TestOnly
public fun readResource(url: String): String =
Thread.currentThread().contextClassLoader.getResourceAsStream(url)?.readBytes()?.decodeToString()
?: error("Could not find resource '$url'")

public class ResourceAccessLock {
public companion object {
public const val LOCKED: Int = -2
public const val UNINITIALIZED: Int = -1
public const val INITIALIZED: Int = 0
}

/*
* status > 0 -> Number of holders using resource
*/
private val status = AtomicInteger(-1)

/**
* ```
* if (res.lock.tryToDispose()) {
* res.internal.close()
* }
* ```
*/
public fun tryDispose(): Boolean {
return status.compareAndSet(0, -1)
}

/**
* ```
* if (res.lock.tryInitialize()) {
* res.internalRes = download()
* }
* ```
*/
public fun tryInitialize(): Boolean {
return status.compareAndSet(-1, 0)
}

public fun tryUse(): Boolean {
val c = status
while (true) {
val v = c.get()
if (v < 0) return false
if (c.compareAndSet(v, v + 1)) return true
}
}

public fun lockIfNotUsing(): Boolean {
val count = this.status
while (true) {
val value = count.get()
if (value != 0) return false
if (count.compareAndSet(0, -2)) return true
}
}

public fun release() {
val count = this.status
while (true) {
val value = count.get()
if (value < 1) throw IllegalStateException("Current resource not in using")

if (count.compareAndSet(value, value - 1)) return
}
}

public fun unlock() {
status.compareAndSet(LOCKED, INITIALIZED)
}

public fun setInitialized() {
status.set(INITIALIZED)
}

public fun setLocked() {
status.set(LOCKED)
}

public fun setDisposed() {
setUninitialized()
}

public fun setUninitialized() {
status.set(UNINITIALIZED)
}

public fun currentStatus(): Int = status.get()

override fun toString(): String {
return when (val status = status.get()) {
0 -> "ResourceAccessLock(INITIALIZED)"
-1 -> "ResourceAccessLock(UNINITIALIZED)"
-2 -> "ResourceAccessLock(LOCKED)"
else -> "ResourceAccessLock($status)"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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/dev/LICENSE
*/

package net.mamoe.mirai.utils

public fun <T : Any> unsafeMutableNonNullPropertyOf(
name: String = "<unknown>"
): UnsafeMutableNonNullProperty<T> {
return UnsafeMutableNonNullProperty(name)
}

@Suppress("NOTHING_TO_INLINE")
public class UnsafeMutableNonNullProperty<T : Any>(
private val propertyName: String = "<unknown>"
) {
@JvmField
public var value0: T? = null

public val isInitialized: Boolean get() = value0 !== null
public var value: T
get() = value0 ?: throw UninitializedPropertyAccessException("Property `$propertyName` not initialized")
set(value) {
value0 = value
}

public fun clear() {
value0 = null
}

public inline operator fun getValue(thiz: Any?, property: Any?): T = value
public inline operator fun setValue(thiz: Any?, property: Any?, value: T) {
value0 = value
}

override fun toString(): String {
return value0?.toString() ?: "<uninitialized>"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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/dev/LICENSE
*/

package net.mamoe.mirai.utils

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertFalse
import kotlin.test.assertTrue

internal class ResourceAccessLockTest {
@Test
fun testInitializedLockCannotReInit() {
val lock = ResourceAccessLock()
lock.setInitialized()
assertFalse { lock.tryInitialize() }
}

@Test
fun testUseFailedIfLockUninitializedOrLocked() {
val lock = ResourceAccessLock()
lock.setUninitialized()
assertFalse { lock.tryUse() }
lock.setLocked()
assertFalse { lock.tryUse() }
}

@Test
fun testLockFailedIfUninitialized() {
val lock = ResourceAccessLock()
lock.setUninitialized()
assertFalse { lock.lockIfNotUsing() }
}

@Test
fun testLockFailedIfUsing() {
val lock = ResourceAccessLock()
lock.setInitialized()
assertTrue { lock.tryUse() }
assertFalse { lock.lockIfNotUsing() }
}

@Test
fun testLockUsedIfInitialized() {
val lock = ResourceAccessLock()
lock.setInitialized()
assertTrue { lock.tryUse() }
}

@Test
fun testRelease() {
val lock = ResourceAccessLock()
lock.setInitialized()
assertFails { lock.release() }
assertEquals(ResourceAccessLock.INITIALIZED, lock.currentStatus())
assertTrue { lock.tryUse() }
lock.release()
}
}
99 changes: 31 additions & 68 deletions mirai-core/src/commonMain/kotlin/MiraiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.util.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
Expand All @@ -38,34 +37,37 @@ import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
import net.mamoe.mirai.internal.network.components.EventDispatcher
import net.mamoe.mirai.internal.network.components.EventDispatcherScopeFlag
import net.mamoe.mirai.internal.network.highway.*
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.highway.tryDownload
import net.mamoe.mirai.internal.network.highway.tryServersDownload
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
import net.mamoe.mirai.internal.network.protocol.packet.chat.MultiMsg
import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact
import net.mamoe.mirai.internal.network.protocol.packet.chat.NudgePacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.PbMessageSvc
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
import net.mamoe.mirai.internal.network.psKey
import net.mamoe.mirai.internal.network.sKey
import net.mamoe.mirai.internal.utils.ImagePatcher
import net.mamoe.mirai.internal.utils.MiraiProtocolInternal
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1
import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
import kotlin.math.absoluteValue
import kotlin.random.Random

internal fun getMiraiImpl() = Mirai as MiraiImpl

Expand Down Expand Up @@ -632,70 +634,18 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
sendMessageHandler: SendMessageHandler<*>,
message: Collection<ForwardMessage.INode>,
isLong: Boolean,
): String = with(bot.asQQAndroidBot()) {
): String {
bot.asQQAndroidBot()
message.forEach {
it.messageChain.ensureSequenceIdAvailable()
}
val uploader = MultiMsgUploader(
client = bot.client,
isLong = isLong,
handler = sendMessageHandler,
).also { it.emitMain(message) }


val data = message.calculateValidationData(
client = client,
random = Random.nextInt().absoluteValue,
sendMessageHandler,
isLong,
)

val response = network.run {
MultiMsg.ApplyUp.createForGroup(
buType = if (isLong) 1 else 2,
client = bot.client,
messageData = data,
dstUin = sendMessageHandler.targetUin
).sendAndExpect()
}

val resId: String
when (response) {
is MultiMsg.ApplyUp.Response.MessageTooLarge ->
error(
"Internal error: message is too large, but this should be handled before sending. "
)
is MultiMsg.ApplyUp.Response.RequireUpload -> {
resId = response.proto.msgResid

val body = LongMsg.ReqBody(
subcmd = 1,
platformType = 9,
termType = 5,
msgUpReq = listOf(
LongMsg.MsgUpReq(
msgType = 3, // group
dstUin = sendMessageHandler.targetUin,
msgId = 0,
msgUkey = response.proto.msgUkey,
needCache = 0,
storeType = 2,
msgContent = data.data
)
)
).toByteArray(LongMsg.ReqBody.serializer())

body.toExternalResource().use { resource ->
Highway.uploadResourceBdh(
bot = bot,
resource = resource,
kind = when (isLong) {
true -> ResourceKind.LONG_MESSAGE
false -> ResourceKind.FORWARD_MESSAGE
},
commandId = 27,
initialTicket = response.proto.msgSig
)
}
}
}

return resId
return uploader.uploadAndReturnResId()
}

override suspend fun solveNewFriendRequestEvent(
Expand Down Expand Up @@ -813,7 +763,20 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {

override fun createImage(imageId: String): Image {
return when {
imageId matches IMAGE_ID_REGEX -> OfflineGroupImage(imageId)
imageId matches IMAGE_ID_REGEX -> {
Bot.instancesSequence.forEach { existsBot ->
runCatching {
val patcher = existsBot.asQQAndroidBot().components[ImagePatcher]

patcher.findCacheByImageId(imageId)?.let { cache ->
val rsp = cache.cacheOGI.value0
cache.accessLock.release()
if (rsp != null) return rsp
}
}
}
OfflineGroupImage(imageId)
}
imageId matches IMAGE_RESOURCE_ID_REGEX_1 -> OfflineFriendImage(imageId)
imageId matches IMAGE_RESOURCE_ID_REGEX_2 -> OfflineFriendImage(imageId)
else ->
Expand Down
2 changes: 2 additions & 0 deletions mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor
import net.mamoe.mirai.internal.network.notice.priv.OtherClientNoticeProcessor
import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.utils.ImagePatcher
import net.mamoe.mirai.internal.utils.subLogger
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiLogger
Expand Down Expand Up @@ -223,6 +224,7 @@ internal open class QQAndroidBot constructor(
AccountSecretsManager,
configuration.createAccountsSecretsManager(bot.logger.subLogger("AccountSecretsManager")),
)
set(ImagePatcher, ImagePatcher())
}

/**
Expand Down
Loading