DownloadWorker: Restrict concurrent downloads to one

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2023-11-01 17:48:50 +05:30
parent dcaf4f024a
commit 26c4749047
8 changed files with 63 additions and 41 deletions

View File

@@ -27,4 +27,16 @@ fun <T> MutableList<T>.flushAndAdd(list: List<T>) {
fun <T> MutableSet<T>.flushAndAdd(list: Set<T>) {
clear()
addAll(list)
}
}
fun <T> MutableSet<T>.copyAndAdd(element: T): MutableSet<T> {
val newSet = this.toMutableSet()
newSet.add(element)
return newSet
}
fun <T> MutableSet<T>.copyAndRemove(element: T): MutableSet<T> {
val newSet = this.toMutableSet()
newSet.remove(element)
return newSet
}

View File

@@ -23,6 +23,7 @@ package com.aurora.store
import android.app.Application
import androidx.core.content.ContextCompat
import com.aurora.extensions.isPAndAbove
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.downloader.DownloadManager
import com.aurora.store.data.receiver.PackageManagerReceiver
import com.aurora.store.data.service.NotificationService
@@ -30,6 +31,7 @@ import com.aurora.store.util.CommonUtil
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PackageUtil
import com.tonyodev.fetch2.Fetch
import kotlinx.coroutines.flow.MutableStateFlow
import org.lsposed.hiddenapibypass.HiddenApiBypass
class AuroraApplication : Application() {
@@ -37,6 +39,7 @@ class AuroraApplication : Application() {
private lateinit var fetch: Fetch
companion object{
val enqueuedDownloads = MutableStateFlow<MutableSet<App>>(mutableSetOf())
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
}

View File

@@ -59,8 +59,8 @@ import com.aurora.extensions.toast
import com.aurora.store.data.model.NetworkStatus
import com.aurora.store.data.model.SelfUpdate
import com.aurora.store.data.providers.NetworkProvider
import com.aurora.store.data.work.DownloadWorker
import com.aurora.store.databinding.ActivityMainBinding
import com.aurora.store.util.CertUtil
import com.aurora.store.util.Log
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
@@ -69,6 +69,9 @@ import com.aurora.store.view.ui.sheets.NetworkDialogSheet
import com.aurora.store.view.ui.sheets.SelfUpdateSheet
import com.aurora.store.viewmodel.MainViewModel
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -92,6 +95,7 @@ class MainActivity : AppCompatActivity() {
R.id.updatesFragment
)
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
applyThemeAccent()
super.onCreate(savedInstanceState)
@@ -213,6 +217,22 @@ class MainActivity : AppCompatActivity() {
}
}
}
// Handle enqueued downloads
GlobalScope.launch {
AuroraApplication.enqueuedDownloads.collect { enqueuedDownloads ->
try {
if (enqueuedDownloads.isNotEmpty()) {
// Let any existing work related to download/install get finished
delay(3000)
Log.i("Downloading ${enqueuedDownloads.first().packageName}")
DownloadWorker.downloadApp(applicationContext, enqueuedDownloads.first())
}
} catch (exception: Exception) {
Log.i("Failed to download enqueued apps", exception)
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@@ -46,11 +46,7 @@ class DownloadManager private constructor(var context: Context) {
}
private fun getFetchConfiguration(context: Context): FetchConfiguration {
var maxActive = Preferences.getInteger(context, Preferences.PREFERENCE_DOWNLOAD_ACTIVE)
if (maxActive == 0)
maxActive = 3
return FetchConfiguration.Builder(context)
.setDownloadConcurrentLimit(maxActive)
.enableLogging(BuildConfig.DEBUG)
.enableHashCheck(true)
.enableFileExistChecks(true)

View File

@@ -8,17 +8,20 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ExistingWorkPolicy.REPLACE
import androidx.work.ExistingWorkPolicy.KEEP
import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.aurora.Constants
import com.aurora.extensions.copyAndAdd
import com.aurora.extensions.copyTo
import com.aurora.extensions.isQAndAbove
import com.aurora.extensions.copyAndRemove
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.store.AuroraApplication
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.model.Request
import com.aurora.store.data.network.HttpClient
@@ -26,13 +29,13 @@ import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.receiver.InstallReceiver
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PathUtil
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.withContext
import java.io.File
import java.net.URL
import kotlinx.coroutines.flow.update
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteRecursively
@@ -42,25 +45,27 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
CoroutineWorker(appContext, workerParams) {
companion object {
const val DOWNLOAD_DATA = "DOWNLOAD_DATA"
const val DOWNLOAD_WORKER = "DOWNLOAD_WORKER"
const val DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS"
private const val TAG = "DownloadWorker"
fun enqueueApp(app: App) {
AuroraApplication.enqueuedDownloads.update { it.copyAndAdd(app) }
}
/**
* Downloads and install an [App]
*
* Triggers Immediate downloads and installation. In most cases, you don't need to call
* this method. Consider using [enqueueApp] instead.
*/
fun downloadApp(context: Context, app: App) {
Log.i(TAG, "Downloading ${app.packageName}")
val downloadData = Data.Builder()
.putString(DOWNLOAD_DATA, Gson().toJson(app))
.build()
val work = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(downloadData)
.addTag(app.packageName)
.addTag(app.versionCode.toString())
.setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(app.packageName, REPLACE, work)
WorkManager.getInstance(context).enqueueUniqueWork(DOWNLOAD_WORKER, KEEP, work)
}
}
@@ -71,8 +76,6 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
private val TAG = DownloadWorker::class.java.simpleName
private val notificationID = 200
private val gson = Gson()
override suspend fun doWork(): Result {
// Purchase the app (free apps needs to be purchased too)
val authData = AuthProvider.with(appContext).getAuthData()
@@ -83,14 +86,11 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
appContext.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager
// Try to parse input data into a valid app
withContext(Dispatchers.Default) {
try {
app = gson.fromJson(inputData.getString(DOWNLOAD_DATA), App::class.java)
} catch (exception: Exception) {
Log.e(TAG, "Failed parsing requested app!", exception)
notifyStatus(DownloadStatus.FAILED)
return@withContext Result.failure()
}
try {
app = AuroraApplication.enqueuedDownloads.value.first()
} catch (exception: Exception) {
Log.e(TAG, "No apps enqueued for downloads", exception)
return Result.failure()
}
// Set work/service to foreground on < Android 12.0
@@ -140,6 +140,9 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
appContext.sendBroadcast(it)
}
// Remove the app from the list
AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) }
return Result.success()
}
@@ -149,6 +152,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
PathUtil.getAppDownloadDir(appContext, app.packageName, app.versionCode)
.deleteRecursively()
notificationManager.cancel(notificationID)
AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) }
}
private fun getDownloadRequest(files: List<GPlayFile>): List<Request> {

View File

@@ -48,7 +48,6 @@ object Preferences {
const val INSTALLATION_ABANDON_SESSION = "INSTALLATION_ABANDON_SESSION"
const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE"
const val PREFERENCE_DOWNLOAD_EXTERNAL = "PREFERENCE_DOWNLOAD_EXTERNAL"
const val PREFERENCE_DOWNLOAD_DIRECTORY = "PREFERENCE_DOWNLOAD_DIRECTORY"
const val PREFERENCE_DOWNLOAD_WIFI_ONLY = "PREFERENCE_DOWNLOAD_WIFI_ONLY"

View File

@@ -38,7 +38,6 @@ import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_ACTIVE
import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_DIRECTORY
import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_EXTERNAL
import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_WIFI_ONLY
@@ -165,7 +164,6 @@ class OnboardingFragment : Fragment(R.layout.fragment_onboarding) {
save(PREFERENCE_FILTER_SEARCH, true)
/*Downloader*/
save(PREFERENCE_DOWNLOAD_ACTIVE, 3)
save(PREFERENCE_DOWNLOAD_EXTERNAL, false)
save(PREFERENCE_DOWNLOAD_DIRECTORY, PathUtil.getExternalPath(requireContext()))
save(PREFERENCE_DOWNLOAD_WIFI_ONLY, false)