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 16, 2021
1 parent b5d8c70 commit 2422aa3
Show file tree
Hide file tree
Showing 9 changed files with 443 additions and 62 deletions.
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()
}
}
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.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
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

0 comments on commit 2422aa3

Please sign in to comment.