Prechádzať zdrojové kódy

Image download support, close #49

Him188 6 rokov pred
rodič
commit
692e7c950c

+ 18 - 10
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt

@@ -9,7 +9,9 @@
 
 package net.mamoe.mirai.qqandroid
 
-import kotlinx.io.core.ByteReadPacket
+import io.ktor.client.request.get
+import io.ktor.client.statement.HttpResponse
+import io.ktor.utils.io.ByteReadChannel
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.BotImpl
 import net.mamoe.mirai.contact.*
@@ -17,10 +19,9 @@ import net.mamoe.mirai.data.AddFriendResult
 import net.mamoe.mirai.data.FriendInfo
 import net.mamoe.mirai.data.GroupInfo
 import net.mamoe.mirai.data.MemberInfo
-import net.mamoe.mirai.message.data.Image
-import net.mamoe.mirai.message.data.MessageSource
-import net.mamoe.mirai.message.data.messageRandom
-import net.mamoe.mirai.message.data.sequenceId
+import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.qqandroid.message.CustomFaceFromServer
+import net.mamoe.mirai.qqandroid.message.NotOnlineImageFromServer
 import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
 import net.mamoe.mirai.qqandroid.network.QQAndroidClient
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
@@ -150,13 +151,20 @@ internal abstract class QQAndroidBotBase constructor(
         }
     }
 
-    override suspend fun Image.download(): ByteReadPacket {
-        TODO("not implemented")
+    override suspend fun Image.url(): String = "http://gchat.qpic.cn" + when (this) {
+        is NotOnlineImageFromServer -> this.delegate.origUrl
+        is CustomFaceFromServer -> this.delegate.origUrl
+        is CustomFaceFromFile -> {
+            TODO()
+        }
+        is NotOnlineImageFromFile -> {
+            TODO()
+        }
+        else -> error("unsupported image class: ${this::class.simpleName}")
     }
 
-    @Suppress("OverridingDeprecatedMember")
-    override suspend fun Image.downloadAsByteArray(): ByteArray {
-        TODO("not implemented")
+    override suspend fun Image.channel(): ByteReadChannel {
+        return Http.get<HttpResponse>(url()).content
     }
 
     override suspend fun approveFriendAddRequest(id: Long, remark: String?) {

+ 13 - 26
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt

@@ -11,14 +11,11 @@
 
 package net.mamoe.mirai
 
+import io.ktor.utils.io.ByteReadChannel
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
-import kotlinx.io.OutputStream
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.IoBuffer
-import kotlinx.io.core.use
 import net.mamoe.mirai.contact.*
 import net.mamoe.mirai.data.AddFriendResult
 import net.mamoe.mirai.data.FriendInfo
@@ -30,7 +27,6 @@ import net.mamoe.mirai.message.data.MessageSource
 import net.mamoe.mirai.network.BotNetworkHandler
 import net.mamoe.mirai.network.LoginFailedException
 import net.mamoe.mirai.utils.*
-import net.mamoe.mirai.utils.io.transferTo
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.jvm.JvmStatic
@@ -226,11 +222,12 @@ abstract class Bot : CoroutineScope {
     abstract suspend fun recall(source: MessageSource)
 
     /**
-     * 撤回这条消息.
+     * 撤回一条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
      *
      * [Bot] 撤回自己的消息不需要权限.
      * [Bot] 撤回群员的消息需要管理员权限.
      *
+     * @param senderId 这条消息的发送人. 可以为 [Bot.uin] 或 [Member.id]
      * @param messageId 即 [MessageSource.id]
      *
      * @throws PermissionDeniedException 当 [Bot] 无权限操作时
@@ -239,15 +236,18 @@ abstract class Bot : CoroutineScope {
      */
     abstract suspend fun recall(groupId: Long, senderId: Long, messageId: Long)
 
-    @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING)
-    @MiraiExperimentalAPI("未支持")
-    abstract suspend fun Image.downloadAsByteArray(): ByteArray
+    /**
+     * 获取图片下载链接
+     */
+    abstract suspend fun Image.url(): String
 
     /**
-     * 将图片下载到内存中 (使用 [IoBuffer.Pool])
+     * 获取图片下载链接并开始下载.
+     *
+     * @see ByteReadChannel.copyAndClose
+     * @see ByteReadChannel.copyTo
      */
-    @MiraiExperimentalAPI("未支持")
-    abstract suspend fun Image.download(): ByteReadPacket
+    abstract suspend fun Image.channel(): ByteReadChannel
 
     /**
      * 添加一个好友
@@ -278,20 +278,7 @@ abstract class Bot : CoroutineScope {
      */
     abstract fun close(cause: Throwable? = null)
 
-    // region extensions
-
-    final override fun toString(): String {
-        return "Bot(${uin})"
-    }
-
-    /**
-     * 需要调用者自行 close [output]
-     */
-    @MiraiExperimentalAPI("未支持")
-    suspend inline fun Image.downloadTo(output: OutputStream) =
-        download().use { input -> input.transferTo(output) }
-
-    // endregion
+    final override fun toString(): String = "Bot(${uin})"
 }
 
 /**

+ 11 - 11
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt

@@ -11,9 +11,7 @@
 
 package net.mamoe.mirai.message
 
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.IoBuffer
-import kotlinx.io.core.readBytes
+import io.ktor.utils.io.ByteReadChannel
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.Group
@@ -128,20 +126,22 @@ abstract class MessagePacketBase<TSender : QQ, TSubject : Contact> : Packet, Bot
     // endregion
 
     // region 下载图片
+
+
     /**
-     * 将图片下载到内存.
+     * 获取图片下载链接
      *
-     * 非常不推荐这样做.
+     * @return "http://gchat.qpic.cn/gchatpic_new/..."
      */
-    @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING)
-    suspend inline fun Image.downloadAsByteArray(): ByteArray = bot.run { download().readBytes() }
-
-    // TODO: 2020/2/5 为下载图片添加文件系统的存储方式
+    suspend inline fun Image.url(): String = bot.run { url() }
 
     /**
-     * 将图片下载到内存缓存中 (使用 [IoBuffer.Pool])
+     * 获取图片下载链接并开始下载.
+     *
+     * @see ByteReadChannel.copyAndClose
+     * @see ByteReadChannel.copyTo
      */
-    suspend inline fun Image.download(): ByteReadPacket = bot.run { download() }
+    suspend inline fun Image.channel(): ByteReadChannel = bot.run { channel() }
     // endregion
 }
 

