package com.casic.smarttube.utils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.Call import okhttp3.Callback import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.io.File import java.io.IOException import java.util.concurrent.atomic.AtomicBoolean class FileDownloadManager(builder: Builder) { private val httpClient by lazy { OkHttpClient() } class Builder { lateinit var url: String lateinit var suffix: String lateinit var directory: File lateinit var downloadListener: OnFileDownloadListener /** * 文件下载地址 * */ fun setDownloadFileSource(url: String): Builder { this.url = url return this } /** * 文件后缀 * 如:apk等 * */ fun setFileSuffix(suffix: String): Builder { this.suffix = if (suffix.contains(".")) { //去掉前缀的点 suffix.drop(1) } else { suffix } return this } /** * 文件保存的地址 * */ fun setFileSaveDirectory(directory: File): Builder { this.directory = directory return this } /** * 设置文件下载回调监听 * */ fun setOnFileDownloadListener(downloadListener: OnFileDownloadListener): Builder { this.downloadListener = downloadListener return this } fun build(): FileDownloadManager { if (!::url.isInitialized || !::suffix.isInitialized || !::directory.isInitialized || !::downloadListener.isInitialized) { throw IllegalStateException("All properties must be initialized before building.") } return FileDownloadManager(this) } } private val url = builder.url private val suffix = builder.suffix private val directory = builder.directory private val listener = builder.downloadListener /** * 开始下载 * */ fun start() { val job = SupervisorJob() val scope = CoroutineScope(Dispatchers.Main + job) val request = Request.Builder().get().url(url).build() val newCall = httpClient.newCall(request) val isExecuting = AtomicBoolean(false) /** * 如果已被加入下载队列,则取消之前的,重新下载 */ if (isExecuting.getAndSet(true)) { newCall.cancel() } //异步下载文件 newCall.enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { scope.launch(Dispatchers.Main) { listener.onFailed(e) } } override fun onResponse(call: Call, response: Response) { scope.launch(Dispatchers.IO) { val body = response.body if (body == null) { listener.onFailed(IOException("Response body is null")) throw IOException("Response body is null") } else { val inputStream = body.byteStream() val fileSize = body.contentLength() if (fileSize <= 0) { throw IllegalArgumentException("Invalid file size") } listener.onDownloadStart(fileSize) val file = File(directory, "${System.currentTimeMillis()}.${suffix}") file.outputStream().use { fos -> val buffer = ByteArray(2048) var sum = 0L var read: Int while (inputStream.read(buffer).also { read = it } != -1) { fos.write(buffer, 0, read) sum += read.toLong() withContext(Dispatchers.Main) { listener.onProgressChanged(sum) } } } withContext(Dispatchers.Main) { listener.onDownloadEnd(file) } job.cancel() } } } }) } interface OnFileDownloadListener { fun onDownloadStart(total: Long) fun onProgressChanged(progress: Long) fun onDownloadEnd(file: File) fun onFailed(t: Throwable) } }