diff --git a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api index 4cfb21fd5..d3a1e4787 100644 --- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api +++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api @@ -5625,12 +5625,14 @@ public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io public static fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([B)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([BLjava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; + public static fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getClosed ()Lkotlinx/coroutines/Deferred; public abstract fun getFormatName ()Ljava/lang/String; public abstract fun getMd5 ()[B public fun getSha1 ()[B public abstract fun getSize ()J public abstract fun inputStream ()Ljava/io/InputStream; + public fun isAutoClose ()Z public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; @@ -5686,6 +5688,7 @@ public final class net/mamoe/mirai/utils/ExternalResource$Companion { public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/RandomAccessFile;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;[BLjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; + public final fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; @@ -5742,6 +5745,11 @@ public final class net/mamoe/mirai/utils/ExternalResource$Companion { public static synthetic fun uploadTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } +public final class net/mamoe/mirai/utils/ExternalResourceKt { + public static final fun runAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun useAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + public abstract interface class net/mamoe/mirai/utils/FileCacheStrategy { public static final field Companion Lnet/mamoe/mirai/utils/FileCacheStrategy$Companion; public static fun getPlatformDefault ()Lnet/mamoe/mirai/utils/FileCacheStrategy; diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api index 162f2073d..6e34ab174 100644 --- a/binary-compatibility-validator/api/binary-compatibility-validator.api +++ b/binary-compatibility-validator/api/binary-compatibility-validator.api @@ -5625,12 +5625,14 @@ public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io public static fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([B)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([BLjava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; + public static fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getClosed ()Lkotlinx/coroutines/Deferred; public abstract fun getFormatName ()Ljava/lang/String; public abstract fun getMd5 ()[B public fun getSha1 ()[B public abstract fun getSize ()J public abstract fun inputStream ()Ljava/io/InputStream; + public fun isAutoClose ()Z public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; @@ -5686,6 +5688,7 @@ public final class net/mamoe/mirai/utils/ExternalResource$Companion { public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/RandomAccessFile;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;[BLjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; + public final fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; @@ -5742,6 +5745,11 @@ public final class net/mamoe/mirai/utils/ExternalResource$Companion { public static synthetic fun uploadTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } +public final class net/mamoe/mirai/utils/ExternalResourceKt { + public static final fun runAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun useAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +} + public abstract interface class net/mamoe/mirai/utils/FileCacheStrategy { public static final field Companion Lnet/mamoe/mirai/utils/FileCacheStrategy$Companion; public static fun getPlatformDefault ()Lnet/mamoe/mirai/utils/FileCacheStrategy; diff --git a/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt b/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt index 56f8ac3cd..52b9a27d3 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt @@ -32,6 +32,8 @@ import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage import net.mamoe.mirai.utils.RemoteFile.Companion.sendFile import net.mamoe.mirai.utils.RemoteFile.Companion.uploadFile import java.io.* +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** @@ -67,6 +69,19 @@ import java.io.* */ public interface ExternalResource : Closeable { + /** + * 是否在 _使用一次_ 后自动 [close]. + * + * 该属性仅供调用方参考. 如 [Contact.uploadImage] 会在方法结束时关闭 [isAutoClose] 为 `true` 的 [ExternalResource], 无论上传图片是否成功. + * + * 所有 mirai 内置的上传图片, 上传语音等方法都支持该行为. + * + * @since 2.8 + */ + @MiraiExperimentalApi + public val isAutoClose: Boolean + get() = false + /** * 文件内容 MD5. 16 bytes */ @@ -188,6 +203,23 @@ public interface ExternalResource : Closeable { public fun InputStream.toExternalResource(formatName: String? = null): ExternalResource = Mirai.FileCacheStrategy.newCache(this, formatName) + /** + * 创建一个在 _使用一次_ 后就会自动 [close] 的 [ExternalResource]. + * + * @since 2.8 + */ + @JvmName("createAutoCloseable") + @JvmStatic + public fun ExternalResource.toAutoCloseable(): ExternalResource { + return if (isAutoClose) this else { + val delegate = this + object : ExternalResource by delegate { + override val isAutoClose: Boolean get() = true + override fun toString(): String = "ExternalResourceWithAutoClose(delegate=$delegate)" + } + } + } + // endregion /////////////////////////////////////////////////////////////////////////// @@ -427,3 +459,41 @@ public interface ExternalResource : Closeable { // endregion } } + +/** + * 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close]. + * + * @since 2.8 + */ +@MiraiExperimentalApi +// Continuing mark it as experimental until Kotlin's contextual receivers design is published. +// We might be able to make `action` a type `context(ExternalResource) () -> R`. +public inline fun T.withAutoClose(action: () -> R): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + trySafely( + block = { return action() }, + finally = { if (isAutoClose) close() } + ) +} + +/** + * 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close]. + * + * @since 2.8 + */ +@MiraiExperimentalApi +public inline fun T.runAutoClose(action: T.() -> R): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return withAutoClose { action() } +} + +/** + * 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close]. + * + * @since 2.8 + */ +@MiraiExperimentalApi +public inline fun T.useAutoClose(action: (resource: T) -> R): R { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + return runAutoClose(action) +} diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index a47648cae..6cc920741 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -1,10 +1,10 @@ /* * 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ package net.mamoe.mirai.internal.contact @@ -73,7 +73,7 @@ internal sealed class AbstractUser( open val info: UserInfo = userInfo @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - override suspend fun uploadImage(resource: ExternalResource): Image { + override suspend fun uploadImage(resource: ExternalResource): Image = resource.withAutoClose { if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index d06fe3d29..352421dca 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -1,10 +1,10 @@ /* * 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:OptIn(LowLevelApi::class) @@ -28,15 +28,13 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList +import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.OfflineAudio -import net.mamoe.mirai.utils.ExternalResource -import net.mamoe.mirai.utils.recoverCatchingSuppressed -import net.mamoe.mirai.utils.toByteArray -import net.mamoe.mirai.utils.toUHexString +import net.mamoe.mirai.utils.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext @@ -83,7 +81,7 @@ internal class FriendImpl( override fun toString(): String = "Friend($id)" - override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = bot.network.run { + override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.withAutoClose { var audio: OfflineAudioImpl? = null kotlin.runCatching { val resp = Highway.uploadResourceBdh( @@ -115,7 +113,7 @@ internal class FriendImpl( ) ) }.recoverCatchingSuppressed { - when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()) { + when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect(bot)) { is PttStore.GroupPttUp.Response.RequireUpload -> { tryServersUpload( bot, diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 50dc785fe..1a0cedc5a 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -166,7 +166,7 @@ internal class GroupImpl constructor( } @OptIn(ExperimentalTime::class) - override suspend fun uploadImage(resource: ExternalResource): Image { + override suspend fun uploadImage(resource: ExternalResource): Image = resource.withAutoClose { if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } @@ -217,7 +217,7 @@ internal class GroupImpl constructor( } @Suppress("OverridingDeprecatedMember", "DEPRECATION") - override suspend fun uploadVoice(resource: ExternalResource): Voice { + override suspend fun uploadVoice(resource: ExternalResource): Voice = resource.withAutoClose { return bot.network.run { uploadAudioResource(resource) @@ -262,7 +262,7 @@ internal class GroupImpl constructor( }.getOrThrow() } - override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio { + override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.withAutoClose { return bot.network.run { uploadAudioResource(resource) diff --git a/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt b/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt index 8a0d19115..7be76f52e 100644 --- a/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt +++ b/mirai-core/src/commonMain/kotlin/utils/RemoteFileImpl.kt @@ -1,10 +1,10 @@ /* * 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ package net.mamoe.mirai.internal.utils @@ -440,7 +440,7 @@ internal class RemoteFileImpl( private suspend fun upload0( resource: ExternalResource, callback: RemoteFile.ProgressionCallback?, - ): Oidb0x6d6.UploadFileRspBody? { + ): Oidb0x6d6.UploadFileRspBody? = resource.withAutoClose { val parent = parent ?: return null val parentInfo = parent.getFileFolderInfo() ?: return null val resp = FileManagement.RequestUpload(