diff --git a/Http/src/main/java/com/arialyy/aria/http/download/HttpHeaderInterceptor.kt b/Http/src/main/java/com/arialyy/aria/http/download/HttpDHeaderInterceptor.kt similarity index 83% rename from Http/src/main/java/com/arialyy/aria/http/download/HttpHeaderInterceptor.kt rename to Http/src/main/java/com/arialyy/aria/http/download/HttpDHeaderInterceptor.kt index fdbe2c04..7ec03b1e 100644 --- a/Http/src/main/java/com/arialyy/aria/http/download/HttpHeaderInterceptor.kt +++ b/Http/src/main/java/com/arialyy/aria/http/download/HttpDHeaderInterceptor.kt @@ -40,7 +40,7 @@ import java.util.UUID * @Description * @Date 2:27 PM 2023/1/28 **/ -internal class HttpHeaderInterceptor : ITaskInterceptor { +internal class HttpDHeaderInterceptor : ITaskInterceptor { private lateinit var task: ITask private lateinit var taskOption: HttpDTaskOption @@ -53,22 +53,27 @@ internal class HttpHeaderInterceptor : ITaskInterceptor { override fun interceptor(chain: TaskChain): TaskResp { if (Looper.myLooper() == Looper.getMainLooper()) { - throw IllegalThreadStateException("Io operations cannot be in the main thread") + throw IllegalThreadStateException("io operations cannot be in the main thread") } + Timber.i("step 1. get file info") task = chain.getTask() taskOption = task.getTaskOption(HttpDTaskOption::class.java) - return try { + try { val fileSize = getFileSize() - - chain.proceed(task) + if (fileSize >= 0) { + task.taskState.fileSize = fileSize + return chain.proceed(task) + } } catch (e: IOException) { Timber.e( "download fail, url: ${ chain.getTask().getTaskOption(HttpDTaskOption::class.java).sourUrl }" ) - TaskResp(TaskResp.CODE_GET_FILE_INFO_FAIL) + return TaskResp(TaskResp.CODE_GET_FILE_INFO_FAIL) } + Timber.e("can't get fileSize") + return TaskResp(TaskResp.CODE_INTERRUPT) } @Throws(IOException::class) @@ -78,8 +83,8 @@ internal class HttpHeaderInterceptor : ITaskInterceptor { val conn: HttpURLConnection = IRequest.getRequest(taskOption.httpOption!!) .getDConnection(taskOption.sourUrl!!, taskOption.httpOption!!) // https://httpwg.org/specs/rfc9110.html#byte.ranges - // conn.setRequestProperty("Range", "bytes=" + 0 + "-") - conn.setRequestProperty("Range", "bytes=0-1") // 尝试获取1个字节 + conn.setRequestProperty("Range", "bytes=0-") + // conn.setRequestProperty("Range", "bytes=0-1") // 尝试获取1个字节 conn.connect() return handleConnect(conn) } @@ -109,9 +114,17 @@ internal class HttpHeaderInterceptor : ITaskInterceptor { reader.close() return handleUrlReTurn(conn, HttpUtil.getWindowReplaceUrl(sb.toString())) } + val chunkSize = checkChunkFileSize(conn) + if (chunkSize > -1) { + Timber.d("the url is chunk task, ${conn.url}") + return chunkSize + } // code is 200, but file size cannot be obtained. return -1 } + code == 416 -> { + return getFileSizeFromHeader(conn.headerFields, taskOption) + } code in CODE_30X -> { Timber.d("handle 30x turn, code: $code") return handleUrlReTurn(conn, conn.getHeaderField("Location")) @@ -126,6 +139,19 @@ internal class HttpHeaderInterceptor : ITaskInterceptor { } } + /** + * if headers has [rfc9112 Transfer-Encoding](https://httpwg.org/specs/rfc9112.html#chunked.trailer.section) + * the url is chunk task + */ + private fun checkChunkFileSize(conn: HttpURLConnection): Long { + val chunkHead = conn.headerFields["Transfer-Encoding"] + if (chunkHead.isNullOrEmpty()) { + return -1 + } + taskOption.isChunkTask = true + return 0 + } + @Throws(IOException::class) private fun handleUrlReTurn( oldConn: HttpURLConnection, newUrl: String? diff --git a/Http/src/main/java/com/arialyy/aria/http/download/HttpDStartController.kt b/Http/src/main/java/com/arialyy/aria/http/download/HttpDStartController.kt index 9494e6ca..cbbe59e2 100644 --- a/Http/src/main/java/com/arialyy/aria/http/download/HttpDStartController.kt +++ b/Http/src/main/java/com/arialyy/aria/http/download/HttpDStartController.kt @@ -22,12 +22,14 @@ import com.arialyy.aria.core.command.StartCmd import com.arialyy.aria.core.inf.IStartController import com.arialyy.aria.core.processor.IHttpFileLenAdapter import com.arialyy.aria.core.task.DownloadTask +import com.arialyy.aria.core.task.ITaskInterceptor import com.arialyy.aria.core.task.TaskCachePool import com.arialyy.aria.http.HttpBaseController import com.arialyy.aria.http.HttpOption import com.arialyy.aria.http.HttpUtil import com.arialyy.aria.orm.entity.DEntity import kotlinx.coroutines.launch +import timber.log.Timber import java.net.HttpURLConnection /** @@ -44,6 +46,19 @@ class HttpDStartController(target: Any, val url: String) : HttpBaseController(ta httpDTaskOption.sourUrl = url } + /** + * use multi-threaded download file, if file size <= 5m, this setting is not valid + * @param threadNum range [1 - 32] + */ + fun setThreadNum(threadNum: Long): HttpDStartController { + if (threadNum !in 1..32) { + Timber.e("set thread num fail, only 0 < threadNum < 33, threadNum: $threadNum") + return this + } + httpDTaskOption.threadNum = threadNum + return this + } + /** * set http params, link Header */ @@ -56,11 +71,22 @@ class HttpDStartController(target: Any, val url: String) : HttpBaseController(ta * Maybe the server has special rules, you need set [IHttpFileLenAdapter] to get the file length from [HttpURLConnection.getHeaderFields] */ fun setHttpFileLenAdapter(adapter: IHttpFileLenAdapter): HttpDStartController { - httpDTaskOption.fileSizeAdapter = adapter return this } + /** + * if you want to do something before the task is executed, you can set up a task interceptor + * eg: determine the network status before task execution + */ + fun setTaskInterceptor(taskInterceptor: ITaskInterceptor): HttpDStartController { + httpDTaskOption.taskInterceptor.add(taskInterceptor) + return this + } + + /** + * set download listener + */ fun setListener(listener: HttpDownloadListener): HttpDStartController { DuaContext.getLifeManager().addCustomListener(target, listener) return this diff --git a/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskOption.kt b/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskOption.kt index 133fca15..77ba878f 100644 --- a/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskOption.kt +++ b/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskOption.kt @@ -17,6 +17,7 @@ package com.arialyy.aria.http.download import com.arialyy.aria.core.download.DTaskOption import com.arialyy.aria.core.processor.IHttpFileLenAdapter +import com.arialyy.aria.core.task.ITaskInterceptor import com.arialyy.aria.http.HttpOption /** @@ -26,6 +27,13 @@ import com.arialyy.aria.http.HttpOption **/ class HttpDTaskOption : DTaskOption() { + companion object { + const val BLOCK_SIZE = 1024 * 1024 * 5 + } + var httpOption: HttpOption? = null var fileSizeAdapter: IHttpFileLenAdapter? = null + var taskInterceptor = mutableListOf() + var isChunkTask = false + var threadNum: Long = 1L } \ No newline at end of file diff --git a/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskUtil.kt b/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskUtil.kt index bc554f0f..9040d7db 100644 --- a/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskUtil.kt +++ b/Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskUtil.kt @@ -1,6 +1,9 @@ package com.arialyy.aria.http.download +import com.arialyy.aria.core.DuaContext import com.arialyy.aria.core.task.AbsTaskUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch /** * @Author laoyuyu @@ -9,10 +12,6 @@ import com.arialyy.aria.core.task.AbsTaskUtil **/ internal class HttpDTaskUtil : AbsTaskUtil() { - init { - - } - override fun isRunning(): Boolean { TODO("Not yet implemented") } @@ -26,6 +25,14 @@ internal class HttpDTaskUtil : AbsTaskUtil() { } override fun start() { - + getTask().getTaskOption(HttpDTaskOption::class.java).taskInterceptor.let { + if (it.isNotEmpty()) { + addInterceptors(it) + } + } + addCoreInterceptor(HttpDHeaderInterceptor()) + DuaContext.duaScope.launch(Dispatchers.IO) { + val resp = interceptor() + } } } \ No newline at end of file diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTaskUtil.kt b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTaskUtil.kt index 053c2d5e..0d74b060 100644 --- a/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTaskUtil.kt +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTaskUtil.kt @@ -25,8 +25,8 @@ import com.arialyy.aria.core.task.ITaskInterceptor.IChain * @Date 1:12 PM 2023/1/28 **/ abstract class AbsTaskUtil : ITaskUtil { - protected lateinit var mTask: ITask - protected lateinit var mEventListener: IEventListener + private lateinit var mTask: ITask + private lateinit var mEventListener: IEventListener private val mUserInterceptor = mutableListOf() private val mCoreInterceptor = mutableListOf() @@ -36,14 +36,16 @@ abstract class AbsTaskUtil : ITaskUtil { mEventListener = listener } + protected fun getTask() = mTask + /** * add user interceptor */ - open fun setInterceptors(userInterceptors: List) { + protected fun addInterceptors(userInterceptors: List) { mUserInterceptor.addAll(userInterceptors) } - protected open fun addCoreInterceptor(interceptor: ITaskInterceptor) { + protected fun addCoreInterceptor(interceptor: ITaskInterceptor) { mCoreInterceptor.add(interceptor) }