diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index 7a778176..ce65ae50 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -1062,8 +1062,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel result.message = repository.startDownloadWorker(queued, context).getOrElse { "" } - val ids = queued.filter { it.needsDataUpdating() }.map { it.id } - continueUpdatingDataInBackground(ids) + val idsToUpdateDataInBackground = queued.filter { it.needsDataUpdating() && it.downloadStartTime > 0 }.map { it.id } + if (idsToUpdateDataInBackground.isNotEmpty()) { + continueUpdatingDataInBackground(idsToUpdateDataInBackground) + } } @@ -1201,18 +1203,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel } private fun continueUpdatingDataInBackground(ids: List){ - val id = System.currentTimeMillis().toInt() val workRequest = OneTimeWorkRequestBuilder() .setInputData( Data.Builder() .putLongArray("ids", ids.toLongArray()) - .putInt("id", id) .build()) .addTag("updateData") .build() val context = App.instance WorkManager.getInstance(context).enqueueUniqueWork( - id.toString(), + System.currentTimeMillis().toString(), ExistingWorkPolicy.REPLACE, workRequest ) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt index 3a1b65c3..42f5e441 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt @@ -626,7 +626,7 @@ class NotificationUtil(var context: Context) { return notificationBuilder .setContentTitle(resources.getString(R.string.updating_download_data)) .setOngoing(true) - .setCategory(Notification.CATEGORY_PROGRESS) + .setCategory(Notification.CATEGORY_MESSAGE) .setSmallIcon(R.drawable.ic_launcher_foreground_large) .setLargeIcon( BitmapFactory.decodeResource( @@ -637,7 +637,6 @@ class NotificationUtil(var context: Context) { .setContentText("") .setPriority(NotificationCompat.PRIORITY_LOW) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setProgress(PROGRESS_MAX, PROGRESS_CURR, false) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) .clearActions() .build() diff --git a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt index 091b534e..746db6be 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt @@ -43,17 +43,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.supervisorScope @@ -80,6 +75,8 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters private lateinit var eventBus: EventBus private lateinit var queueState : Flow> + lateinit var observeJob : Job + private val handler = Handler(Looper.getMainLooper()) @@ -107,11 +104,6 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters ) } - private fun cancelSelf() { - workManager.cancelWorkById(this@DownloadWorker.id) - } - - @SuppressLint("RestrictedApi") override suspend fun doWork(): Result { //init @@ -157,75 +149,75 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters val downloadJobs = mutableMapOf() - queueState.collectLatest { queue -> - val runningCount = queue.asSequence() - .filter { it.status == DownloadRepository.Status.Active.toString() } - .count() + observeJob = CoroutineScope(SupervisorJob()).launch { + queueState.collectLatest { queue -> + val runningCount = queue.asSequence() + .filter { it.status == DownloadRepository.Status.Active.toString() } + .count() - val queueItems = queue.asSequence() - .filter { it.status != DownloadRepository.Status.Active.toString() } + val queueItems = queue.asSequence() + .filter { it.status != DownloadRepository.Status.Active.toString() } - val queueCount = queueItems.count() - if (queueCount == 0 && runningCount == 0) { - cancelSelf() - return@collectLatest - } - - val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) - if (useScheduler){ - if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) { - cancelSelf() + val queueCount = queueItems.count() + if (queueCount == 0 && runningCount == 0) { + observeJob.cancel() return@collectLatest } - } - val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount - if (concurrentDownloads > 0) { - //free spots are open - if (priorityItemIDs.isNotEmpty()) { - if (!continueAfterPriorityIds && queueItems.none { priorityItemIDs.contains(it.id) }) { - //dont queue any more items if only required priority items - cancelSelf() + val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) + if (useScheduler){ + if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) { + observeJob.cancel() return@collectLatest } } - // Use supervisorScope to cancel child jobs when the downloader job is cancelled - supervisorScope { - val queuedDownloads = queueItems - .toList() - .take(concurrentDownloads) - - val queuedIDs = queuedDownloads.map { it.id } - val downloadJobsToStop = downloadJobs.filter { it.key !in queuedIDs } - downloadJobsToStop.forEach { (download, job) -> - job.cancel() - downloadJobs.remove(download) + val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount + if (concurrentDownloads > 0) { + //free spots are open + if (priorityItemIDs.isNotEmpty()) { + if (!continueAfterPriorityIds && queueItems.none { priorityItemIDs.contains(it.id) }) { + //dont queue any more items if only required priority items + observeJob.cancel() + return@collectLatest + } } - val downloadsToStart = queuedDownloads.filter { it.id !in downloadJobs } - downloadsToStart.forEach { download -> - downloadJobs[download.id] = launchDownloadJob(download) + // Use supervisorScope to cancel child jobs when the downloader job is cancelled + supervisorScope { + val queuedDownloads = queueItems + .toList() + .take(concurrentDownloads) + + val queuedIDs = queuedDownloads.map { it.id } + val downloadJobsToStop = downloadJobs.filter { it.key !in queuedIDs } + downloadJobsToStop.forEach { (download, job) -> + job.cancel() + downloadJobs.remove(download) + } + + val downloadsToStart = queuedDownloads.filter { it.id !in downloadJobs } + downloadsToStart.forEach { downloadItem -> + val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url }) + notificationUtil.notify(downloadItem.id.toInt(), notification) + downloadJobs[downloadItem.id] = launchDownloadJob(downloadItem) + } } } - } + } + } + + while (observeJob.isActive) { + //keep alive } return Result.Success() } - - @OptIn(DelicateCoroutinesApi::class) - private fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) - @OptIn(ExperimentalStdlibApi::class) - private fun launchDownloadJob(downloadItem: DownloadItem) = launchIO { - val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url }) - notificationUtil.notify(downloadItem.id.toInt(), notification) - + private fun launchDownloadJob(downloadItem: DownloadItem) = CoroutineScope(Dispatchers.IO).launch { val writtenPath = downloadItem.format.format_note.contains("-P ") val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()) diff --git a/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt index 07d0764c..03f8b228 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt @@ -1,8 +1,10 @@ package com.deniscerri.ytdl.work import android.content.Context +import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.os.Build +import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.Worker import androidx.work.WorkerParameters @@ -14,37 +16,37 @@ import com.deniscerri.ytdl.util.NotificationUtil import kotlinx.coroutines.runBlocking -class UpdateMultipleDownloadsDataWorker( - private val context: Context, - workerParams: WorkerParameters -) : Worker(context, workerParams) { - override fun doWork(): Result { +class UpdateMultipleDownloadsDataWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { + + override suspend fun getForegroundInfo(): ForegroundInfo { + val workNotif = NotificationUtil(App.instance).createDataUpdateNotification() + + return ForegroundInfo( + 2000000000, + workNotif, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + FOREGROUND_SERVICE_TYPE_DATA_SYNC + } else { + 0 + }, + ) + } + + + override suspend fun doWork(): Result { val dbManager = DBManager.getInstance(context) val dao = dbManager.downloadDao val resDao = dbManager.resultDao val commandTemplateDao = dbManager.commandTemplateDao val resultRepo = ResultRepository(resDao,commandTemplateDao, context) - val notificationUtil = NotificationUtil(context) val ids = inputData.getLongArray("ids")!!.toMutableList() - val workID = inputData.getInt("id", 0) - if (workID == 0) return Result.failure() - val notification = notificationUtil.createDataUpdateNotification() - - if (Build.VERSION.SDK_INT > 33) { - setForegroundAsync(ForegroundInfo(workID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC)) - }else{ - setForegroundAsync(ForegroundInfo(workID, notification)) - } - - var count = 0 - - return try{ + setForegroundSafely() + try{ ids.forEach { if (!isStopped){ val d = dao.getDownloadById(it) if (d.title.isNotBlank() && d.author.isNotBlank() && d.thumb.isNotBlank()) { - count++ return@forEach } @@ -59,20 +61,18 @@ class UpdateMultipleDownloadsDataWorker( } } } - - count++ - notificationUtil.updateDataUpdateNotification(workID, UpdateMultipleDownloadsDataWorker::class.java.name, count, ids.size) }else{ throw Exception() } } - Result.success() + }catch (e: Exception){ - notificationUtil.cancelDownloadNotification(workID) ids.clear() - Result.failure() + return Result.failure() } + + return Result.success() } } \ No newline at end of file