mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-24 03:42:15 +00:00
Improve: Start working on new downloader
This commit is contained in:
parent
f465f5da2a
commit
3654b50d54
@ -0,0 +1,15 @@
|
|||||||
|
package com.looker.droidify.network
|
||||||
|
|
||||||
|
sealed class DownloadResult<T>(
|
||||||
|
val progress: Long? = 0,
|
||||||
|
val total: Long? = 0,
|
||||||
|
val data: T? = null,
|
||||||
|
val message: String? = null
|
||||||
|
) {
|
||||||
|
class Loading<T>(progress: Long? = null, total: Long? = null, data: T? = null) :
|
||||||
|
DownloadResult<T>(progress, total, data)
|
||||||
|
|
||||||
|
class Success<T>(data: T?) : DownloadResult<T>(data = data)
|
||||||
|
class Error<T>(message: String, data: T? = null) :
|
||||||
|
DownloadResult<T>(data = data, message = message)
|
||||||
|
}
|
118
src/main/kotlin/com/looker/droidify/network/DownloaderX.kt
Normal file
118
src/main/kotlin/com/looker/droidify/network/DownloaderX.kt
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package com.looker.droidify.network
|
||||||
|
|
||||||
|
import com.looker.droidify.utility.ProgressInputStream
|
||||||
|
import com.looker.droidify.utility.extension.await
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Cache
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
|
||||||
|
object DownloaderX {
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
|
private data class ClientConfiguration(val cache: Cache?, val onion: Boolean)
|
||||||
|
|
||||||
|
private val clients = mutableMapOf<ClientConfiguration, OkHttpClient>()
|
||||||
|
private val onionProxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", 9050))
|
||||||
|
|
||||||
|
var proxy: Proxy? = null
|
||||||
|
set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
synchronized(clients) {
|
||||||
|
field = value
|
||||||
|
clients.keys.removeAll { !it.onion }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCall(request: Request.Builder, authentication: String): Call {
|
||||||
|
val oldRequest = request.build()
|
||||||
|
val newRequest = if (authentication.isNotEmpty()) {
|
||||||
|
request.addHeader("Authorization", authentication).build()
|
||||||
|
} else {
|
||||||
|
request.build()
|
||||||
|
}
|
||||||
|
val onion = oldRequest.url.host.endsWith(".onion")
|
||||||
|
val client = synchronized(clients) {
|
||||||
|
val proxy = if (onion) onionProxy else proxy
|
||||||
|
val clientConfiguration = ClientConfiguration(null, onion)
|
||||||
|
clients[clientConfiguration] ?: run {
|
||||||
|
val client = this.client
|
||||||
|
.newBuilder()
|
||||||
|
.connectTimeout(30L, SECONDS)
|
||||||
|
.readTimeout(15L, SECONDS)
|
||||||
|
.writeTimeout(15L, SECONDS)
|
||||||
|
.proxy(proxy).build()
|
||||||
|
clients[clientConfiguration] = client
|
||||||
|
client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client.newCall(newRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun startDownload(
|
||||||
|
url: String,
|
||||||
|
partialFile: File,
|
||||||
|
authentication: String
|
||||||
|
): Flow<DownloadResult<Unit>> = flow {
|
||||||
|
val scope = currentCoroutineContext()
|
||||||
|
val start = if (partialFile.exists()) partialFile.length()
|
||||||
|
.let { if (it > 0L) it else null } else null
|
||||||
|
val request = Request.Builder().url(url)
|
||||||
|
.apply { if (start != null) addHeader("Range", "bytes=$start-") }
|
||||||
|
|
||||||
|
val response = createCall(request, authentication).await()
|
||||||
|
|
||||||
|
response.use { it ->
|
||||||
|
if (it.code == 304) emit(DownloadResult.Loading())
|
||||||
|
else {
|
||||||
|
val body = it.body!!
|
||||||
|
val append = start != null && it.header("Content-Range") != null
|
||||||
|
val progressStart = if (append && start != null) start else 0L
|
||||||
|
val progressTotal =
|
||||||
|
body.contentLength().let { if (it >= 0L) it else null }
|
||||||
|
?.let { progressStart + it }
|
||||||
|
withContext(Dispatchers.IO + scope) {
|
||||||
|
val inputStream = ProgressInputStream(body.byteStream()) {
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
launch { emit(DownloadResult.Error("Thread Interrupted")) }
|
||||||
|
throw InterruptedException()
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
emit(
|
||||||
|
DownloadResult.Loading(
|
||||||
|
progress = progressStart + it,
|
||||||
|
total = progressTotal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputStream.use { input ->
|
||||||
|
val outputStream =
|
||||||
|
if (append) FileOutputStream(partialFile, true)
|
||||||
|
else FileOutputStream(partialFile)
|
||||||
|
outputStream.use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
output.fd.runCatching { sync() }
|
||||||
|
.onSuccess { emit(DownloadResult.Success(Unit)) }
|
||||||
|
.onFailure { emit(DownloadResult.Error(it.message.toString())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.looker.droidify.utility.extension
|
||||||
|
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.internal.closeQuietly
|
||||||
|
import okio.IOException
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
suspend fun Call.await(): Response {
|
||||||
|
return suspendCancellableCoroutine { continuation ->
|
||||||
|
enqueue(
|
||||||
|
object : Callback {
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
continuation.resume(response) {
|
||||||
|
response.body?.closeQuietly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
// Don't bother with resuming the continuation if it is already cancelled.
|
||||||
|
if (continuation.isCancelled) return
|
||||||
|
continuation.resumeWithException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
continuation.invokeOnCancellation {
|
||||||
|
try {
|
||||||
|
cancel()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
// Ignore cancel exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user