Skip to content

Commit

Permalink
Add image caches; Fix deserialized image not rendering in `ForwardMes…
Browse files Browse the repository at this point in the history
…sage`; fix #1507, fix #1636
  • Loading branch information
Karlatemp committed Nov 12, 2021
1 parent c77eac0 commit d48ed30
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 62 deletions.
85 changes: 85 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,93 @@

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 {
// -2: Locked
// -1: Uninitialized
// 0: Initialized
// >0: Using
private val count = AtomicInteger(-1)

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

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

public fun tryToUse(): Boolean {
val c = count
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.count
while (true) {
val value = count.get()
if (value != 0) return false
if (count.compareAndSet(0, -2)) return true
}
}

public fun release() {
count.getAndDecrement()
}

public fun unlock() {
count.compareAndSet(-2, 0)
}

public fun setToInit() {
count.set(0)
}

public fun setToLocked() {
count.set(-2)
}

public fun setToDisposed() {
count.set(-1)
}

public fun setToUninitialized() {
setToDisposed()
}

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

override fun toString(): String {
return when (val status = count.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,54 @@
/*
* 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.assertFalse
import kotlin.test.assertTrue

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

@Test
fun testUseFailedIfLockUninitializedOrLocked() {
val lock = ResourceAccessLock()
lock.setToUninitialized()
assertFalse { lock.tryToUse() }
lock.setToLocked()
assertFalse { lock.tryToUse() }
}

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

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

@Test
fun testLockUsedIfInitialized() {
val lock = ResourceAccessLock()
lock.setToInit()
assertTrue { lock.tryToUse() }
}
}
16 changes: 15 additions & 1 deletion mirai-core/src/commonMain/kotlin/MiraiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ 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
Expand Down Expand Up @@ -762,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.access.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
9 changes: 9 additions & 0 deletions mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
import net.mamoe.mirai.internal.utils.ImagePatcher
import net.mamoe.mirai.internal.utils.RemoteFileImpl
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.internal.utils.subLogger
Expand Down Expand Up @@ -181,6 +182,12 @@ internal class GroupImpl constructor(
if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
}

fun OfflineGroupImage.putIntoCache() {
// We can't understand wny Image(group.uploadImage().imageId)
bot.components[ImagePatcher].putCache(this)
}

val imageInfo = runBIO { resource.calculateImageInfo() }
bot.network.run<NetworkHandler, Image> {
val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
Expand Down Expand Up @@ -216,6 +223,7 @@ internal class GroupImpl constructor(
.also {
it.fileId = response.fileId.toInt()
}
.also { it.putIntoCache() }
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
}
is ImgStore.GroupPicUp.Response.RequireUpload -> {
Expand Down Expand Up @@ -244,6 +252,7 @@ internal class GroupImpl constructor(
size = resource.size
)
}.also { it.fileId = response.fileId.toInt() }
.also { it.putIntoCache() }
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
}
}
Expand Down
77 changes: 16 additions & 61 deletions mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.*
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.utils.ImagePatcher
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.castOrNull
Expand Down Expand Up @@ -141,10 +140,20 @@ internal abstract class SendMessageHandler<C : Contact> {
if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
return when (step) {
SendMessageStep.FIRST -> {
sendMessageImpl(originalMessage, transformedMessage, isMiraiInternal, SendMessageStep.LONG_MESSAGE)
sendMessageImpl(
originalMessage,
transformedMessage,
isMiraiInternal,
SendMessageStep.LONG_MESSAGE,
)
}
SendMessageStep.LONG_MESSAGE -> {
sendMessageImpl(originalMessage, transformedMessage, isMiraiInternal, SendMessageStep.FRAGMENTED)
sendMessageImpl(
originalMessage,
transformedMessage,
isMiraiInternal,
SendMessageStep.FRAGMENTED,
)

}
else -> {
Expand All @@ -157,7 +166,7 @@ internal abstract class SendMessageHandler<C : Contact> {
}
}
}
if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable){
if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable) {
throw IllegalStateException("Send message to $contact failed, server service is unavailable.")
}
if (resp is MessageSvcPbSendMsg.Response.Failed) {
Expand Down Expand Up @@ -446,65 +455,11 @@ internal open class GroupSendMessageHandler(

companion object {
private suspend fun GroupImpl.fixImageFileId(image: OfflineGroupImage) {
if (image.fileId == null) {
val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp(
bot.client,
uin = bot.id,
groupCode = this.id,
md5 = image.md5,
size = 1,
).sendAndExpect(bot)

when (response) {
is ImgStore.GroupPicUp.Response.Failed -> {
image.fileId = 0 // Failed
}
is ImgStore.GroupPicUp.Response.FileExists -> {
image.fileId = response.fileId.toInt()
}
is ImgStore.GroupPicUp.Response.RequireUpload -> {
image.fileId = response.fileId.toInt()
}
}
}
bot.components[ImagePatcher].patchOfflineGroupImage(this, image)
}

/**
* Ensures server holds the cache
*/
private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage): OfflineGroupImage {
bot.network.run {
val response = ImgStore.GroupPicUp(
bot.client,
uin = bot.id,
groupCode = id,
md5 = image.md5,
size = image.size
).sendAndExpect()
return OfflineGroupImage(
imageId = image.imageId,
width = image.width,
height = image.height,
size = if (response is ImgStore.GroupPicUp.Response.FileExists) {
response.fileInfo.fileSize
} else {
image.size
},
imageType = image.imageType
).also { img ->
when (response) {
is ImgStore.GroupPicUp.Response.FileExists -> {
img.fileId = response.fileId.toInt()
}
is ImgStore.GroupPicUp.Response.RequireUpload -> {
img.fileId = response.fileId.toInt()
}
is ImgStore.GroupPicUp.Response.Failed -> {
img.fileId = 0
}
}
}
}
return bot.components[ImagePatcher].patchFriendImageToGroupImage(this, image)
}
}
}
2 changes: 2 additions & 0 deletions mirai-core/src/commonMain/kotlin/message/MultiMsgUploader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ internal open class MultiMsgUploader(
)
}

msgChain = handler.conversionMessageChain(msgChain)

var seq: Int = -1
var uid: Int = -1
msg.messageChain.sourceOrNull?.let { source ->
Expand Down
Loading

0 comments on commit d48ed30

Please sign in to comment.