+ 10 - 7
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt

@@ -16,7 +16,6 @@ package net.mamoe.mirai.utils
 
 import io.ktor.utils.io.ByteReadChannel
 import io.ktor.utils.io.readAvailable
-import kotlinx.coroutines.io.close
 import kotlinx.io.OutputStream
 import kotlinx.io.core.Output
 import kotlinx.io.pool.useInstance
@@ -24,6 +23,7 @@ import net.mamoe.mirai.utils.io.ByteArrayPool
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 
+// copyTo
 
 /**
  * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
@@ -49,6 +49,8 @@ suspend fun ByteReadChannel.copyTo(dst: Output) {
     }
 }
 
+
+/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel
 /**
  * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst]
  */
@@ -60,7 +62,7 @@ suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel)
         } while (size != 0)
     }
 }
-
+*/
 
 // copyAndClose
 
@@ -97,18 +99,19 @@ suspend fun ByteReadChannel.copyAndClose(dst: Output) {
     }
 }
 
+/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel
 /**
  * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst]
  */
 suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) {
-    try {
+    dst.close(kotlin.runCatching {
         ByteArrayPool.useInstance {
             do {
                 val size = this.readAvailable(it)
                 dst.writeFully(it, 0, size)
             } while (size != 0)
         }
-    } finally {
-        dst.close()
-    }
-}
+    }.exceptionOrNull())
+}
+
+ */

+ 14 - 12
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt

@@ -11,23 +11,19 @@
 
 package net.mamoe.mirai.message
 
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
 import kotlinx.io.core.Input
 import kotlinx.io.core.use
-import kotlinx.io.streams.inputStream
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.contact.QQ
 import net.mamoe.mirai.message.data.Image
-import net.mamoe.mirai.utils.ExternalImage
 import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.toExternalImage
