DownloadWorker: Progress improvements
* Handle split apk download progress * Also share download speed and time remaining Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -1,24 +1,37 @@
|
|||||||
package com.aurora.extensions
|
package com.aurora.extensions
|
||||||
|
|
||||||
|
import com.aurora.store.data.model.DownloadInfo
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
|
import kotlin.concurrent.fixedRateTimer
|
||||||
|
|
||||||
fun InputStream.copyTo(out: OutputStream, streamSize: Long): Flow<Int> {
|
fun InputStream.copyTo(out: OutputStream, streamSize: Long): Flow<DownloadInfo> {
|
||||||
return flow {
|
return flow {
|
||||||
var bytesCopied: Long = 0
|
var bytesCopied: Long = 0
|
||||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
var bytes = read(buffer)
|
var bytes = read(buffer)
|
||||||
|
|
||||||
|
var lastTotalBytesRead = 0L
|
||||||
|
var speed: Long = 0
|
||||||
|
@Suppress("KotlinConstantConditions") // False-positive for bytesCopied always being zero
|
||||||
|
val timer = fixedRateTimer("timer", true, 0L, 1000) {
|
||||||
|
val totalBytesRead = bytesCopied
|
||||||
|
speed = totalBytesRead - lastTotalBytesRead
|
||||||
|
lastTotalBytesRead = totalBytesRead
|
||||||
|
}
|
||||||
|
|
||||||
while (bytes >= 0) {
|
while (bytes >= 0) {
|
||||||
out.write(buffer, 0, bytes)
|
out.write(buffer, 0, bytes)
|
||||||
bytesCopied += bytes
|
bytesCopied += bytes
|
||||||
bytes = read(buffer)
|
|
||||||
// Emit stream progress in percentage
|
// Emit stream progress in percentage
|
||||||
emit((bytesCopied * 100 / streamSize).toInt())
|
emit(DownloadInfo((bytesCopied * 100 / streamSize).toInt(), bytesCopied, speed))
|
||||||
|
bytes = read(buffer)
|
||||||
}
|
}
|
||||||
}.flowOn(Dispatchers.IO).distinctUntilChanged()
|
timer.cancel()
|
||||||
|
}.flowOn(Dispatchers.IO).distinctUntilChangedBy { it.progress }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.aurora.store.data.model
|
||||||
|
|
||||||
|
data class DownloadInfo(
|
||||||
|
val progress: Int = 0,
|
||||||
|
val bytesCopied: Long = 0,
|
||||||
|
val speed: Long = 0
|
||||||
|
)
|
||||||
@@ -22,6 +22,7 @@ import com.aurora.extensions.copyAndRemove
|
|||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||||
import com.aurora.store.AuroraApplication
|
import com.aurora.store.AuroraApplication
|
||||||
|
import com.aurora.store.data.model.DownloadInfo
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
import com.aurora.store.data.model.Request
|
import com.aurora.store.data.model.Request
|
||||||
import com.aurora.store.data.network.HttpClient
|
import com.aurora.store.data.network.HttpClient
|
||||||
@@ -39,6 +40,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlin.io.path.ExperimentalPathApi
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
import kotlin.io.path.createDirectories
|
import kotlin.io.path.createDirectories
|
||||||
import kotlin.io.path.deleteRecursively
|
import kotlin.io.path.deleteRecursively
|
||||||
|
import kotlin.properties.Delegates
|
||||||
import com.aurora.gplayapi.data.models.File as GPlayFile
|
import com.aurora.gplayapi.data.models.File as GPlayFile
|
||||||
|
|
||||||
class DownloadWorker(private val appContext: Context, workerParams: WorkerParameters) :
|
class DownloadWorker(private val appContext: Context, workerParams: WorkerParameters) :
|
||||||
@@ -47,6 +49,8 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
companion object {
|
companion object {
|
||||||
const val DOWNLOAD_WORKER = "DOWNLOAD_WORKER"
|
const val DOWNLOAD_WORKER = "DOWNLOAD_WORKER"
|
||||||
const val DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS"
|
const val DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS"
|
||||||
|
const val DOWNLOAD_TIME = "DOWNLOAD_TIME"
|
||||||
|
const val DOWNLOAD_SPEED = "DOWNLOAD_SPEED"
|
||||||
|
|
||||||
fun enqueueApp(app: App) {
|
fun enqueueApp(app: App) {
|
||||||
AuroraApplication.enqueuedDownloads.update { it.copyAndAdd(app) }
|
AuroraApplication.enqueuedDownloads.update { it.copyAndAdd(app) }
|
||||||
@@ -73,6 +77,10 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
private lateinit var notificationManager: NotificationManager
|
private lateinit var notificationManager: NotificationManager
|
||||||
private var downloading = false
|
private var downloading = false
|
||||||
|
|
||||||
|
private var totalBytes by Delegates.notNull<Long>()
|
||||||
|
private var totalProgress = 0
|
||||||
|
private var downloadedBytes = 0L
|
||||||
|
|
||||||
private val TAG = DownloadWorker::class.java.simpleName
|
private val TAG = DownloadWorker::class.java.simpleName
|
||||||
private val notificationID = 200
|
private val notificationID = 200
|
||||||
|
|
||||||
@@ -105,6 +113,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download and verify all files exists
|
// Download and verify all files exists
|
||||||
|
totalBytes = files.sumOf { it.size }
|
||||||
PathUtil.getAppDownloadDir(appContext, app.packageName, app.versionCode).createDirectories()
|
PathUtil.getAppDownloadDir(appContext, app.packageName, app.versionCode).createDirectories()
|
||||||
|
|
||||||
val requestList = getDownloadRequest(files)
|
val requestList = getDownloadRequest(files)
|
||||||
@@ -196,10 +205,30 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onProgress(progress: Int) {
|
private suspend fun onProgress(downloadInfo: DownloadInfo) {
|
||||||
if (!isStopped) {
|
if (!isStopped) {
|
||||||
setProgress(Data.Builder().putInt(DOWNLOAD_PROGRESS, progress).build())
|
val progress = ((downloadedBytes + downloadInfo.bytesCopied) * 100 / totalBytes).toInt()
|
||||||
notifyStatus(DownloadStatus.DOWNLOADING, progress, notificationID)
|
|
||||||
|
// Individual file progress can be negligible in contrast to total progress
|
||||||
|
// Only notify the UI if progress is greater to avoid being rate-limited by Android
|
||||||
|
if (progress > totalProgress) {
|
||||||
|
val bytesRemaining = totalBytes - (downloadedBytes + downloadInfo.bytesCopied)
|
||||||
|
val speed = if (downloadInfo.speed == 0L) 1 else downloadInfo.speed
|
||||||
|
|
||||||
|
if (downloadInfo.progress == 100) {
|
||||||
|
downloadedBytes += downloadInfo.bytesCopied
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = Data.Builder()
|
||||||
|
.putInt(DOWNLOAD_PROGRESS, progress)
|
||||||
|
.putLong(DOWNLOAD_SPEED, downloadInfo.speed)
|
||||||
|
.putLong(DOWNLOAD_TIME, bytesRemaining / speed * 1000)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
setProgress(data)
|
||||||
|
notifyStatus(DownloadStatus.DOWNLOADING, progress, notificationID)
|
||||||
|
totalProgress = progress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user