This commit is contained in:
deniscerri 2025-03-23 16:34:30 +01:00
parent e1db3fa5e5
commit fd151105ef
No known key found for this signature in database
GPG key ID: 95C43D517D830350
6 changed files with 78 additions and 77 deletions

View file

@ -26,7 +26,7 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloads() : Flow<List<DownloadItem>> fun getActiveDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Active' or status = 'Paused' ORDER BY id") @Query("SELECT * FROM downloads WHERE status='Active' or status = 'Paused' ORDER BY id, CASE WHEN status='Active' THEN 0 ELSE 1 END")
fun getActiveAndPausedDownloads() : Flow<List<DownloadItem>> fun getActiveAndPausedDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Paused'") @Query("SELECT * FROM downloads WHERE status='Paused'")
@ -115,7 +115,7 @@ interface DownloadDao {
@Query(""" @Query("""
SELECT * FROM downloads SELECT * FROM downloads
WHERE status in ('Active','Queued', 'Scheduled') AND downloadStartTime <= :currentTime WHERE status in ('Active', 'Queued', 'Scheduled') AND downloadStartTime <= :currentTime
ORDER BY ORDER BY
CASE CASE
WHEN id in (:priorityItems) THEN 0 WHEN id in (:priorityItems) THEN 0

View file

@ -1314,8 +1314,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
suspend fun pauseAllDownloads() { suspend fun pauseAllDownloads() {
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
isPausingResuming = true isPausingResuming = true
withContext(Dispatchers.IO){
downloadManager.cancelAll()
}
WorkManager.getInstance(application).cancelAllWorkByTag("download") WorkManager.getInstance(application).cancelAllWorkByTag("download")
downloadManager.cancelAll()
val activeDownloadsList = withContext(Dispatchers.IO){ val activeDownloadsList = withContext(Dispatchers.IO){
getActiveDownloads() getActiveDownloads()
} }
@ -1333,7 +1337,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
isPausingResuming = true isPausingResuming = true
WorkManager.getInstance(application).cancelAllWorkByTag("download") WorkManager.getInstance(application).cancelAllWorkByTag("download")
downloadManager.cancelAll()
withContext(Dispatchers.IO){
downloadManager.cancelAll()
}
val paused = withContext(Dispatchers.IO) { val paused = withContext(Dispatchers.IO) {
dao.getPausedDownloadsList() dao.getPausedDownloadsList()
} }
@ -1359,15 +1367,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
private suspend fun cancelAllDownloadsImpl() { private suspend fun cancelAllDownloadsImpl() {
WorkManager.getInstance(application).cancelAllWorkByTag("download")
val activeAndQueued = withContext(Dispatchers.IO){
repository.getActiveAndQueuedDownloadIDs()
}
activeAndQueued.forEach { id ->
YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.cancelDownloadNotification(id.toInt())
}
cancelActiveQueued() cancelActiveQueued()
withContext(Dispatchers.IO) {
downloadManager.cancelAll()
}
WorkManager.getInstance(application).cancelAllWorkByTag("download")
} }
fun resumeDownload(itemID: Long) = viewModelScope.launch { fun resumeDownload(itemID: Long) = viewModelScope.launch {

View file

@ -201,6 +201,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
override fun onResumeClick(itemID: Long) { override fun onResumeClick(itemID: Long) {
downloadViewModel.resumeDownload(itemID) downloadViewModel.resumeDownload(itemID)
//activeRecyclerView.smoothScrollToPosition(0)
} }
} }

View file

@ -55,9 +55,9 @@ import java.util.Locale
class DownloadManager private constructor() { class DownloadManager private constructor() {
private var notificationUtil: NotificationUtil = NotificationUtil(App.instance) private var notificationUtil: NotificationUtil = NotificationUtil(App.instance)
private var sharedPreferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.instance) private var sharedPreferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.instance)
private var dbManager: DBManager = DBManager.getInstance(App.instance)
private var dao : DownloadDao = dbManager.downloadDao
private lateinit var dbManager: DBManager
private lateinit var dao : DownloadDao
private lateinit var historyDao: HistoryDao private lateinit var historyDao: HistoryDao
private lateinit var commandTemplateDao: CommandTemplateDao private lateinit var commandTemplateDao: CommandTemplateDao
private lateinit var logRepo: LogRepository private lateinit var logRepo: LogRepository
@ -75,37 +75,18 @@ class DownloadManager private constructor() {
val isRunning get() = observeJob?.isActive == true val isRunning get() = observeJob?.isActive == true
private fun getActiveDownloadsStore() : Set<String> {
return sharedPreferences.getStringSet("ytdlp_active_downloads", setOf())!!
}
private fun addIDToActiveDownloadsStore(id: Long) {
val new = mutableSetOf<String>()
new.addAll(getActiveDownloadsStore())
new.add(id.toString())
sharedPreferences.edit().putStringSet("ytdlp_active_downloads", new).apply()
}
private fun removeIDFromActiveDownloadsStore(id: Long) {
val new = mutableSetOf<String>()
new.addAll(getActiveDownloadsStore())
new.remove(id.toString())
sharedPreferences.edit().putStringSet("ytdlp_active_downloads", new).apply()
}
fun cancelDownload(id: Long) { fun cancelDownload(id: Long) {
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.cancelDownloadNotification(id.toInt()) notificationUtil.cancelDownloadNotification(id.toInt())
removeIDFromActiveDownloadsStore(id)
} }
fun cancelAll() { fun cancelAll() {
getActiveDownloadsStore().map { it.toLong() }.forEach {
cancelDownload(it)
}
observeJob?.cancel() observeJob?.cancel()
observeJob = null
val activeDownloads = dao.getActiveDownloadsList()
activeDownloads.forEach {
cancelDownload(it.id)
}
} }
//only call from download worker //only call from download worker
@ -163,53 +144,68 @@ class DownloadManager private constructor() {
} }
observeJob = CoroutineScope(SupervisorJob() + Dispatchers.IO).launch { observeJob = CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
queueState.distinctUntilChanged().collectLatest { queue -> supervisorScope {
val runningCount = queue.asSequence() queueState.distinctUntilChanged().collectLatest { queue ->
.filter { it.status == DownloadRepository.Status.Active.toString() } val activeDownloads = queue
.count() .asSequence()
.filter { it.status == DownloadRepository.Status.Active.toString() }
.map { it.id }
val queueItems = queue.asSequence() val activeDownloadsCount = activeDownloads.count()
.filter { it.status != DownloadRepository.Status.Active.toString() }
val queueCount = queueItems.count() val queueItems = queue
if (queueCount == 0 && runningCount == 0) { .asSequence()
observeJob?.cancel() .filter { it.status != DownloadRepository.Status.Active.toString() }
return@collectLatest
}
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) val queueCount = queueItems.count()
if (useScheduler){ if (queueCount == 0 && activeDownloadsCount == 0) {
if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
observeJob?.cancel() observeJob?.cancel()
return@collectLatest return@collectLatest
} }
}
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) { if (!isRunning) {
observeJob?.cancel() observeJob?.cancel()
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 } && activeDownloadsCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
// Use supervisorScope to cancel child jobs when the downloader job is cancelled observeJob?.cancel()
supervisorScope { return@collectLatest
val queuedDownloads = queueItems
.toList()
.take(concurrentDownloads)
priorityItemIDs.removeAll(queuedDownloads.map { it.id })
val downloadsToStart = queuedDownloads.filter { it.id.toString() !in getActiveDownloadsStore() }
downloadsToStart.forEach { downloadItem ->
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
notificationUtil.notify(downloadItem.id.toInt(), notification)
launchDownloadJob(context, downloadItem)
addIDToActiveDownloadsStore(downloadItem.id)
} }
} }
}
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - activeDownloadsCount
if (concurrentDownloads > 0) {
supervisorScope {
//free spots are open
val queuedDownloads = if (priorityItemIDs.isNotEmpty() && !continueAfterPriorityIds) {
queueItems
.filter { priorityItemIDs.contains(it.id) }
.toList()
}else{
queueItems.toList()
}.take(concurrentDownloads)
priorityItemIDs.removeAll(queuedDownloads.map { it.id })
val downloadsToStart = queuedDownloads.filter { it.id !in activeDownloads }
downloadsToStart.forEach { downloadItem ->
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
notificationUtil.notify(downloadItem.id.toInt(), notification)
launchDownloadJob(context, downloadItem)
}
}
}
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) {
observeJob?.cancel()
return@collectLatest
}
}
} }
} }
} }
@ -288,7 +284,6 @@ class DownloadManager private constructor() {
resultRepo.updateDownloadItem(downloadItem)?.apply { resultRepo.updateDownloadItem(downloadItem)?.apply {
dao.updateWithoutUpsert(this) dao.updateWithoutUpsert(this)
} }
removeIDFromActiveDownloadsStore(downloadItem.id)
//val wasQuickDownloaded = resultDao.getCountInt() == 0 //val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking { runBlocking {
var finalPaths = mutableListOf<String>() var finalPaths = mutableListOf<String>()
@ -414,7 +409,6 @@ class DownloadManager private constructor() {
}.onFailure { }.onFailure {
FileUtil.deleteConfigFiles(request) FileUtil.deleteConfigFiles(request)
removeIDFromActiveDownloadsStore(downloadItem.id)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
} }

View file

@ -10,6 +10,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="none" android:scrollbars="none"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton

View file

@ -33,7 +33,8 @@
android:id="@+id/download_tablayout" android:id="@+id/download_tablayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_marginHorizontal="10dp"
android:layout_marginBottom="10dp"
android:backgroundTint="@android:color/transparent" android:backgroundTint="@android:color/transparent"
app:tabGravity="start" app:tabGravity="start"
app:tabMinWidth="0dp" app:tabMinWidth="0dp"