DownloadWorker: Move downloads observing logic into worker as well
This allows us to keep all the modifications related to DownloadWorker in a single place. Also, drop the 3seconds delay and do the cleanup instead of delegating it to worker if last job was failed. This is better and more error-proof. Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -185,7 +185,7 @@ dependencies {
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
||||
//WorkManager
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0-rc01")
|
||||
|
||||
// LeakCanary
|
||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
||||
|
||||
@@ -28,15 +28,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ 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
|
||||
import com.aurora.store.data.work.DownloadWorker
|
||||
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() {
|
||||
@@ -39,7 +39,7 @@ class AuroraApplication : Application() {
|
||||
private lateinit var fetch: Fetch
|
||||
|
||||
companion object{
|
||||
val enqueuedDownloads = MutableStateFlow<MutableSet<App>>(mutableSetOf())
|
||||
val enqueuedDownloads = mutableSetOf<App>()
|
||||
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ class AuroraApplication : Application() {
|
||||
|
||||
fetch = DownloadManager.with(this).fetch
|
||||
|
||||
// Initialize DownloadWorker to observe and trigger downloads
|
||||
DownloadWorker.initDownloadWorker(applicationContext)
|
||||
|
||||
//Register broadcast receiver for package install/uninstall
|
||||
ContextCompat.registerReceiver(
|
||||
this,
|
||||
|
||||
@@ -59,7 +59,6 @@ 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.Log
|
||||
import com.aurora.store.util.Preferences
|
||||
@@ -70,8 +69,6 @@ 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
|
||||
|
||||
|
||||
@@ -217,22 +214,6 @@ 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 {
|
||||
|
||||
@@ -12,13 +12,12 @@ import androidx.work.ExistingWorkPolicy.KEEP
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkInfo
|
||||
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
|
||||
@@ -36,7 +35,8 @@ import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.deleteRecursively
|
||||
@@ -58,16 +58,54 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
private const val DOWNLOAD_APP = "DOWNLOAD_APP"
|
||||
private const val DOWNLOAD_UPDATE = "DOWNLOAD_UPDATE"
|
||||
|
||||
fun isEnqueued(packageName: String): Boolean {
|
||||
return AuroraApplication.enqueuedDownloads.value.any { it.packageName == packageName }
|
||||
private const val notificationID = 200
|
||||
private val cleanupStates = listOf(
|
||||
WorkInfo.State.CANCELLED,
|
||||
WorkInfo.State.FAILED
|
||||
)
|
||||
|
||||
fun initDownloadWorker(applicationContext: Context) {
|
||||
GlobalScope.launch {
|
||||
WorkManager.getInstance(applicationContext)
|
||||
.getWorkInfosByTagFlow(DOWNLOAD_WORKER)
|
||||
.collectLatest { downloadsList ->
|
||||
try {
|
||||
if (downloadsList.all { it.state.isFinished }) {
|
||||
// Do cleanup for last download if required
|
||||
downloadsList.getOrNull(0)?.let { workInfo ->
|
||||
if (workInfo.state in cleanupStates) {
|
||||
onFailure(
|
||||
applicationContext,
|
||||
AuroraApplication.enqueuedDownloads.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check and trigger download if enqueue list is not empty
|
||||
if (AuroraApplication.enqueuedDownloads.isNotEmpty()) {
|
||||
val app = AuroraApplication.enqueuedDownloads.first()
|
||||
Log.i(DOWNLOAD_WORKER, "Downloading ${app.packageName}")
|
||||
downloadApp(applicationContext, app)
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.i(DOWNLOAD_WORKER, "Failed to download enqueued apps", exception)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun enqueueApp(app: App) {
|
||||
AuroraApplication.enqueuedDownloads.update { it.copyAndAdd(app) }
|
||||
fun isEnqueued(packageName: String): Boolean {
|
||||
return AuroraApplication.enqueuedDownloads.any { it.packageName == packageName }
|
||||
}
|
||||
|
||||
fun enqueueApp(context: Context, app: App) {
|
||||
if (AuroraApplication.enqueuedDownloads.isEmpty()) downloadApp(context, app)
|
||||
AuroraApplication.enqueuedDownloads.add(app)
|
||||
}
|
||||
|
||||
fun cancelDownload(context: Context, app: App) {
|
||||
AuroraApplication.enqueuedDownloads.update {it.copyAndRemove(app) }
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(app.packageName)
|
||||
}
|
||||
|
||||
@@ -78,13 +116,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
if (updates) workManager.cancelAllWorkByTag(DOWNLOAD_UPDATE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
private fun downloadApp(context: Context, app: App) {
|
||||
val work = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.addTag(DOWNLOAD_WORKER)
|
||||
.addTag(app.packageName)
|
||||
@@ -98,6 +130,17 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork("${DOWNLOAD_WORKER}/${app.packageName}", KEEP, work)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
private fun onFailure(context: Context, app: App) {
|
||||
Log.i(DOWNLOAD_WORKER, "Cleaning up!")
|
||||
PathUtil.getAppDownloadDir(context, app.packageName, app.versionCode)
|
||||
.deleteRecursively()
|
||||
with (context.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager) {
|
||||
cancel(notificationID)
|
||||
}
|
||||
AuroraApplication.enqueuedDownloads.remove(app)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var app: App
|
||||
@@ -109,7 +152,6 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
private var downloadedBytes = 0L
|
||||
|
||||
private val TAG = DownloadWorker::class.java.simpleName
|
||||
private val notificationID = 200
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
// Purchase the app (free apps needs to be purchased too)
|
||||
@@ -122,7 +164,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
|
||||
// Try to parse input data into a valid app
|
||||
try {
|
||||
app = AuroraApplication.enqueuedDownloads.value.first()
|
||||
app = AuroraApplication.enqueuedDownloads.first()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "No apps enqueued for downloads", exception)
|
||||
return Result.failure()
|
||||
@@ -155,13 +197,11 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
.onFailure {
|
||||
Log.e(TAG, "Failed to download ${app.packageName}", it)
|
||||
downloading = false
|
||||
onFailure()
|
||||
return Result.failure()
|
||||
}
|
||||
while (downloading) {
|
||||
delay(1000)
|
||||
if (isStopped) {
|
||||
onFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -182,19 +222,10 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
|
||||
}
|
||||
|
||||
// Remove the app from the list
|
||||
AuroraApplication.enqueuedDownloads.update { it.copyAndRemove(app) }
|
||||
AuroraApplication.enqueuedDownloads.remove(app)
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
private fun onFailure() {
|
||||
Log.i(TAG, "Cleaning up!")
|
||||
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> {
|
||||
val downloadList = mutableListOf<Request>()
|
||||
files.filter { it.url.isNotBlank() }.forEach {
|
||||
|
||||
Reference in New Issue
Block a user