This commit is contained in:
deniscerri 2025-03-22 22:16:04 +01:00
parent 3b40ba7e02
commit 76394309fd
No known key found for this signature in database
GPG key ID: 95C43D517D830350
4 changed files with 82 additions and 91 deletions

View file

@ -1062,8 +1062,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" } result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
val ids = queued.filter { it.needsDataUpdating() }.map { it.id } val idsToUpdateDataInBackground = queued.filter { it.needsDataUpdating() && it.downloadStartTime > 0 }.map { it.id }
continueUpdatingDataInBackground(ids) if (idsToUpdateDataInBackground.isNotEmpty()) {
continueUpdatingDataInBackground(idsToUpdateDataInBackground)
}
} }
@ -1201,18 +1203,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
private fun continueUpdatingDataInBackground(ids: List<Long>){ private fun continueUpdatingDataInBackground(ids: List<Long>){
val id = System.currentTimeMillis().toInt()
val workRequest = OneTimeWorkRequestBuilder<UpdateMultipleDownloadsDataWorker>() val workRequest = OneTimeWorkRequestBuilder<UpdateMultipleDownloadsDataWorker>()
.setInputData( .setInputData(
Data.Builder() Data.Builder()
.putLongArray("ids", ids.toLongArray()) .putLongArray("ids", ids.toLongArray())
.putInt("id", id)
.build()) .build())
.addTag("updateData") .addTag("updateData")
.build() .build()
val context = App.instance val context = App.instance
WorkManager.getInstance(context).enqueueUniqueWork( WorkManager.getInstance(context).enqueueUniqueWork(
id.toString(), System.currentTimeMillis().toString(),
ExistingWorkPolicy.REPLACE, ExistingWorkPolicy.REPLACE,
workRequest workRequest
) )

View file

@ -626,7 +626,7 @@ class NotificationUtil(var context: Context) {
return notificationBuilder return notificationBuilder
.setContentTitle(resources.getString(R.string.updating_download_data)) .setContentTitle(resources.getString(R.string.updating_download_data))
.setOngoing(true) .setOngoing(true)
.setCategory(Notification.CATEGORY_PROGRESS) .setCategory(Notification.CATEGORY_MESSAGE)
.setSmallIcon(R.drawable.ic_launcher_foreground_large) .setSmallIcon(R.drawable.ic_launcher_foreground_large)
.setLargeIcon( .setLargeIcon(
BitmapFactory.decodeResource( BitmapFactory.decodeResource(
@ -637,7 +637,6 @@ class NotificationUtil(var context: Context) {
.setContentText("") .setContentText("")
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.clearActions() .clearActions()
.build() .build()

View file

@ -43,17 +43,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest 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.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
@ -80,6 +75,8 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
private lateinit var eventBus: EventBus private lateinit var eventBus: EventBus
private lateinit var queueState : Flow<List<DownloadItem>> private lateinit var queueState : Flow<List<DownloadItem>>
lateinit var observeJob : Job
private val handler = Handler(Looper.getMainLooper()) 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") @SuppressLint("RestrictedApi")
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
//init //init
@ -157,75 +149,75 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
val downloadJobs = mutableMapOf<Long, Job>() val downloadJobs = mutableMapOf<Long, Job>()
queueState.collectLatest { queue -> observeJob = CoroutineScope(SupervisorJob()).launch {
val runningCount = queue.asSequence() queueState.collectLatest { queue ->
.filter { it.status == DownloadRepository.Status.Active.toString() } val runningCount = queue.asSequence()
.count() .filter { it.status == DownloadRepository.Status.Active.toString() }
.count()
val queueItems = queue.asSequence() val queueItems = queue.asSequence()
.filter { it.status != DownloadRepository.Status.Active.toString() } .filter { it.status != DownloadRepository.Status.Active.toString() }
val queueCount = queueItems.count() val queueCount = queueItems.count()
if (queueCount == 0 && runningCount == 0) { if (queueCount == 0 && runningCount == 0) {
cancelSelf() observeJob.cancel()
return@collectLatest
}
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (useScheduler){
if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
cancelSelf()
return@collectLatest return@collectLatest
} }
}
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (concurrentDownloads > 0) { if (useScheduler){
//free spots are open if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
if (priorityItemIDs.isNotEmpty()) { observeJob.cancel()
if (!continueAfterPriorityIds && queueItems.none { priorityItemIDs.contains(it.id) }) {
//dont queue any more items if only required priority items
cancelSelf()
return@collectLatest return@collectLatest
} }
} }
// Use supervisorScope to cancel child jobs when the downloader job is cancelled val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount
supervisorScope { if (concurrentDownloads > 0) {
val queuedDownloads = queueItems //free spots are open
.toList() if (priorityItemIDs.isNotEmpty()) {
.take(concurrentDownloads) if (!continueAfterPriorityIds && queueItems.none { priorityItemIDs.contains(it.id) }) {
//dont queue any more items if only required priority items
val queuedIDs = queuedDownloads.map { it.id } observeJob.cancel()
val downloadJobsToStop = downloadJobs.filter { it.key !in queuedIDs } return@collectLatest
downloadJobsToStop.forEach { (download, job) -> }
job.cancel()
downloadJobs.remove(download)
} }
val downloadsToStart = queuedDownloads.filter { it.id !in downloadJobs } // Use supervisorScope to cancel child jobs when the downloader job is cancelled
downloadsToStart.forEach { download -> supervisorScope {
downloadJobs[download.id] = launchDownloadJob(download) 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() return Result.Success()
} }
@OptIn(DelicateCoroutinesApi::class)
private fun launchIO(block: suspend CoroutineScope.() -> Unit): Job =
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block)
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
private fun launchDownloadJob(downloadItem: DownloadItem) = launchIO { private fun launchDownloadJob(downloadItem: DownloadItem) = CoroutineScope(Dispatchers.IO).launch {
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
notificationUtil.notify(downloadItem.id.toInt(), notification)
val writtenPath = downloadItem.format.format_note.contains("-P ") val writtenPath = downloadItem.format.format_note.contains("-P ")
val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()) val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite())

View file

@ -1,8 +1,10 @@
package com.deniscerri.ytdl.work package com.deniscerri.ytdl.work
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
@ -14,37 +16,37 @@ import com.deniscerri.ytdl.util.NotificationUtil
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class UpdateMultipleDownloadsDataWorker( class UpdateMultipleDownloadsDataWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
private val context: Context,
workerParams: WorkerParameters override suspend fun getForegroundInfo(): ForegroundInfo {
) : Worker(context, workerParams) { val workNotif = NotificationUtil(App.instance).createDataUpdateNotification()
override fun doWork(): Result {
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 dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao val dao = dbManager.downloadDao
val resDao = dbManager.resultDao val resDao = dbManager.resultDao
val commandTemplateDao = dbManager.commandTemplateDao val commandTemplateDao = dbManager.commandTemplateDao
val resultRepo = ResultRepository(resDao,commandTemplateDao, context) val resultRepo = ResultRepository(resDao,commandTemplateDao, context)
val notificationUtil = NotificationUtil(context)
val ids = inputData.getLongArray("ids")!!.toMutableList() val ids = inputData.getLongArray("ids")!!.toMutableList()
val workID = inputData.getInt("id", 0)
if (workID == 0) return Result.failure()
val notification = notificationUtil.createDataUpdateNotification() setForegroundSafely()
try{
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{
ids.forEach { ids.forEach {
if (!isStopped){ if (!isStopped){
val d = dao.getDownloadById(it) val d = dao.getDownloadById(it)
if (d.title.isNotBlank() && d.author.isNotBlank() && d.thumb.isNotBlank()) { if (d.title.isNotBlank() && d.author.isNotBlank() && d.thumb.isNotBlank()) {
count++
return@forEach return@forEach
} }
@ -59,20 +61,18 @@ class UpdateMultipleDownloadsDataWorker(
} }
} }
} }
count++
notificationUtil.updateDataUpdateNotification(workID, UpdateMultipleDownloadsDataWorker::class.java.name, count, ids.size)
}else{ }else{
throw Exception() throw Exception()
} }
} }
Result.success()
}catch (e: Exception){ }catch (e: Exception){
notificationUtil.cancelDownloadNotification(workID)
ids.clear() ids.clear()
Result.failure() return Result.failure()
} }
return Result.success()
} }
} }