DownloadWorker: Restrict concurrent downloads to one
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user