feat: 支持任务重试和取消操作.

This commit is contained in:
LamGC 2024-01-23 01:01:17 +08:00
parent 4429997989
commit b8f7340d5f
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
3 changed files with 148 additions and 15 deletions

View File

@ -9,11 +9,14 @@ import com.microsoft.graph.models.DriveItemUploadableProperties
import com.microsoft.graph.requests.GraphServiceClient import com.microsoft.graph.requests.GraphServiceClient
import com.microsoft.graph.tasks.IProgressCallback import com.microsoft.graph.tasks.IProgressCallback
import com.microsoft.graph.tasks.LargeFileUploadTask import com.microsoft.graph.tasks.LargeFileUploadTask
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging import mu.KotlinLogging
import okhttp3.Request import okhttp3.Request
import org.telegram.abilitybots.api.bot.BaseAbilityBot import org.telegram.abilitybots.api.bot.BaseAbilityBot
import org.telegram.telegrambots.meta.api.methods.GetFile import org.telegram.telegrambots.meta.api.methods.GetFile
import org.telegram.telegrambots.meta.api.methods.send.SendMessage import org.telegram.telegrambots.meta.api.methods.send.SendMessage
import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageText import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageText
import org.telegram.telegrambots.meta.api.objects.Document import org.telegram.telegrambots.meta.api.objects.Document
import org.telegram.telegrambots.meta.exceptions.TelegramApiException import org.telegram.telegrambots.meta.exceptions.TelegramApiException
@ -32,6 +35,8 @@ object OneDriveTransferCenter {
executor.submitTransferTask(task) executor.submitTransferTask(task)
} }
fun cancelUploadTask(task: OneDriveTransferTask): Boolean = executor.cancelTransferTask(task)
fun getQueueingTaskCount(): Int = executor.taskQueue.size fun getQueueingTaskCount(): Int = executor.taskQueue.size
fun getProcessingTasks(): Map<Int, OneDriveTransferWorkerProgress> = fun getProcessingTasks(): Map<Int, OneDriveTransferWorkerProgress> =
@ -42,18 +47,46 @@ object OneDriveTransferCenter {
} }
object DefaultOneDriveTransferCallback : OneDriveTransferCallback { object DefaultOneDriveTransferCallback : OneDriveTransferCallback {
private val logger = KotlinLogging.logger { }
override fun onTransferTaskCreated(task: OneDriveTransferTask) { override fun onTransferTaskCreated(task: OneDriveTransferTask) {
if (task.extra["infoMessageId"] != null) {
val deleteMessage = DeleteMessage.builder()
.chatId(task.extra["chatId"].toString().toLong())
.messageId(task.extra["infoMessageId"].toString().toInt())
.build()
try {
task.bot.execute(deleteMessage)
} catch (e: Exception) {
logger.debug(e) { "删除旧状态消息时出错, 忽略该异常." }
}
}
val msg = if (task.retryCount == 0) {
"""
OneDrive 中转任务已创建
正在排队中...
文件名 ${task.document.fileName}
-------------------------------------------------
#Queuing
""".trimIndent()
} else {
"""
OneDrive 中转任务已创建
正在排队中...
文件名 ${task.document.fileName}
上次错误信息${(task.extra["lastError"] as Exception?)?.message ?: "(没有错误信息)"}
重试次数${task.retryCount}
-------------------------------------------------
#Queuing
""".trimIndent()
}
val message = task.bot.execute( val message = task.bot.execute(
SendMessage.builder() SendMessage.builder()
.text( .text(msg)
"""
OneDrive 中转任务已创建
正在排队中...
文件名 ${task.document.fileName}
-------------------------------------------------
#Queuing
""".trimIndent()
)
.chatId(task.extra["chatId"].toString().toLong()) .chatId(task.extra["chatId"].toString().toLong())
.replyToMessageId(task.extra["messageId"].toString().toInt()) .replyToMessageId(task.extra["messageId"].toString().toInt())
.build() .build()
@ -152,6 +185,9 @@ object DefaultOneDriveTransferCallback : OneDriveTransferCallback {
OneDrive 中转任务执行失败 OneDrive 中转任务执行失败
文件名 ${task.document.fileName} 文件名 ${task.document.fileName}
错误信息${progress.exception?.message} 错误信息${progress.exception?.message}
重试次数${task.retryCount}
任务将会追加至队列尾部进行重试
------------------------------------------------- -------------------------------------------------
#Failure #Error #Failure #Error
""".trimIndent() """.trimIndent()
@ -159,6 +195,14 @@ object DefaultOneDriveTransferCallback : OneDriveTransferCallback {
.build().orSendMessage(progress.currentTask.bot, progress.currentTask.extra["messageId"].toString().toInt()) .build().orSendMessage(progress.currentTask.bot, progress.currentTask.extra["messageId"].toString().toInt())
if (newMessage != null) { if (newMessage != null) {
progress.currentTask.extra["infoMessageId"] = newMessage.messageId progress.currentTask.extra["infoMessageId"] = newMessage.messageId
progress.currentTask.extra["lastError"] = progress.exception
}
if (task.retryCount < 5) {
runBlocking {
delay(10000)
task.retryCount++
OneDriveTransferCenter.submitUploadTask(task)
}
} }
} }
@ -181,6 +225,24 @@ object DefaultOneDriveTransferCallback : OneDriveTransferCallback {
} }
} }
override fun onTransferCancelled(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress?) {
val newMessage = EditMessageText.builder()
.chatId(task.extra["chatId"].toString().toLong())
.messageId(task.extra["infoMessageId"].toString().toInt())
.text(
"""
OneDrive 中转任务已取消
文件名 ${task.document.fileName}
-------------------------------------------------
#Cancelled
""".trimIndent()
)
.build().orSendMessage(task.bot, task.extra["messageId"].toString().toInt())
if (newMessage != null) {
task.extra["infoMessageId"] = newMessage.messageId
}
}
} }
interface OneDriveTransferCallback { interface OneDriveTransferCallback {
@ -191,6 +253,7 @@ interface OneDriveTransferCallback {
fun onUploadProgress(progress: OneDriveTransferWorkerProgress) fun onUploadProgress(progress: OneDriveTransferWorkerProgress)
fun onTransferFailure(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) fun onTransferFailure(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress)
fun onTransferSuccess(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) fun onTransferSuccess(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress)
fun onTransferCancelled(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress?)
} }
/** /**
@ -209,13 +272,14 @@ class OneDriveTransferTaskExecutor(
threadNum, threadNum, 0, TimeUnit.SECONDS, threadNum, threadNum, 0, TimeUnit.SECONDS,
ArrayBlockingQueue(50), ArrayBlockingQueue(50),
ThreadFactoryBuilder() ThreadFactoryBuilder()
.setNameFormat("onedrive-transfer-worker-%d") .setNameFormat("Transfer Worker %d")
.build() .build()
) { ) {
private val logger = KotlinLogging.logger { } private val logger = KotlinLogging.logger { }
val threadStatusMap = ConcurrentHashMap<Int, OneDriveTransferWorkerProgress>() val threadStatusMap = ConcurrentHashMap<Int, OneDriveTransferWorkerProgress>()
val threadMap = ConcurrentHashMap<Int, Thread>()
init { init {
if (chunkSize > MAX_CHUNK_SIZE) { if (chunkSize > MAX_CHUNK_SIZE) {
@ -232,9 +296,28 @@ class OneDriveTransferTaskExecutor(
taskQueue.offer(task) taskQueue.offer(task)
} }
fun cancelTransferTask(task: OneDriveTransferTask): Boolean {
if (!taskQueue.remove(task)) {
for (i in 0 until threadNum) {
if (threadStatusMap[i]?.currentTask?.id == task.id) {
threadMap[i]?.interrupt()
return true
}
}
return false
}
try {
callback.onTransferCancelled(task, null)
} catch (e: Exception) {
logger.warn(e) { "OneDrive 中转任务取消回调失败: ${e.message}" }
}
return true
}
private fun createWorker(id: Int): Runnable = Runnable { private fun createWorker(id: Int): Runnable = Runnable {
threadMap[id] = Thread.currentThread()
logger.info { "下载线程 $id 已启动." } logger.info { "下载线程 $id 已启动." }
while (!Thread.interrupted()) { while (!isTerminating) {
val task = taskQueue.take() val task = taskQueue.take()
logger.info { "线程 $id 开始执行任务: ${task.document.fileName}" } logger.info { "线程 $id 开始执行任务: ${task.document.fileName}" }
val progress = OneDriveTransferWorkerProgress(task) val progress = OneDriveTransferWorkerProgress(task)
@ -254,6 +337,15 @@ class OneDriveTransferTaskExecutor(
logger.warn(e) { "OneDrive 中转任务成功回调失败: ${e.message}" } logger.warn(e) { "OneDrive 中转任务成功回调失败: ${e.message}" }
} }
} catch (e: Exception) { } catch (e: Exception) {
if (e is InterruptedException) {
logger.info { "线程 $id 任务被取消: ${task.document.fileName}" }
try {
callback.onTransferCancelled(task, progress)
} catch (e: Exception) {
logger.warn(e) { "OneDrive 中转任务取消回调失败: ${e.message}" }
}
continue
}
logger.warn(e) { "OneDrive 中转任务执行失败: ${e.message}" } logger.warn(e) { "OneDrive 中转任务执行失败: ${e.message}" }
progress.status = OneDriveTransferStatus.FAILURE progress.status = OneDriveTransferStatus.FAILURE
callback.onTransferFailure(task, progress.apply { callback.onTransferFailure(task, progress.apply {
@ -264,6 +356,7 @@ class OneDriveTransferTaskExecutor(
threadStatusMap.remove(id) threadStatusMap.remove(id)
} }
} }
logger.info { "下载线程 $id 已停止." }
} }
private fun doTransferFile(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) { private fun doTransferFile(task: OneDriveTransferTask, progress: OneDriveTransferWorkerProgress) {
@ -520,7 +613,8 @@ data class OneDriveTransferTask(
val document: Document, val document: Document,
val onedriveId: String, val onedriveId: String,
val storagePath: String, val storagePath: String,
val extra: MutableMap<String, Any> = mutableMapOf(), val extra: MutableMap<String, Any?> = mutableMapOf(),
val createdAt: Date = Date(), val createdAt: Date = Date(),
var retryCount: Int = 0 var retryCount: Int = 0,
val id: UUID = UUID.randomUUID()
) )

View File

@ -392,7 +392,7 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
"Worker $workerId: [${task.status}] ${ "Worker $workerId: [${task.status}] ${
String.format( String.format(
"%.3f", "%.3f",
task.progress.get() task.progress.get() * 100.0
) )
}% ${task.currentTask.document.fileSize} Bytes\n" }% ${task.currentTask.document.fileSize} Bytes\n"
) )
@ -402,6 +402,33 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) :
} }
.build() .build()
fun cancelTask(): Ability = Ability.builder()
.named("cancel_task")
.info("取消指定的任务(回复对应的文件或状态消息)")
.privacy(Privacy.PUBLIC)
.locality(Locality.USER)
.action {
if (!it.update().message.isReply) {
it.bot().silent().send("请回复要取消的消息.", it.chatId())
return@action
}
val replyMessage = it.update().message.replyToMessage
val result = onedriveService.cancelTask(it.chatId(), replyMessage.messageId)
val msg = if (result) {
"已取消任务."
} else {
"任务已完成,无法取消."
}
val sendMessage = SendMessage.builder()
.chatId(it.chatId().toString())
.text(msg)
.replyToMessageId(it.update().message.messageId)
.build()
it.bot().execute(sendMessage)
}
.build()
private fun AbilityBuilder.named(name: String): AbilityBuilder { private fun AbilityBuilder.named(name: String): AbilityBuilder {
return if (config.useCommandPrefix) { return if (config.useCommandPrefix) {
name("odt_$name") name("odt_$name")

View File

@ -17,6 +17,7 @@ import org.telegram.abilitybots.api.bot.BaseAbilityBot
import org.telegram.telegrambots.meta.api.objects.Document import org.telegram.telegrambots.meta.api.objects.Document
import java.net.URL import java.net.URL
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.CopyOnWriteArrayList
class OneDriveTransferService( class OneDriveTransferService(
@ -35,6 +36,8 @@ class OneDriveTransferService(
.setTokenCacheAccessAspect(DatabaseTokenCache(db)) .setTokenCacheAccessAspect(DatabaseTokenCache(db))
.build() .build()
private val tasks = CopyOnWriteArrayList<OneDriveTransferTask>()
init { init {
transaction(db) { transaction(db) {
SchemaUtils.create(OneDriveTransferSettings, TokenCaches) SchemaUtils.create(OneDriveTransferSettings, TokenCaches)
@ -117,10 +120,19 @@ class OneDriveTransferService(
).apply { ).apply {
extra["chatId"] = userId extra["chatId"] = userId
extra["messageId"] = messageId extra["messageId"] = messageId
} }.also { tasks.add(it) }
) )
} }
fun cancelTask(userId: Long, replyMessageId: Int): Boolean {
val task = tasks.find {
it.tgUserId == userId &&
(it.extra["infoMessageId"] == replyMessageId || it.extra["messageId"] == replyMessageId)
} ?: throw NoSuchElementException()
return OneDriveTransferCenter.cancelUploadTask(task)
}
companion object { companion object {
private val THREAD_CURRENT_GRAPH_CLIENT = ThreadLocal<ClientCache>() private val THREAD_CURRENT_GRAPH_CLIENT = ThreadLocal<ClientCache>()