|
@@ -11,28 +11,24 @@
|
|
|
|
|
|
package net.mamoe.mirai.utils
|
|
package net.mamoe.mirai.utils
|
|
|
|
|
|
-import kotlinx.atomicfu.atomic
|
|
|
|
import kotlinx.coroutines.CompletableDeferred
|
|
import kotlinx.coroutines.CompletableDeferred
|
|
import kotlinx.coroutines.Deferred
|
|
import kotlinx.coroutines.Deferred
|
|
|
|
+import kotlinx.io.core.Input
|
|
import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
|
|
import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
|
|
-import net.mamoe.mirai.Mirai
|
|
|
|
import net.mamoe.mirai.contact.Contact
|
|
import net.mamoe.mirai.contact.Contact
|
|
import net.mamoe.mirai.contact.Contact.Companion.sendImage
|
|
import net.mamoe.mirai.contact.Contact.Companion.sendImage
|
|
import net.mamoe.mirai.contact.Contact.Companion.uploadImage
|
|
import net.mamoe.mirai.contact.Contact.Companion.uploadImage
|
|
-import net.mamoe.mirai.contact.FileSupported
|
|
|
|
-import net.mamoe.mirai.contact.Group
|
|
|
|
import net.mamoe.mirai.internal.utils.*
|
|
import net.mamoe.mirai.internal.utils.*
|
|
import net.mamoe.mirai.message.MessageReceipt
|
|
import net.mamoe.mirai.message.MessageReceipt
|
|
-import net.mamoe.mirai.message.data.FileMessage
|
|
|
|
import net.mamoe.mirai.message.data.Image
|
|
import net.mamoe.mirai.message.data.Image
|
|
-import net.mamoe.mirai.message.data.sendTo
|
|
|
|
-import net.mamoe.mirai.utils.AbstractExternalResource.ResourceCleanCallback
|
|
|
|
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
|
|
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
|
|
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
|
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
|
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
|
|
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
|
|
-import java.io.*
|
|
|
|
import kotlin.contracts.InvocationKind
|
|
import kotlin.contracts.InvocationKind
|
|
import kotlin.contracts.contract
|
|
import kotlin.contracts.contract
|
|
|
|
+import kotlin.jvm.JvmName
|
|
|
|
+import kotlin.jvm.JvmOverloads
|
|
|
|
+import kotlin.jvm.JvmStatic
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -124,7 +120,7 @@ import kotlin.contracts.contract
|
|
*
|
|
*
|
|
* @see FileCacheStrategy
|
|
* @see FileCacheStrategy
|
|
*/
|
|
*/
|
|
-public interface ExternalResource : Closeable {
|
|
|
|
|
|
+public expect interface ExternalResource : Closeable {
|
|
|
|
|
|
/**
|
|
/**
|
|
* 是否在 _使用一次_ 后自动 [close].
|
|
* 是否在 _使用一次_ 后自动 [close].
|
|
@@ -136,8 +132,7 @@ public interface ExternalResource : Closeable {
|
|
* @since 2.8
|
|
* @since 2.8
|
|
*/
|
|
*/
|
|
@MiraiExperimentalApi
|
|
@MiraiExperimentalApi
|
|
- public val isAutoClose: Boolean
|
|
|
|
- get() = false
|
|
|
|
|
|
+ public open val isAutoClose: Boolean
|
|
|
|
|
|
/**
|
|
/**
|
|
* 文件内容 MD5. 16 bytes
|
|
* 文件内容 MD5. 16 bytes
|
|
@@ -148,11 +143,7 @@ public interface ExternalResource : Closeable {
|
|
* 文件内容 SHA1. 16 bytes
|
|
* 文件内容 SHA1. 16 bytes
|
|
* @since 2.5
|
|
* @since 2.5
|
|
*/
|
|
*/
|
|
- public val sha1: ByteArray
|
|
|
|
- get() =
|
|
|
|
- throw UnsupportedOperationException("ExternalResource.sha1 is not implemented by ${this::class.simpleName}")
|
|
|
|
- // 如果你要实现 [ExternalResource], 你也应该实现 [sha1].
|
|
|
|
- // 这里默认抛出 [UnsupportedOperationException] 是为了 (姑且) 兼容 2.5 以前的版本的实现.
|
|
|
|
|
|
+ public open val sha1: ByteArray
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -178,17 +169,17 @@ public interface ExternalResource : Closeable {
|
|
public val closed: Deferred<Unit>
|
|
public val closed: Deferred<Unit>
|
|
|
|
|
|
/**
|
|
/**
|
|
- * 打开 [InputStream]. 在返回的 [InputStream] 被 [关闭][InputStream.close] 前无法再次打开流.
|
|
|
|
|
|
+ * 打开 [Input]. 在返回的 [Input] 被 [关闭][Input.close] 前无法再次打开流.
|
|
*
|
|
*
|
|
* 关闭此流不会关闭 [ExternalResource].
|
|
* 关闭此流不会关闭 [ExternalResource].
|
|
* @throws IllegalStateException 当上一个流未关闭又尝试打开新的流时抛出
|
|
* @throws IllegalStateException 当上一个流未关闭又尝试打开新的流时抛出
|
|
|
|
+ *
|
|
|
|
+ * @since SINCE_NATIVE_TARGET
|
|
*/
|
|
*/
|
|
- public fun inputStream(): InputStream
|
|
|
|
|
|
+ public fun input(): Input
|
|
|
|
|
|
@MiraiInternalApi
|
|
@MiraiInternalApi
|
|
- public fun calculateResourceId(): String {
|
|
|
|
- return generateImageId(md5, formatName.ifEmpty { DEFAULT_FORMAT_NAME })
|
|
|
|
- }
|
|
|
|
|
|
+ public open fun calculateResourceId(): String
|
|
|
|
|
|
/**
|
|
/**
|
|
* 该 [ExternalResource] 的数据来源, 可能有以下的返回
|
|
* 该 [ExternalResource] 的数据来源, 可能有以下的返回
|
|
@@ -209,25 +200,14 @@ public interface ExternalResource : Closeable {
|
|
*
|
|
*
|
|
* @since 2.8.0
|
|
* @since 2.8.0
|
|
*/
|
|
*/
|
|
- public val origin: Any? get() = null
|
|
|
|
|
|
+ public open val origin: Any?
|
|
|
|
|
|
/**
|
|
/**
|
|
* 创建一个在 _使用一次_ 后就会自动 [close] 的 [ExternalResource].
|
|
* 创建一个在 _使用一次_ 后就会自动 [close] 的 [ExternalResource].
|
|
*
|
|
*
|
|
* @since 2.8.0
|
|
* @since 2.8.0
|
|
*/
|
|
*/
|
|
- public fun 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)"
|
|
|
|
- override fun toAutoCloseable(): ExternalResource {
|
|
|
|
- return this
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ public open fun toAutoCloseable(): ExternalResource
|
|
|
|
|
|
|
|
|
|
public companion object {
|
|
public companion object {
|
|
@@ -236,48 +216,12 @@ public interface ExternalResource : Closeable {
|
|
*
|
|
*
|
|
* @see ExternalResource.formatName
|
|
* @see ExternalResource.formatName
|
|
*/
|
|
*/
|
|
- public const val DEFAULT_FORMAT_NAME: String = "mirai"
|
|
|
|
|
|
+ public val DEFAULT_FORMAT_NAME: String
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// region toExternalResource
|
|
// region toExternalResource
|
|
///////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
- /**
|
|
|
|
- * **打开文件**并创建 [ExternalResource].
|
|
|
|
- * 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭.
|
|
|
|
- *
|
|
|
|
- * 将以只读模式打开这个文件 (因此文件会处于被占用状态), 直到 [ExternalResource.close].
|
|
|
|
- *
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmOverloads
|
|
|
|
- @JvmName("create")
|
|
|
|
- public fun File.toExternalResource(formatName: String? = null): ExternalResource =
|
|
|
|
- // although RandomAccessFile constructor throws IOException, actual performance influence is minor so not propagating IOException
|
|
|
|
- RandomAccessFile(this, "r").toExternalResource(formatName).also {
|
|
|
|
- it.cast<ExternalResourceImplByFile>().origin = this@toExternalResource
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 创建 [ExternalResource].
|
|
|
|
- * 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭, 届时将会关闭 [RandomAccessFile].
|
|
|
|
- *
|
|
|
|
- * **注意**:若关闭 [RandomAccessFile], 也会间接关闭 [ExternalResource].
|
|
|
|
- *
|
|
|
|
- * @see closeOriginalFileOnClose 若为 `true`, 在 [ExternalResource.close] 时将会同步关闭 [RandomAccessFile]. 否则不会.
|
|
|
|
- *
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmOverloads
|
|
|
|
- @JvmName("create")
|
|
|
|
- public fun RandomAccessFile.toExternalResource(
|
|
|
|
- formatName: String? = null,
|
|
|
|
- closeOriginalFileOnClose: Boolean = true,
|
|
|
|
- ): ExternalResource =
|
|
|
|
- ExternalResourceImplByFile(this, formatName, closeOriginalFileOnClose)
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* 创建 [ExternalResource]. 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭.
|
|
* 创建 [ExternalResource]. 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭.
|
|
*
|
|
*
|
|
@@ -286,67 +230,10 @@ public interface ExternalResource : Closeable {
|
|
@JvmStatic
|
|
@JvmStatic
|
|
@JvmOverloads
|
|
@JvmOverloads
|
|
@JvmName("create")
|
|
@JvmName("create")
|
|
- public fun ByteArray.toExternalResource(formatName: String? = null): ExternalResource =
|
|
|
|
- ExternalResourceImplByByteArray(this, formatName)
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 立即使用 [FileCacheStrategy] 缓存 [InputStream] 并创建 [ExternalResource].
|
|
|
|
- * 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭.
|
|
|
|
- *
|
|
|
|
- * **注意**:本函数不会关闭流.
|
|
|
|
- *
|
|
|
|
- * ### 在 Java 获得和使用 [ExternalResource] 实例
|
|
|
|
- *
|
|
|
|
- * ```
|
|
|
|
- * try(ExternalResource resource = ExternalResource.create(file)) { // 使用文件 file
|
|
|
|
- * contact.uploadImage(resource); // 用来上传图片
|
|
|
|
- * contact.files.uploadNewFile("/foo/test.txt", file); // 或者用来上传文件
|
|
|
|
- * }
|
|
|
|
- * ```
|
|
|
|
- *
|
|
|
|
- * 注意, 若使用 [InputStream], 必须手动关闭 [InputStream]. 一种使用情况示例:
|
|
|
|
- *
|
|
|
|
- * ```
|
|
|
|
- * try(InputStream stream = ...) {
|
|
|
|
- * try(ExternalResource resource = ExternalResource.create(stream)) {
|
|
|
|
- * contact.uploadImage(resource); // 用来上传图片
|
|
|
|
- * contact.files.uploadNewFile("/foo/test.txt", file); // 或者用来上传文件
|
|
|
|
- * }
|
|
|
|
- * }
|
|
|
|
- * ```
|
|
|
|
- *
|
|
|
|
- *
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- * @see ExternalResource
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmOverloads
|
|
|
|
- @JvmName("create")
|
|
|
|
- @Throws(IOException::class) // not in BIO context so propagate IOException
|
|
|
|
- public fun InputStream.toExternalResource(formatName: String? = null): ExternalResource =
|
|
|
|
- Mirai.FileCacheStrategy.newCache(this, formatName)
|
|
|
|
|
|
+ public fun ByteArray.toExternalResource(formatName: String? = null): ExternalResource
|
|
|
|
|
|
// endregion
|
|
// endregion
|
|
|
|
|
|
-
|
|
|
|
- /* note:
|
|
|
|
- 于 2.8.0-M1 添加 (#1392)
|
|
|
|
-
|
|
|
|
- 于 2.8.0-RC 移动至 `toExternalResource`(#1588)
|
|
|
|
- */
|
|
|
|
- @JvmName("createAutoCloseable")
|
|
|
|
- @JvmStatic
|
|
|
|
- @Deprecated(
|
|
|
|
- level = DeprecationLevel.HIDDEN,
|
|
|
|
- message = "Moved to `toExternalResource()`",
|
|
|
|
- replaceWith = ReplaceWith("resource.toAutoCloseable()"),
|
|
|
|
- )
|
|
|
|
- @DeprecatedSinceMirai(errorSince = "2.8", hiddenSince = "2.10")
|
|
|
|
- public fun createAutoCloseable(resource: ExternalResource): ExternalResource {
|
|
|
|
- return resource.toAutoCloseable()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// region sendAsImageTo
|
|
// region sendAsImageTo
|
|
///////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -364,43 +251,7 @@ public interface ExternalResource : Closeable {
|
|
@JvmBlockingBridge
|
|
@JvmBlockingBridge
|
|
@JvmStatic
|
|
@JvmStatic
|
|
@JvmName("sendAsImage")
|
|
@JvmName("sendAsImage")
|
|
- public suspend fun <C : Contact> ExternalResource.sendAsImageTo(contact: C): MessageReceipt<C> =
|
|
|
|
- contact.uploadImage(this).sendTo(contact)
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人.
|
|
|
|
- *
|
|
|
|
- * 注意:本函数不会关闭流.
|
|
|
|
- *
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- * @throws OverFileSizeMaxException
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmName("sendAsImage")
|
|
|
|
- @JvmOverloads
|
|
|
|
- public suspend fun <C : Contact> InputStream.sendAsImageTo(
|
|
|
|
- contact: C,
|
|
|
|
- formatName: String? = null,
|
|
|
|
- ): MessageReceipt<C> =
|
|
|
|
- runBIO {
|
|
|
|
- // toExternalResource throws IOException however we're in BIO context so not propagating IOException to sendAsImageTo
|
|
|
|
- toExternalResource(formatName)
|
|
|
|
- }.withUse { sendAsImageTo(contact) }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 将文件作为图片发送到指定联系人.
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- * @throws OverFileSizeMaxException
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmName("sendAsImage")
|
|
|
|
- @JvmOverloads
|
|
|
|
- public suspend fun <C : Contact> File.sendAsImageTo(contact: C, formatName: String? = null): MessageReceipt<C> {
|
|
|
|
- require(this.exists() && this.canRead())
|
|
|
|
- return toExternalResource(formatName).withUse { sendAsImageTo(contact) }
|
|
|
|
- }
|
|
|
|
|
|
+ public suspend fun <C : Contact> ExternalResource.sendAsImageTo(contact: C): MessageReceipt<C>
|
|
|
|
|
|
// endregion
|
|
// endregion
|
|
|
|
|
|
@@ -419,435 +270,9 @@ public interface ExternalResource : Closeable {
|
|
*/
|
|
*/
|
|
@JvmStatic
|
|
@JvmStatic
|
|
@JvmBlockingBridge
|
|
@JvmBlockingBridge
|
|
- public suspend fun ExternalResource.uploadAsImage(contact: Contact): Image = contact.uploadImage(this)
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image].
|
|
|
|
- *
|
|
|
|
- * 注意:本函数不会关闭流.
|
|
|
|
- *
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- * @throws OverFileSizeMaxException
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmOverloads
|
|
|
|
- public suspend fun InputStream.uploadAsImage(contact: Contact, formatName: String? = null): Image =
|
|
|
|
- // toExternalResource throws IOException however we're in BIO context so not propagating IOException to sendAsImageTo
|
|
|
|
- runBIO { toExternalResource(formatName) }.withUse { uploadAsImage(contact) }
|
|
|
|
-
|
|
|
|
- // endregion
|
|
|
|
-
|
|
|
|
- ///////////////////////////////////////////////////////////////////////////
|
|
|
|
- // region uploadAsFile
|
|
|
|
- ///////////////////////////////////////////////////////////////////////////
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 将文件作为图片上传后构造 [Image].
|
|
|
|
- *
|
|
|
|
- * @param formatName 查看 [ExternalResource.formatName]
|
|
|
|
- * @throws OverFileSizeMaxException
|
|
|
|
- */
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmOverloads
|
|
|
|
- public suspend fun File.uploadAsImage(contact: Contact, formatName: String? = null): Image =
|
|
|
|
- toExternalResource(formatName).withUse { uploadAsImage(contact) }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 上传文件并获取文件消息.
|
|
|
|
- *
|
|
|
|
- * 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
|
|
|
|
- *
|
|
|
|
- * 需要调用方手动[关闭资源][ExternalResource.close].
|
|
|
|
- *
|
|
|
|
- * ## 已弃用
|
|
|
|
- * 查看 [RemoteFile.upload] 获取更多信息.
|
|
|
|
- *
|
|
|
|
- * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt'
|
|
|
|
- * @since 2.5
|
|
|
|
- * @see RemoteFile.path
|
|
|
|
- * @see RemoteFile.upload
|
|
|
|
- */
|
|
|
|
- @Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmOverloads
|
|
|
|
- @Deprecated(
|
|
|
|
- "Use sendTo instead.",
|
|
|
|
- ReplaceWith(
|
|
|
|
- "this.sendTo(contact, path, callback)",
|
|
|
|
- "net.mamoe.mirai.utils.ExternalResource.Companion.sendTo"
|
|
|
|
- ),
|
|
|
|
- level = DeprecationLevel.HIDDEN
|
|
|
|
- ) // deprecated since 2.7-M1
|
|
|
|
- @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11")
|
|
|
|
- public suspend fun File.uploadTo(
|
|
|
|
- contact: FileSupported,
|
|
|
|
- path: String,
|
|
|
|
- callback: RemoteFile.ProgressionCallback? = null,
|
|
|
|
- ): FileMessage = toExternalResource().use {
|
|
|
|
- contact.filesRoot.resolve(path).upload(it, callback)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 上传文件并获取文件消息.
|
|
|
|
- *
|
|
|
|
- * 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
|
|
|
|
- *
|
|
|
|
- * 需要调用方手动[关闭资源][ExternalResource.close].
|
|
|
|
- *
|
|
|
|
- * ## 已弃用
|
|
|
|
- * 查看 [RemoteFile.upload] 获取更多信息.
|
|
|
|
- *
|
|
|
|
- * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt'
|
|
|
|
- * @since 2.5
|
|
|
|
- * @see RemoteFile.path
|
|
|
|
- * @see RemoteFile.upload
|
|
|
|
- */
|
|
|
|
- @Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmName("uploadAsFile")
|
|
|
|
- @JvmOverloads
|
|
|
|
- @Deprecated(
|
|
|
|
- "Use sendAsFileTo instead.",
|
|
|
|
- ReplaceWith(
|
|
|
|
- "this.sendAsFileTo(contact, path, callback)",
|
|
|
|
- "net.mamoe.mirai.utils.ExternalResource.Companion.sendAsFileTo"
|
|
|
|
- ),
|
|
|
|
- level = DeprecationLevel.HIDDEN
|
|
|
|
- ) // deprecated since 2.7-M1
|
|
|
|
- @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11")
|
|
|
|
- public suspend fun ExternalResource.uploadAsFile(
|
|
|
|
- contact: FileSupported,
|
|
|
|
- path: String,
|
|
|
|
- callback: RemoteFile.ProgressionCallback? = null,
|
|
|
|
- ): FileMessage {
|
|
|
|
- return contact.filesRoot.resolve(path).upload(this, callback)
|
|
|
|
- }
|
|
|
|
|
|
+ public suspend fun ExternalResource.uploadAsImage(contact: Contact): Image
|
|
|
|
|
|
// endregion
|
|
// endregion
|
|
-
|
|
|
|
- ///////////////////////////////////////////////////////////////////////////
|
|
|
|
- // region sendAsFileTo
|
|
|
|
- ///////////////////////////////////////////////////////////////////////////
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 上传文件并发送文件消息.
|
|
|
|
- *
|
|
|
|
- * 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
|
|
|
|
- *
|
|
|
|
- * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt'
|
|
|
|
- * @since 2.5
|
|
|
|
- * @see RemoteFile.path
|
|
|
|
- * @see RemoteFile.uploadAndSend
|
|
|
|
- */
|
|
|
|
- @Suppress("DEPRECATION_ERROR", "DEPRECATION")
|
|
|
|
- @Deprecated(
|
|
|
|
- "Deprecated. Please use AbsoluteFolder.uploadNewFile",
|
|
|
|
- ReplaceWith("contact.files.uploadNewFile(path, this, callback)"),
|
|
|
|
- level = DeprecationLevel.ERROR,
|
|
|
|
- ) // deprecated since 2.8.0-RC
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmOverloads
|
|
|
|
- @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.12")
|
|
|
|
- public suspend fun <C : FileSupported> File.sendTo(
|
|
|
|
- contact: C,
|
|
|
|
- path: String,
|
|
|
|
- callback: RemoteFile.ProgressionCallback? = null,
|
|
|
|
- ): MessageReceipt<C> = toExternalResource().use {
|
|
|
|
- contact.filesRoot.resolve(path).upload(it, callback).sendTo(contact)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 上传文件并发送件消息. 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型.
|
|
|
|
- *
|
|
|
|
- * 需要调用方手动[关闭资源][ExternalResource.close].
|
|
|
|
- *
|
|
|
|
- * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt'
|
|
|
|
- * @since 2.5
|
|
|
|
- * @see RemoteFile.path
|
|
|
|
- * @see RemoteFile.uploadAndSend
|
|
|
|
- */
|
|
|
|
- @Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
|
|
|
- @Deprecated(
|
|
|
|
- "Deprecated. Please use AbsoluteFolder.uploadNewFile",
|
|
|
|
- ReplaceWith("contact.files.uploadNewFile(path, this, callback)"),
|
|
|
|
- level = DeprecationLevel.ERROR,
|
|
|
|
- ) // deprecated since 2.8.0-RC
|
|
|
|
- @JvmStatic
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmName("sendAsFile")
|
|
|
|
- @JvmOverloads
|
|
|
|
- @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.12")
|
|
|
|
- public suspend fun <C : FileSupported> ExternalResource.sendAsFileTo(
|
|
|
|
- contact: C,
|
|
|
|
- path: String,
|
|
|
|
- callback: RemoteFile.ProgressionCallback? = null,
|
|
|
|
- ): MessageReceipt<C> {
|
|
|
|
- return contact.filesRoot.resolve(path).upload(this, callback).sendTo(contact)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // endregion
|
|
|
|
-
|
|
|
|
- ///////////////////////////////////////////////////////////////////////////
|
|
|
|
- // region uploadAsVoice
|
|
|
|
- ///////////////////////////////////////////////////////////////////////////
|
|
|
|
-
|
|
|
|
- @Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
|
|
|
- @JvmBlockingBridge
|
|
|
|
- @JvmStatic
|
|
|
|
- @Deprecated(
|
|
|
|
- "Use `contact.uploadAudio(resource)` instead",
|
|
|
|
- level = DeprecationLevel.HIDDEN
|
|
|
|
- ) // deprecated since 2.7
|
|
|
|
- @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11")
|
|
|
|
- public suspend fun ExternalResource.uploadAsVoice(contact: Contact): net.mamoe.mirai.message.data.Voice {
|
|
|
|
- @Suppress("DEPRECATION", "DEPRECATION_ERROR")
|
|
|
|
- if (contact is Group) return contact.uploadAudio(this)
|
|
|
|
- .let { net.mamoe.mirai.message.data.Voice.fromAudio(it) }
|
|
|
|
- else throw UnsupportedOperationException("Contact `$contact` is not supported uploading voice")
|
|
|
|
- }
|
|
|
|
- // endregion
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/**
|
|
|
|
- * 一个实现了基本方法的外部资源
|
|
|
|
- *
|
|
|
|
- * ## 实现
|
|
|
|
- *
|
|
|
|
- * [AbstractExternalResource] 实现了大部分必要的方法,
|
|
|
|
- * 只有 [ExternalResource.inputStream], [ExternalResource.size] 还未实现
|
|
|
|
- *
|
|
|
|
- * 其中 [ExternalResource.inputStream] 要求每次读取的内容都是一致的
|
|
|
|
- *
|
|
|
|
- * Example:
|
|
|
|
- * ```
|
|
|
|
- * class MyCustomExternalResource: AbstractExternalResource() {
|
|
|
|
- * override fun inputStream0(): InputStream = FileInputStream("/test.txt")
|
|
|
|
- * override val size: Long get() = File("/test.txt").length()
|
|
|
|
- * }
|
|
|
|
- * ```
|
|
|
|
- *
|
|
|
|
- * ## 资源释放
|
|
|
|
- *
|
|
|
|
- * 如同 mirai 内置的 [ExternalResource] 实现一样,
|
|
|
|
- * [AbstractExternalResource] 也会被注册进入资源泄露监视器
|
|
|
|
- * (即意味着 [AbstractExternalResource] 也要求手动关闭)
|
|
|
|
- *
|
|
|
|
- * 为了确保逻辑正确性, [AbstractExternalResource] 不允许覆盖其 [close] 方法,
|
|
|
|
- * 必须在构造 [AbstractExternalResource] 的时候给定一个 [ResourceCleanCallback] 以进行资源释放
|
|
|
|
- *
|
|
|
|
- * 对于 [ResourceCleanCallback], 有以下要求
|
|
|
|
- *
|
|
|
|
- * - 没有对 [AbstractExternalResource] 的访问 (即没有 [AbstractExternalResource] 的任何引用)
|
|
|
|
- *
|
|
|
|
- * Example:
|
|
|
|
- * ```
|
|
|
|
- * class MyRes(
|
|
|
|
- * cleanup: ResourceCleanCallback,
|
|
|
|
- * val delegate: Closable,
|
|
|
|
- * ): AbstractExternalResource(cleanup) {
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- * // 错误, 该写法会导致 Resource 永远也不会被自动释放
|
|
|
|
- * lateinit var myRes: MyRes
|
|
|
|
- * val cleanup = ResourceCleanCallback {
|
|
|
|
- * myRes.delegate.close()
|
|
|
|
- * }
|
|
|
|
- * myRes = MyRes(cleanup, fetchDelegate())
|
|
|
|
- *
|
|
|
|
- * // 正确
|
|
|
|
- * val delegate: Closable
|
|
|
|
- * val cleanup = ResourceCleanCallback {
|
|
|
|
- * delegate.close()
|
|
|
|
- * }
|
|
|
|
- * val myRes = MyRes(cleanup, delegate)
|
|
|
|
- * ```
|
|
|
|
- *
|
|
|
|
- * @since 2.9
|
|
|
|
- *
|
|
|
|
- * @see ExternalResource
|
|
|
|
- * @see AbstractExternalResource.setResourceCleanCallback
|
|
|
|
- * @see AbstractExternalResource.registerToLeakObserver
|
|
|
|
- */
|
|
|
|
-@Suppress("MemberVisibilityCanBePrivate")
|
|
|
|
-public abstract class AbstractExternalResource
|
|
|
|
-@JvmOverloads
|
|
|
|
-public constructor(
|
|
|
|
- displayName: String? = null,
|
|
|
|
- cleanup: ResourceCleanCallback? = null,
|
|
|
|
-) : ExternalResource {
|
|
|
|
-
|
|
|
|
- public constructor(
|
|
|
|
- cleanup: ResourceCleanCallback? = null,
|
|
|
|
- ) : this(null, cleanup)
|
|
|
|
-
|
|
|
|
- public fun interface ResourceCleanCallback {
|
|
|
|
- @Throws(IOException::class)
|
|
|
|
- public fun cleanup()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- override val md5: ByteArray by lazy { inputStream().md5() }
|
|
|
|
- override val sha1: ByteArray by lazy { inputStream().sha1() }
|
|
|
|
- override val formatName: String by lazy {
|
|
|
|
- inputStream().detectFileTypeAndClose() ?: ExternalResource.DEFAULT_FORMAT_NAME
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private val leakObserverRegistered = atomic(false)
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 注册 [ExternalResource] 资源泄露监视器
|
|
|
|
- *
|
|
|
|
- * 受限于类继承构造器调用顺序, [AbstractExternalResource] 无法做到在完成初始化后马上注册监视器
|
|
|
|
- *
|
|
|
|
- * 该方法以允许 实现类 在完成初始化后直接注册资源监视器以避免意外的资源泄露
|
|
|
|
- *
|
|
|
|
- * 在不调用本方法的前提下, 如果没有相关的资源访问操作, `this` 可能会被意外泄露
|
|
|
|
- *
|
|
|
|
- * 正确示例:
|
|
|
|
- * ```
|
|
|
|
- * // Kotlin
|
|
|
|
- * public class MyResource: AbstractExternalResource() {
|
|
|
|
- * init {
|
|
|
|
- * val res: SomeResource
|
|
|
|
- * // 一些资源初始化
|
|
|
|
- * registerToLeakObserver()
|
|
|
|
- * setResourceCleanCallback(Releaser(res))
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- * private class Releaser(
|
|
|
|
- * private val res: SomeResource,
|
|
|
|
- * ) : AbstractExternalResource.ResourceCleanCallback {
|
|
|
|
- * override fun cleanup() = res.close()
|
|
|
|
- * }
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- * // Java
|
|
|
|
- * public class MyResource extends AbstractExternalResource {
|
|
|
|
- * public MyResource() throws IOException {
|
|
|
|
- * SomeResource res;
|
|
|
|
- * // 一些资源初始化
|
|
|
|
- * registerToLeakObserver();
|
|
|
|
- * setResourceCleanCallback(new Releaser(res));
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- * private static class Releaser implements ResourceCleanCallback {
|
|
|
|
- * private final SomeResource res;
|
|
|
|
- * Releaser(SomeResource res) { this.res = res; }
|
|
|
|
- *
|
|
|
|
- * public void cleanup() throws IOException { res.close(); }
|
|
|
|
- * }
|
|
|
|
- * }
|
|
|
|
- * ```
|
|
|
|
- *
|
|
|
|
- * @see setResourceCleanCallback
|
|
|
|
- */
|
|
|
|
- protected fun registerToLeakObserver() {
|
|
|
|
- // 用户自定义 AbstractExternalResource 也许会在 <init> 的时候失败
|
|
|
|
- // 于是在第一次使用 ExternalResource 相关的函数的时候注册 LeakObserver
|
|
|
|
- if (leakObserverRegistered.compareAndSet(expect = false, update = true)) {
|
|
|
|
- ExternalResourceLeakObserver.register(this, holder)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 该方法用于告知 [AbstractExternalResource] 不需要注册资源泄露监视器。
|
|
|
|
- * **仅在我知道我在干什么的前提下调用此方法**
|
|
|
|
- *
|
|
|
|
- * 不建议取消注册监视器, 这可能带来意外的错误
|
|
|
|
- *
|
|
|
|
- * @see registerToLeakObserver
|
|
|
|
- */
|
|
|
|
- protected fun dontRegisterLeakObserver() {
|
|
|
|
- leakObserverRegistered.value = true
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- final override fun inputStream(): InputStream {
|
|
|
|
- registerToLeakObserver()
|
|
|
|
- return inputStream0()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- protected abstract fun inputStream0(): InputStream
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * 修改 `this` 的资源释放回调。
|
|
|
|
- * **仅在我知道我在干什么的前提下调用此方法**
|
|
|
|
- *
|
|
|
|
- * ```
|
|
|
|
- * class MyRes {
|
|
|
|
- * // region kotlin
|
|
|
|
- *
|
|
|
|
- * private inner class Releaser : ResourceCleanCallback
|
|
|
|
- *
|
|
|
|
- * private class NotInnerReleaser : ResourceCleanCallback
|
|
|
|
- *
|
|
|
|
- * init {
|
|
|
|
- * // 错误, 内部类, Releaser 存在对 MyRes 的引用
|
|
|
|
- * setResourceCleanCallback(Releaser())
|
|
|
|
- * // 错误, 匿名对象, 可能存在对 MyRes 的引用, 取决于编译器
|
|
|
|
- * setResourceCleanCallback(object : ResourceCleanCallback {})
|
|
|
|
- * // 正确, 无 inner 修饰, 等同于 java 的 private static class
|
|
|
|
- * setResourceCleanCallback(NotInnerReleaser(directResource))
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- * // endregion kotlin
|
|
|
|
- *
|
|
|
|
- * // region java
|
|
|
|
- *
|
|
|
|
- * private class Releaser implements ResourceCleanCallback {}
|
|
|
|
- * private static class StaticReleaser implements ResourceCleanCallback {}
|
|
|
|
- *
|
|
|
|
- * MyRes() {
|
|
|
|
- * // 错误, 内部类, 存在对 MyRes 的引用
|
|
|
|
- * setResourceCleanCallback(new Releaser());
|
|
|
|
- * // 错误, 匿名对象, 可能存在对 MyRes 的引用, 取决于 javac
|
|
|
|
- * setResourceCleanCallback(new ResourceCleanCallback() {});
|
|
|
|
- * // 正确
|
|
|
|
- * setResourceCleanCallback(new StaticReleaser(directResource));
|
|
|
|
- * }
|
|
|
|
- *
|
|
|
|
- * // endregion java
|
|
|
|
- * }
|
|
|
|
- * ```
|
|
|
|
- *
|
|
|
|
- * @see registerToLeakObserver
|
|
|
|
- */
|
|
|
|
- protected fun setResourceCleanCallback(cleanup: ResourceCleanCallback?) {
|
|
|
|
- holder.cleanup = cleanup
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private class UsrCustomResHolder(
|
|
|
|
- @JvmField var cleanup: ResourceCleanCallback?,
|
|
|
|
- private val resourceName: String,
|
|
|
|
- ) : ExternalResourceHolder() {
|
|
|
|
-
|
|
|
|
- override val closed: Deferred<Unit> = CompletableDeferred()
|
|
|
|
-
|
|
|
|
- override fun closeImpl() {
|
|
|
|
- cleanup?.cleanup()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // display on logger of ExternalResourceLeakObserver
|
|
|
|
- override fun toString(): String = resourceName
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private val holder = UsrCustomResHolder(cleanup, displayName ?: buildString {
|
|
|
|
- append("ExternalResourceHolder<")
|
|
|
|
- append(this@AbstractExternalResource.javaClass.name)
|
|
|
|
- append('@')
|
|
|
|
- append(System.identityHashCode(this@AbstractExternalResource))
|
|
|
|
- append('>')
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- final override val closed: Deferred<Unit> get() = holder.closed.also { registerToLeakObserver() }
|
|
|
|
-
|
|
|
|
- @Throws(IOException::class)
|
|
|
|
- final override fun close() {
|
|
|
|
- holder.close()
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|