+import net.mamoe.mirai.utils.copyAndClose
+import net.mamoe.mirai.utils.copyTo
 import java.awt.image.BufferedImage
 import java.io.File
 import java.io.InputStream
 import java.io.OutputStream
 import java.net.URL
-import javax.imageio.ImageIO
 
 /**
  * 一条从服务器接收到的消息事件.
@@ -72,16 +68,22 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con
     // endregion 发送图片 (扩展)
 
     // region 下载图片 (扩展)
-    suspend inline fun Image.downloadTo(file: File): Long = file.outputStream().use { downloadTo(it) }
+    suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) }
 
     /**
-     * 这个函数结束后不会关闭 [output]. 请务必解决好 [OutputStream.close]
+     * 下载图片到 [output] 但不关闭这个 [output]
      */
-    suspend inline fun Image.downloadTo(output: OutputStream): Long =
-        download().inputStream().use { input -> withContext(Dispatchers.IO) { input.copyTo(output) } }
+    suspend inline fun Image.downloadTo(output: OutputStream) = channel().copyTo(output)
 
-    suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream()
-    suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { download().toExternalImage() }
+    /**
+     * 下载图片到 [output] 并关闭这个 [output]
+     */
+    suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output)
+
+    /*
+    suspend inline fun Image.downloadAsStream(): InputStream = channel().asInputStream()
+    suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() }
     suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(Dispatchers.IO) { ImageIO.read(downloadAsStream()) }
+     */
     // endregion
 }

+ 6 - 9
mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java

@@ -1,6 +1,5 @@
 package net.mamoe.mirai.japt;
 
-import kotlinx.io.core.ByteReadPacket;
 import net.mamoe.mirai.Bot;
 import net.mamoe.mirai.BotAccount;
 import net.mamoe.mirai.BotFactoryJvmKt;
@@ -153,18 +152,16 @@ public interface BlockingBot {
 
     // region actions
 
-    @NotNull
-    byte[] downloadAsByteArray(@NotNull Image image);
-
-    @NotNull
-    ByteReadPacket download(@NotNull Image image);
-
     /**
      * 下载图片到 {@code outputStream}.
-     * <p>
      * 不会自动关闭 {@code outputStream}
      */
-    void download(@NotNull Image image, @NotNull OutputStream outputStream);
+    void downloadTo(@NotNull Image image, @NotNull OutputStream outputStream);
+
+    /**
+     * 下载图片到 {@code outputStream} 并关闭 stream
+     */
+    void downloadAndClose(@NotNull Image image, @NotNull OutputStream outputStream);
 
     /**
      * 添加一个好友

+ 3 - 10
mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt

@@ -10,8 +10,6 @@
 package net.mamoe.mirai.japt.internal
 
 import kotlinx.coroutines.runBlocking
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.BotAccount
 import net.mamoe.mirai.contact.QQ
@@ -23,10 +21,7 @@ import net.mamoe.mirai.japt.BlockingGroup
 import net.mamoe.mirai.japt.BlockingQQ
 import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.network.BotNetworkHandler
-import net.mamoe.mirai.utils.MiraiExperimentalAPI
-import net.mamoe.mirai.utils.MiraiInternalAPI
-import net.mamoe.mirai.utils.MiraiLogger
-import net.mamoe.mirai.utils.toList
+import net.mamoe.mirai.utils.*
 import java.io.OutputStream
 import java.util.stream.Stream
 import kotlin.streams.asStream
@@ -59,10 +54,8 @@ internal class BlockingBotImpl(private val bot: Bot) : BlockingBot {
     override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() }
     override fun getNetwork(): BotNetworkHandler = bot.network
     override fun login() = runBlocking { bot.login() }
-    override fun downloadAsByteArray(image: Image): ByteArray = bot.run { runBlocking { image.download().readBytes() } }
-    override fun download(image: Image): ByteReadPacket = bot.run { runBlocking { image.download() } }
-    override fun download(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.downloadTo(outputStream) } }
-
+    override fun downloadTo(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyTo(outputStream) } }
+    override fun downloadAndClose(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.channel().copyAndClose(outputStream) } }
     override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) }
     override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) }
     override fun close(throwable: Throwable?) = bot.close(throwable)