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:
Aayush Gupta
2023-11-06 20:40:54 +05:30
parent 5b60758347
commit 0f4021117f
3 changed files with 57 additions and 8 deletions

View File

@@ -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 }
} }

View File

@@ -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
)

View File

@@ -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
}
} }
} }