downloader fixes
This commit is contained in:
parent
7626f7f62e
commit
e1db3fa5e5
15 changed files with 264 additions and 172 deletions
|
|
@ -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 CASE WHEN status = 'Active' THEN 0 ELSE 1 END")
|
@Query("SELECT * FROM downloads WHERE status='Active' or status = 'Paused' ORDER BY id")
|
||||||
fun getActiveAndPausedDownloads() : Flow<List<DownloadItem>>
|
fun getActiveAndPausedDownloads() : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Paused'")
|
@Query("SELECT * FROM downloads WHERE status='Paused'")
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import com.deniscerri.ytdl.database.models.DownloadItemSimple
|
||||||
import com.deniscerri.ytdl.util.Extensions.toListString
|
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
||||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||||
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker
|
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker
|
||||||
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
|
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
|
||||||
|
import com.deniscerri.ytdl.work.downloader.DownloadManager
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
|
@ -99,6 +100,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
val savedDownloadsCount : Flow<Int>
|
val savedDownloadsCount : Flow<Int>
|
||||||
val scheduledDownloadsCount : Flow<Int>
|
val scheduledDownloadsCount : Flow<Int>
|
||||||
|
|
||||||
|
val downloadManager: DownloadManager
|
||||||
|
|
||||||
val pausedAllDownloads = MediatorLiveData(PausedAllDownloadsState.HIDDEN)
|
val pausedAllDownloads = MediatorLiveData(PausedAllDownloadsState.HIDDEN)
|
||||||
private val pausedAllDownloadsFlow : Flow<PausedAllDownloadsState>
|
private val pausedAllDownloadsFlow : Flow<PausedAllDownloadsState>
|
||||||
private var isPausingResuming = false
|
private var isPausingResuming = false
|
||||||
|
|
@ -145,6 +148,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, application)
|
resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, application)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
ytdlpUtil = YTDLPUtil(application, commandTemplateDao)
|
ytdlpUtil = YTDLPUtil(application, commandTemplateDao)
|
||||||
|
downloadManager = DownloadManager.getInstance()
|
||||||
|
|
||||||
activeDownloadsCount = repository.activeDownloadsCount
|
activeDownloadsCount = repository.activeDownloadsCount
|
||||||
activePausedDownloadsCount = repository.activePausedDownloadsCount
|
activePausedDownloadsCount = repository.activePausedDownloadsCount
|
||||||
|
|
@ -1291,28 +1295,45 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
return dao.getProcessingAsIncognitoCount() > 0
|
return dao.getProcessingAsIncognitoCount() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancelDownloadOnly(id : Long) {
|
||||||
|
downloadManager.cancelDownload(id)
|
||||||
|
}
|
||||||
|
|
||||||
fun pauseAllDownloads() = viewModelScope.launch {
|
suspend fun cancelDownload(id: Long) {
|
||||||
|
downloadManager.cancelDownload(id)
|
||||||
|
val item = getItemByID(id)
|
||||||
|
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||||
|
updateDownload(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun pauseDownload(id: Long) {
|
||||||
|
downloadManager.cancelDownload(id)
|
||||||
|
updateToStatus(id, DownloadRepository.Status.Paused)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun pauseAllDownloads() {
|
||||||
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
|
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
|
||||||
isPausingResuming = true
|
isPausingResuming = true
|
||||||
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
||||||
|
downloadManager.cancelAll()
|
||||||
val activeDownloadsList = withContext(Dispatchers.IO){
|
val activeDownloadsList = withContext(Dispatchers.IO){
|
||||||
getActiveDownloads()
|
getActiveDownloads()
|
||||||
}
|
}
|
||||||
activeDownloadsList.forEach {
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
|
||||||
}
|
|
||||||
delay(1000)
|
delay(1000)
|
||||||
isPausingResuming = false
|
isPausingResuming = false
|
||||||
repository.setDownloadStatusMultiple(activeDownloadsList.map { it.id }, DownloadRepository.Status.Paused)
|
withContext(Dispatchers.IO) {
|
||||||
pausedAllDownloads.value = PausedAllDownloadsState.RESUME
|
repository.setDownloadStatusMultiple(activeDownloadsList.map { it.id }, DownloadRepository.Status.Paused)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
pausedAllDownloads.value = PausedAllDownloadsState.RESUME
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resumeAllDownloads() = viewModelScope.launch {
|
suspend fun resumeAllDownloads() {
|
||||||
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
|
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
|
||||||
isPausingResuming = true
|
isPausingResuming = true
|
||||||
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
||||||
|
downloadManager.cancelAll()
|
||||||
val paused = withContext(Dispatchers.IO) {
|
val paused = withContext(Dispatchers.IO) {
|
||||||
dao.getPausedDownloadsList()
|
dao.getPausedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
app/src/main/java/com/deniscerri/ytdl/di/AppModule.kt
Normal file
5
app/src/main/java/com/deniscerri/ytdl/di/AppModule.kt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.deniscerri.ytdl.di
|
||||||
|
|
||||||
|
class AppModule {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class ScheduleAlarmReceiver : BroadcastReceiver() {
|
class ScheduleAlarmReceiver : BroadcastReceiver() {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ import com.deniscerri.ytdl.util.Extensions.setFullScreen
|
||||||
import com.deniscerri.ytdl.util.NotificationUtil
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.deniscerri.ytdl.util.VideoPlayerUtil
|
import com.deniscerri.ytdl.util.VideoPlayerUtil
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
|
@ -474,25 +474,16 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
||||||
|
|
||||||
override fun onCancelClick(itemID: Long) {
|
override fun onCancelClick(itemID: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
|
||||||
|
|
||||||
val item = withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.getItemByID(itemID)
|
|
||||||
}
|
|
||||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.updateDownload(item)
|
downloadViewModel.cancelDownload(itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPauseClick(itemID: Long, position: Int) {
|
override fun onPauseClick(itemID: Long, position: Int) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.updateToStatus(itemID, DownloadRepository.Status.Paused)
|
downloadViewModel.pauseDownload(itemID)
|
||||||
}
|
}
|
||||||
activeAdapter.notifyItemChanged(position)
|
activeAdapter.notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
@ -511,7 +502,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
||||||
|
|
||||||
//dont remove
|
//dont remove
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
fun onDownloadProgressEvent(event: com.deniscerri.ytdl.work.downloader.DownloadManager.DownloadProgress) {
|
||||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||||
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter
|
import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter
|
||||||
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
|
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
|
||||||
import com.deniscerri.ytdl.util.NotificationUtil
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadManager
|
||||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -151,7 +149,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
//dont remove
|
//dont remove
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) {
|
||||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||||
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
||||||
|
|
||||||
|
|
@ -176,15 +174,8 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
override fun onCancelClick(itemID: Long) {
|
override fun onCancelClick(itemID: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
|
||||||
|
|
||||||
val item = withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.getItemByID(itemID)
|
|
||||||
}
|
|
||||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.updateDownload(item)
|
downloadViewModel.cancelDownload(itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,10 +193,8 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
override fun onPauseClick(itemID: Long) {
|
override fun onPauseClick(itemID: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.updateToStatus(itemID, DownloadRepository.Status.Paused)
|
downloadViewModel.pauseDownload(itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -216,8 +216,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
||||||
val selectedObjects = getSelectedIDs()
|
val selectedObjects = getSelectedIDs()
|
||||||
adapter.clearCheckedItems()
|
adapter.clearCheckedItems()
|
||||||
for (id in selectedObjects){
|
for (id in selectedObjects){
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toInt().toString())
|
downloadViewModel.cancelDownloadOnly(id)
|
||||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
|
||||||
}
|
}
|
||||||
downloadViewModel.deleteAllWithID(selectedObjects)
|
downloadViewModel.deleteAllWithID(selectedObjects)
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
|
|
@ -524,20 +523,10 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
||||||
|
|
||||||
private fun cancelDownload(itemID: Long){
|
private fun cancelDownload(itemID: Long){
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
cancelItem(itemID.toInt())
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getItemByID(itemID)
|
downloadViewModel.cancelDownload(itemID)
|
||||||
}.let {
|
|
||||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.updateDownload(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelItem(id: Int){
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -123,36 +123,14 @@ class MoreFragment : Fragment() {
|
||||||
val checkbox = dialogView.findViewById<CheckBox>(R.id.doNotShowAgain)
|
val checkbox = dialogView.findViewById<CheckBox>(R.id.doNotShowAgain)
|
||||||
terminateDialog.setView(dialogView)
|
terminateDialog.setView(dialogView)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
checkbox.setOnCheckedChangeListener { compoundButton, b ->
|
checkbox.setOnCheckedChangeListener { compoundButton, b ->
|
||||||
doNotShowAgain = compoundButton.isChecked
|
doNotShowAgain = compoundButton.isChecked
|
||||||
}
|
}
|
||||||
|
|
||||||
val workManager = WorkManager.getInstance(requireContext())
|
|
||||||
val notificationUtil = NotificationUtil(requireContext())
|
|
||||||
|
|
||||||
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
|
terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val activeDownloads = withContext(Dispatchers.IO){
|
downloadViewModel.pauseAllDownloads()
|
||||||
downloadViewModel.getActiveDownloadsCount()
|
|
||||||
}
|
|
||||||
if (activeDownloads > 0) {
|
|
||||||
workManager.cancelAllWorkByTag("download")
|
|
||||||
val activeDownloadsList = withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.getActiveDownloads()
|
|
||||||
}
|
|
||||||
|
|
||||||
activeDownloadsList.forEach {
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
|
||||||
it.status = DownloadRepository.Status.Paused.toString()
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
downloadViewModel.updateDownload(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doNotShowAgain){
|
if (doNotShowAgain){
|
||||||
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false).apply()
|
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false).apply()
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
||||||
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
|
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadManager
|
||||||
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomappbar.BottomAppBar
|
import com.google.android.material.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -265,7 +266,7 @@ class DownloadLogFragment : Fragment() {
|
||||||
|
|
||||||
//dont remove
|
//dont remove
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) {
|
||||||
val progressBar = requireView().findViewById<LinearProgressIndicator>(R.id.progress)
|
val progressBar = requireView().findViewById<LinearProgressIndicator>(R.id.progress)
|
||||||
if (event.logItemID == logID) {
|
if (event.logItemID == logID) {
|
||||||
progressBar.isVisible = event.progress < 100
|
progressBar.isVisible = event.progress < 100
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||||
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
|
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ import com.deniscerri.ytdl.database.models.TerminalItem
|
||||||
import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel
|
import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel
|
||||||
import com.deniscerri.ytdl.ui.adapter.TerminalDownloadsAdapter
|
import com.deniscerri.ytdl.ui.adapter.TerminalDownloadsAdapter
|
||||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.downloader.DownloadManager
|
||||||
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
@ -84,7 +85,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) {
|
||||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||||
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
|
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.util.NotificationUtil
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdl.work.downloader.DownloadManager
|
||||||
|
import com.deniscerri.ytdl.work.downloader.DownloadWorker
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -122,7 +124,7 @@ class TerminalDownloadWorker(
|
||||||
|
|
||||||
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
|
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
|
||||||
runBlocking {
|
runBlocking {
|
||||||
eventBus.post(DownloadWorker.WorkerProgress(progress.toInt(), line, itemId.toLong(), logItem.id))
|
eventBus.post(DownloadManager.DownloadProgress(progress.toInt(), line, itemId.toLong(), logItem.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
val title: String = command.take(65)
|
val title: String = command.take(65)
|
||||||
|
|
@ -142,7 +144,7 @@ class TerminalDownloadWorker(
|
||||||
//move file from internal to set download directory
|
//move file from internal to set download directory
|
||||||
try {
|
try {
|
||||||
FileUtil.moveFile(File(FileUtil.getCachePath(context) + "/TERMINAL/" + itemId),context, downloadLocation!!, false){ p ->
|
FileUtil.moveFile(File(FileUtil.getCachePath(context) + "/TERMINAL/" + itemId),context, downloadLocation!!, false){ p ->
|
||||||
eventBus.post(DownloadWorker.WorkerProgress(p, "", itemId.toLong(), logItem.id))
|
eventBus.post(DownloadManager.DownloadProgress(p, "", itemId.toLong(), logItem.id))
|
||||||
}
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
package com.deniscerri.ytdl.work
|
package com.deniscerri.ytdl.work.downloader
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.ServiceInfo
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
|
@ -15,10 +13,6 @@ import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.ForegroundInfo
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdl.App
|
import com.deniscerri.ytdl.App
|
||||||
import com.deniscerri.ytdl.MainActivity
|
import com.deniscerri.ytdl.MainActivity
|
||||||
|
|
@ -38,17 +32,17 @@ import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.util.NotificationUtil
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
||||||
|
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
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.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.supervisorScope
|
import kotlinx.coroutines.supervisorScope
|
||||||
|
|
@ -58,56 +52,72 @@ import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
class DownloadManager private constructor() {
|
||||||
|
private var notificationUtil: NotificationUtil = NotificationUtil(App.instance)
|
||||||
|
private var sharedPreferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.instance)
|
||||||
|
|
||||||
class DownloadWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
|
|
||||||
private lateinit var dao : DownloadDao
|
|
||||||
private lateinit var notificationUtil: NotificationUtil
|
|
||||||
private lateinit var dbManager: DBManager
|
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
|
||||||
private lateinit var resultRepo: ResultRepository
|
private lateinit var resultRepo: ResultRepository
|
||||||
private lateinit var ytdlpUtil: YTDLPUtil
|
private lateinit var ytdlpUtil: YTDLPUtil
|
||||||
private lateinit var alarmScheduler : AlarmScheduler
|
private lateinit var alarmScheduler : AlarmScheduler
|
||||||
private lateinit var sharedPreferences : SharedPreferences
|
|
||||||
private lateinit var resources: Resources
|
|
||||||
private lateinit var workManager: WorkManager
|
|
||||||
private lateinit var eventBus: EventBus
|
private lateinit var eventBus: EventBus
|
||||||
|
private lateinit var resources: Resources
|
||||||
|
|
||||||
|
private lateinit var openDownloadQueue: PendingIntent
|
||||||
|
|
||||||
private lateinit var queueState : Flow<List<DownloadItem>>
|
private lateinit var queueState : Flow<List<DownloadItem>>
|
||||||
lateinit var observeJob : Job
|
private var observeJob : Job? = null
|
||||||
|
|
||||||
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
private val openDownloadQueue: PendingIntent = PendingIntent.getActivity(
|
val isRunning get() = observeJob?.isActive == true
|
||||||
context,
|
|
||||||
1000000000,
|
|
||||||
Intent(context, MainActivity::class.java).run {
|
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
putExtra("destination", "Queue")
|
|
||||||
},
|
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
private fun getActiveDownloadsStore() : Set<String> {
|
||||||
val workNotif = NotificationUtil(App.instance).createDefaultWorkerNotification()
|
return sharedPreferences.getStringSet("ytdlp_active_downloads", setOf())!!
|
||||||
|
|
||||||
return ForegroundInfo(
|
|
||||||
1000000000,
|
|
||||||
workNotif,
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
private fun addIDToActiveDownloadsStore(id: Long) {
|
||||||
override suspend fun doWork(): Result {
|
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) {
|
||||||
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||||
|
removeIDFromActiveDownloadsStore(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelAll() {
|
||||||
|
getActiveDownloadsStore().map { it.toLong() }.forEach {
|
||||||
|
cancelDownload(it)
|
||||||
|
}
|
||||||
|
observeJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
//only call from download worker
|
||||||
|
fun startDownload(
|
||||||
|
context: Context, //worker context
|
||||||
|
priorityItems: List<Long> = listOf(),
|
||||||
|
continueAfterPriorityIds : Boolean = true
|
||||||
|
) {
|
||||||
|
val priorityItemIDs = priorityItems.toMutableList()
|
||||||
|
|
||||||
//init
|
//init
|
||||||
notificationUtil = NotificationUtil(App.instance)
|
notificationUtil = NotificationUtil(context)
|
||||||
dbManager = DBManager.getInstance(context)
|
dbManager = DBManager.getInstance(context)
|
||||||
dao = dbManager.downloadDao
|
dao = dbManager.downloadDao
|
||||||
historyDao = dbManager.historyDao
|
historyDao = dbManager.historyDao
|
||||||
|
|
@ -119,6 +129,8 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
eventBus = EventBus.getDefault()
|
eventBus = EventBus.getDefault()
|
||||||
|
|
||||||
|
sharedPreferences.edit().putStringSet("ytdlp_active_downloads", setOf()).apply()
|
||||||
|
|
||||||
val confTmp = Configuration(context.resources.configuration)
|
val confTmp = Configuration(context.resources.configuration)
|
||||||
val locale = if (Build.VERSION.SDK_INT < 33) {
|
val locale = if (Build.VERSION.SDK_INT < 33) {
|
||||||
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
|
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
|
||||||
|
|
@ -133,13 +145,16 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
val metrics = DisplayMetrics()
|
val metrics = DisplayMetrics()
|
||||||
resources = Resources(context.assets, metrics, confTmp)
|
resources = Resources(context.assets, metrics, confTmp)
|
||||||
|
|
||||||
workManager = WorkManager.getInstance(context)
|
openDownloadQueue = PendingIntent.getActivity(
|
||||||
if (workManager.isRunning("download")) return Result.Failure()
|
context,
|
||||||
|
1000000000,
|
||||||
|
Intent(context, MainActivity::class.java).run {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
putExtra("destination", "Queue")
|
||||||
|
},
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
setForegroundSafely()
|
|
||||||
|
|
||||||
val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList()
|
|
||||||
val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
|
|
||||||
val time = System.currentTimeMillis() + 6000
|
val time = System.currentTimeMillis() + 6000
|
||||||
queueState = if (priorityItemIDs.isEmpty()) {
|
queueState = if (priorityItemIDs.isEmpty()) {
|
||||||
dao.getActiveQueuedScheduledDownloadsUntil(time)
|
dao.getActiveQueuedScheduledDownloadsUntil(time)
|
||||||
|
|
@ -147,10 +162,8 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
dao.getActiveQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs)
|
dao.getActiveQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadJobs = mutableMapOf<Long, Job>()
|
observeJob = CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
|
||||||
|
queueState.distinctUntilChanged().collectLatest { queue ->
|
||||||
observeJob = CoroutineScope(SupervisorJob()).launch {
|
|
||||||
queueState.collectLatest { queue ->
|
|
||||||
val runningCount = queue.asSequence()
|
val runningCount = queue.asSequence()
|
||||||
.filter { it.status == DownloadRepository.Status.Active.toString() }
|
.filter { it.status == DownloadRepository.Status.Active.toString() }
|
||||||
.count()
|
.count()
|
||||||
|
|
@ -160,66 +173,53 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
|
|
||||||
val queueCount = queueItems.count()
|
val queueCount = queueItems.count()
|
||||||
if (queueCount == 0 && runningCount == 0) {
|
if (queueCount == 0 && runningCount == 0) {
|
||||||
observeJob.cancel()
|
observeJob?.cancel()
|
||||||
return@collectLatest
|
return@collectLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
if (useScheduler){
|
if (useScheduler){
|
||||||
if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
|
if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
|
||||||
observeJob.cancel()
|
observeJob?.cancel()
|
||||||
return@collectLatest
|
return@collectLatest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) {
|
||||||
|
observeJob?.cancel()
|
||||||
|
return@collectLatest
|
||||||
|
}
|
||||||
|
|
||||||
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount
|
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount
|
||||||
if (concurrentDownloads > 0) {
|
if (concurrentDownloads > 0) {
|
||||||
//free spots are open
|
//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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use supervisorScope to cancel child jobs when the downloader job is cancelled
|
// Use supervisorScope to cancel child jobs when the downloader job is cancelled
|
||||||
supervisorScope {
|
supervisorScope {
|
||||||
val queuedDownloads = queueItems
|
val queuedDownloads = queueItems
|
||||||
.toList()
|
.toList()
|
||||||
.take(concurrentDownloads)
|
.take(concurrentDownloads)
|
||||||
|
|
||||||
val queuedIDs = queuedDownloads.map { it.id }
|
priorityItemIDs.removeAll(queuedDownloads.map { it.id })
|
||||||
val downloadJobsToStop = downloadJobs.filter { it.key !in queuedIDs }
|
val downloadsToStart = queuedDownloads.filter { it.id.toString() !in getActiveDownloadsStore() }
|
||||||
downloadJobsToStop.forEach { (download, job) ->
|
|
||||||
job.cancel()
|
|
||||||
downloadJobs.remove(download)
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadsToStart = queuedDownloads.filter { it.id !in downloadJobs }
|
|
||||||
downloadsToStart.forEach { downloadItem ->
|
downloadsToStart.forEach { downloadItem ->
|
||||||
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
|
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
|
||||||
notificationUtil.notify(downloadItem.id.toInt(), notification)
|
notificationUtil.notify(downloadItem.id.toInt(), notification)
|
||||||
downloadJobs[downloadItem.id] = launchDownloadJob(downloadItem)
|
launchDownloadJob(context, downloadItem)
|
||||||
|
addIDToActiveDownloadsStore(downloadItem.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (observeJob.isActive) {
|
|
||||||
//keep alive
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.Success()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
private fun launchDownloadJob(downloadItem: DownloadItem) = CoroutineScope(Dispatchers.IO).launch {
|
private fun launchDownloadJob(context: Context, downloadItem: DownloadItem) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
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())
|
||||||
|
|
||||||
val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem)
|
val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem)
|
||||||
|
|
||||||
|
|
@ -270,7 +270,7 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
runCatching {
|
runCatching {
|
||||||
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
|
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
|
||||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||||
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID))
|
eventBus.post(DownloadProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID))
|
||||||
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
|
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
|
||||||
notificationUtil.updateDownloadNotification(
|
notificationUtil.updateDownloadNotification(
|
||||||
downloadItem.id.toInt(),
|
downloadItem.id.toInt(),
|
||||||
|
|
@ -288,12 +288,13 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
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>()
|
||||||
|
|
||||||
if (noCache){
|
if (noCache){
|
||||||
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID))
|
eventBus.post(DownloadProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID))
|
||||||
val outputSequence = it.out.split("\n")
|
val outputSequence = it.out.split("\n")
|
||||||
finalPaths =
|
finalPaths =
|
||||||
outputSequence.asSequence()
|
outputSequence.asSequence()
|
||||||
|
|
@ -315,17 +316,17 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
FileUtil.scanMedia(finalPaths, context)
|
FileUtil.scanMedia(finalPaths, context)
|
||||||
}else{
|
}else{
|
||||||
//move file from internal to set download directory
|
//move file from internal to set download directory
|
||||||
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
|
eventBus.post(DownloadProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
|
||||||
try {
|
try {
|
||||||
finalPaths = withContext(Dispatchers.IO){
|
finalPaths = withContext(Dispatchers.IO){
|
||||||
FileUtil.moveFile(tempFileDir.absoluteFile,
|
FileUtil.moveFile(tempFileDir.absoluteFile,
|
||||||
context, downloadLocation, keepCache){ p ->
|
context, downloadLocation, keepCache){ p ->
|
||||||
eventBus.post(WorkerProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
|
eventBus.post(DownloadProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
|
||||||
}
|
}
|
||||||
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
|
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
|
||||||
|
|
||||||
if (finalPaths.isNotEmpty()){
|
if (finalPaths.isNotEmpty()){
|
||||||
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
|
eventBus.post(DownloadProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
|
||||||
}
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -413,10 +414,11 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
|
|
||||||
}.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())
|
||||||
}
|
}
|
||||||
if (isStopped) return@onFailure
|
if (!this.isActive) return@onFailure
|
||||||
if (it is YoutubeDL.CanceledException) return@onFailure
|
if (it is YoutubeDL.CanceledException) return@onFailure
|
||||||
if (it.message?.contains("JSONDecodeError") == true) {
|
if (it.message?.contains("JSONDecodeError") == true) {
|
||||||
val cachePath = "${FileUtil.getCachePath(context)}infojsons"
|
val cachePath = "${FileUtil.getCachePath(context)}infojsons"
|
||||||
|
|
@ -456,19 +458,28 @@ class DownloadWorker(private val context: Context,workerParams: WorkerParameters
|
||||||
resources
|
resources
|
||||||
)
|
)
|
||||||
|
|
||||||
eventBus.post(WorkerProgress(100, it.toString(), downloadItem.id, downloadItem.logID))
|
eventBus.post(DownloadProgress(100, it.toString(), downloadItem.id, downloadItem.logID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "DownloadWorker"
|
const val TAG = "DownloadManager"
|
||||||
|
@Volatile
|
||||||
|
private var instance: DownloadManager? = null
|
||||||
|
|
||||||
|
fun getInstance() : DownloadManager {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: DownloadManager().also {
|
||||||
|
instance = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WorkerProgress(
|
class DownloadProgress(
|
||||||
val progress: Int,
|
val progress: Int,
|
||||||
val output: String,
|
val output: String,
|
||||||
val downloadItemID: Long,
|
val downloadItemID: Long,
|
||||||
val logItemID: Long?
|
val logItemID: Long?
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.deniscerri.ytdl.work.downloader
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
|
import com.deniscerri.ytdl.App
|
||||||
|
import com.deniscerri.ytdl.MainActivity
|
||||||
|
import com.deniscerri.ytdl.R
|
||||||
|
import com.deniscerri.ytdl.database.DBManager
|
||||||
|
import com.deniscerri.ytdl.database.dao.CommandTemplateDao
|
||||||
|
import com.deniscerri.ytdl.database.dao.DownloadDao
|
||||||
|
import com.deniscerri.ytdl.database.dao.HistoryDao
|
||||||
|
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdl.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdl.database.repository.LogRepository
|
||||||
|
import com.deniscerri.ytdl.database.repository.ResultRepository
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||||
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
||||||
|
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||||
|
import com.deniscerri.ytdl.work.isRunning
|
||||||
|
import com.deniscerri.ytdl.work.setForegroundSafely
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
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.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.supervisorScope
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import java.io.File
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
|
||||||
|
private lateinit var workManager: WorkManager
|
||||||
|
private lateinit var downloadManager: DownloadManager
|
||||||
|
|
||||||
|
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||||
|
val workNotif = NotificationUtil(App.instance).createDefaultWorkerNotification()
|
||||||
|
|
||||||
|
return ForegroundInfo(
|
||||||
|
1000000000,
|
||||||
|
workNotif,
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
workManager = WorkManager.getInstance(context)
|
||||||
|
if (workManager.isRunning("download")) return Result.Failure()
|
||||||
|
|
||||||
|
setForegroundSafely()
|
||||||
|
downloadManager = DownloadManager.getInstance()
|
||||||
|
|
||||||
|
val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList()
|
||||||
|
val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
|
||||||
|
downloadManager.startDownload(context, priorityItemIDs, continueAfterPriorityIds)
|
||||||
|
|
||||||
|
var active = downloadManager.isRunning
|
||||||
|
while (active) {
|
||||||
|
active = !isStopped && downloadManager.isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "DownloadWorker"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue