This commit is contained in:
deniscerri 2025-03-23 20:37:07 +01:00
parent fd151105ef
commit 444e188549
No known key found for this signature in database
GPG key ID: 95C43D517D830350
14 changed files with 475 additions and 646 deletions

View file

@ -107,15 +107,15 @@ interface DownloadDao {
@Query("""
SELECT * FROM downloads
WHERE status in ('Active', 'Queued', 'Scheduled') AND downloadStartTime <= :currentTime
WHERE status in ('Queued', 'Scheduled') AND downloadStartTime <= :currentTime
ORDER BY downloadStartTime, id
LIMIT 10
""")
fun getActiveQueuedScheduledDownloadsUntil(currentTime: Long) : Flow<List<DownloadItem>>
fun getQueuedScheduledDownloadsUntil(currentTime: Long) : Flow<List<DownloadItem>>
@Query("""
SELECT * FROM downloads
WHERE status in ('Active', 'Queued', 'Scheduled') AND downloadStartTime <= :currentTime
WHERE status in ('Queued', 'Scheduled') AND downloadStartTime <= :currentTime
ORDER BY
CASE
WHEN id in (:priorityItems) THEN 0
@ -124,7 +124,7 @@ interface DownloadDao {
downloadStartTime, id
LIMIT 10
""")
fun getActiveQueuedScheduledDownloadsUntilWithPriority(currentTime: Long, priorityItems: List<Long>) : Flow<List<DownloadItem>>
fun getQueuedScheduledDownloadsUntilWithPriority(currentTime: Long, priorityItems: List<Long>) : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
fun getQueuedDownloadsList() : List<DownloadItem>

View file

@ -22,7 +22,7 @@ import com.deniscerri.ytdl.database.models.DownloadItemSimple
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.downloader.DownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import java.io.File

View file

@ -10,13 +10,9 @@ import android.util.DisplayMetrics
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.media3.common.Player.Command
import androidx.paging.PagingData
import androidx.paging.filter
import androidx.paging.map
import androidx.preference.PreferenceManager
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
@ -49,7 +45,6 @@ import com.deniscerri.ytdl.util.extractors.YTDLPUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
import com.deniscerri.ytdl.work.downloader.DownloadManager
import com.google.gson.Gson
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CancellationException
@ -58,11 +53,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@ -99,9 +91,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val erroredDownloadsCount : Flow<Int>
val savedDownloadsCount : Flow<Int>
val scheduledDownloadsCount : Flow<Int>
val downloadManager: DownloadManager
val pausedAllDownloads = MediatorLiveData(PausedAllDownloadsState.HIDDEN)
private val pausedAllDownloadsFlow : Flow<PausedAllDownloadsState>
private var isPausingResuming = false
@ -148,7 +137,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, application)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
ytdlpUtil = YTDLPUtil(application, commandTemplateDao)
downloadManager = DownloadManager.getInstance()
activeDownloadsCount = repository.activeDownloadsCount
activePausedDownloadsCount = repository.activePausedDownloadsCount
@ -1296,38 +1284,39 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
fun cancelDownloadOnly(id : Long) {
downloadManager.cancelDownload(id)
YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.cancelDownloadNotification(id.toInt())
}
suspend fun cancelDownload(id: Long) {
downloadManager.cancelDownload(id)
val item = getItemByID(id)
item.status = DownloadRepository.Status.Cancelled.toString()
updateDownload(item)
cancelDownloadOnly(id)
updateToStatus(id, DownloadRepository.Status.Cancelled)
}
suspend fun pauseDownload(id: Long) {
downloadManager.cancelDownload(id)
cancelDownloadOnly(id)
updateToStatus(id, DownloadRepository.Status.Paused)
}
suspend fun pauseAllDownloads() {
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
isPausingResuming = true
withContext(Dispatchers.IO){
downloadManager.cancelAll()
}
WorkManager.getInstance(application).cancelAllWorkByTag("download")
val activeDownloadsList = withContext(Dispatchers.IO){
getActiveDownloads()
}
delay(1000)
isPausingResuming = false
activeDownloadsList.forEach {
cancelDownloadOnly(it.id)
}
withContext(Dispatchers.IO) {
repository.setDownloadStatusMultiple(activeDownloadsList.map { it.id }, DownloadRepository.Status.Paused)
}
delay(1500)
isPausingResuming = false
withContext(Dispatchers.Main) {
pausedAllDownloads.value = PausedAllDownloadsState.RESUME
}
@ -1336,12 +1325,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
suspend fun resumeAllDownloads() {
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
isPausingResuming = true
WorkManager.getInstance(application).cancelAllWorkByTag("download")
withContext(Dispatchers.IO){
downloadManager.cancelAll()
}
val paused = withContext(Dispatchers.IO) {
dao.getPausedDownloadsList()
}
@ -1350,7 +1333,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
dbManager.downloadDao.resetPausedToQueued()
repository.startDownloadWorker(paused, application)
}
delay(1000)
delay(1500)
isPausingResuming = false
withContext(Dispatchers.Main) {
pausedAllDownloads.value = PausedAllDownloadsState.PAUSE
@ -1366,12 +1349,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
cancelAllDownloadsImpl()
}
private suspend fun cancelAllDownloadsImpl() {
cancelActiveQueued()
withContext(Dispatchers.IO) {
downloadManager.cancelAll()
}
private fun cancelAllDownloadsImpl() {
WorkManager.getInstance(application).cancelAllWorkByTag("download")
cancelActiveQueued()
}
fun resumeDownload(itemID: Long) = viewModelScope.launch {

View file

@ -1,5 +0,0 @@
package com.deniscerri.ytdl.di
class AppModule {
}

View file

@ -9,7 +9,7 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.deniscerri.ytdl.work.downloader.DownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import java.util.concurrent.TimeUnit
class ScheduleAlarmReceiver : BroadcastReceiver() {

View file

@ -48,7 +48,7 @@ import com.deniscerri.ytdl.util.Extensions.setFullScreen
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.VideoPlayerUtil
import com.deniscerri.ytdl.work.downloader.DownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -502,7 +502,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
//dont remove
@Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadProgressEvent(event: com.deniscerri.ytdl.work.downloader.DownloadManager.DownloadProgress) {
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")

View file

@ -28,7 +28,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.work.downloader.DownloadManager
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.progressindicator.LinearProgressIndicator
@ -149,7 +149,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
//dont remove
@Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) {
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")

View file

@ -30,8 +30,7 @@ import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.work.downloader.DownloadManager
import com.deniscerri.ytdl.work.downloader.DownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
@ -266,7 +265,7 @@ class DownloadLogFragment : Fragment() {
//dont remove
@Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) {
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
val progressBar = requireView().findViewById<LinearProgressIndicator>(R.id.progress)
if (event.logItemID == logID) {
progressBar.isVisible = event.progress < 100

View file

@ -22,7 +22,7 @@ import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
import com.deniscerri.ytdl.work.downloader.DownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import java.util.Calendar
import java.util.concurrent.TimeUnit

View file

@ -20,8 +20,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel
import com.deniscerri.ytdl.ui.adapter.TerminalDownloadsAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.work.downloader.DownloadManager
import com.deniscerri.ytdl.work.downloader.DownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.LinearProgressIndicator
import kotlinx.coroutines.flow.collectLatest
@ -85,7 +84,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadProgressEvent(event: DownloadManager.DownloadProgress) {
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")

View file

@ -0,0 +1,441 @@
package com.deniscerri.ytdl.work
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
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.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.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
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) {
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
},
)
}
@OptIn(ExperimentalStdlibApi::class)
@SuppressLint("RestrictedApi")
override suspend fun doWork(): Result {
val workManager = WorkManager.getInstance(context)
if (workManager.isRunning("download") || isStopped) return Result.Failure()
setForegroundSafely()
val notificationUtil = NotificationUtil(App.instance)
val dbManager = DBManager.getInstance(context)
val dao = dbManager.downloadDao
val historyDao = dbManager.historyDao
val commandTemplateDao = dbManager.commandTemplateDao
val logRepo = LogRepository(dbManager.logDao)
val resultRepo = ResultRepository(dbManager.resultDao, commandTemplateDao, context)
val ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
val handler = Handler(Looper.getMainLooper())
val alarmScheduler = AlarmScheduler(context)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val time = System.currentTimeMillis() + 6000
val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList()
val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
val queuedItems = if (priorityItemIDs.isEmpty()) {
dao.getQueuedScheduledDownloadsUntil(time)
}else {
dao.getQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs)
}
// this is needed for observe sources call, so it wont create result items
// [removed]
//val createResultItem = inputData.getBoolean("createResultItem", true)
val confTmp = Configuration(context.resources.configuration)
val locale = if (Build.VERSION.SDK_INT < 33) {
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
}else{
Locale.getDefault().language
}.run {
split("-")
}.run {
if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
}
confTmp.setLocale(locale)
val metrics = DisplayMetrics()
val resources = Resources(context.assets, metrics, confTmp)
val openQueueIntent = Intent(context, MainActivity::class.java)
openQueueIntent.setAction(Intent.ACTION_VIEW)
openQueueIntent.putExtra("destination", "Queue")
val openDownloadQueue = PendingIntent.getActivity(
context,
1000000000,
openQueueIntent,
PendingIntent.FLAG_IMMUTABLE
)
queuedItems.collectLatest { items ->
if (this@DownloadWorker.isStopped) return@collectLatest
runningYTDLInstances.clear()
val activeDownloads = dao.getActiveDownloadsList()
activeDownloads.forEach {
runningYTDLInstances.add(it.id)
}
val running = ArrayList(runningYTDLInstances)
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (items.isEmpty() && running.isEmpty()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collectLatest
}
if (useScheduler){
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collectLatest
}
}
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collectLatest
}
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
val eligibleDownloads = if (priorityItemIDs.isNotEmpty()) {
val tmp = priorityItemIDs.take(concurrentDownloads)
items.filter { it.id !in running && tmp.contains(it.id) }
}else{
items.take(concurrentDownloads).filter { it.id !in running }
}
eligibleDownloads.forEach{downloadItem ->
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
notificationUtil.notify(downloadItem.id.toInt(), notification)
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())
val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem)
downloadItem.status = DownloadRepository.Status.Active.toString()
CoroutineScope(Dispatchers.IO).launch {
delay(1500)
//update item if its incomplete
resultRepo.updateDownloadItem(downloadItem)?.apply {
val status = dao.checkStatus(this.id)
if (status == DownloadRepository.Status.Active){
dao.updateWithoutUpsert(this)
}
}
}
val cacheDir = FileUtil.getCachePath(context)
val tempFileDir = File(cacheDir, downloadItem.id.toString())
tempFileDir.delete()
tempFileDir.mkdirs()
val downloadLocation = downloadItem.downloadPath
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !downloadItem.incognito
val commandString = ytdlpUtil.parseYTDLRequestString(request)
val initialLogDetails = "Downloading:\n" +
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
"Command:\n$commandString \n\n"
val logString = StringBuilder(initialLogDetails)
val logItem = LogItem(
0,
downloadItem.title.ifBlank { downloadItem.url },
logString.toString(),
downloadItem.format,
downloadItem.type,
System.currentTimeMillis(),
)
runBlocking {
if (logDownloads) logItem.id = logRepo.insert(logItem)
downloadItem.logID = logItem.id
dao.update(downloadItem)
}
val eventBus = EventBus.getDefault()
runCatching {
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID))
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
notificationUtil.updateDownloadNotification(
downloadItem.id.toInt(),
line, progress.toInt(), 0, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
CoroutineScope(Dispatchers.IO).launch {
if (logDownloads) {
logRepo.update(line, logItem.id)
logString.append("$line\n")
}
}
}
}.onSuccess {
resultRepo.updateDownloadItem(downloadItem)?.apply {
dao.updateWithoutUpsert(this)
}
//val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
var finalPaths = mutableListOf<String>()
if (noCache){
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID))
val outputSequence = it.out.split("\n")
finalPaths =
outputSequence.asSequence()
.filter { it.startsWith("'/storage") }
.map { it.removeSuffix("\n") }
.map { it.removeSurrounding("'", "'") }
.toMutableList()
finalPaths.addAll(
outputSequence.asSequence()
.filter { it.startsWith("[SplitChapters]") && it.contains("Destination: ") }
.map { it.split("Destination: ")[1] }
.map { it.removeSuffix("\n") }
.toList()
)
finalPaths.sortBy { File(it).lastModified() }
finalPaths = finalPaths.distinct().toMutableList()
FileUtil.scanMedia(finalPaths, context)
}else{
//move file from internal to set download directory
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
try {
finalPaths = withContext(Dispatchers.IO){
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
eventBus.post(WorkerProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
}
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
if (finalPaths.isNotEmpty()){
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
}
}catch (e: Exception){
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
}
}
val nonMediaExtensions = mutableListOf<String>().apply {
addAll(context.getStringArray(R.array.thumbnail_containers_values))
addAll(context.getStringArray(R.array.sub_formats_values).filter { it.isNotBlank() })
add("description")
add("txt")
}
finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList()
FileUtil.deleteConfigFiles(request)
//put download in history
if (!downloadItem.incognito) {
if (request.hasOption("--download-archive") && finalPaths.isEmpty()) {
handler.postDelayed({
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}, 100)
}else{
if (finalPaths.isNotEmpty()) {
val unixTime = System.currentTimeMillis() / 1000
finalPaths.first().apply {
val file = File(this)
var duration = downloadItem.duration
val d = file.getMediaDuration(context)
if (d > 0) duration = d.toStringDuration(Locale.US)
downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension
downloadItem.duration = duration
}
val historyItem = HistoryItem(0,
downloadItem.url,
downloadItem.title,
downloadItem.author,
downloadItem.duration,
downloadItem.thumb,
downloadItem.type,
unixTime,
finalPaths,
downloadItem.website,
downloadItem.format,
downloadItem.format.filesize,
downloadItem.id,
commandString)
historyDao.insert(historyItem)
}
}
}
withContext(Dispatchers.Main) {
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished(
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.isEmpty()) null else finalPaths, resources
)
}
// if (wasQuickDownloaded && createResultItem){
// runCatching {
// eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
// runBlocking {
// infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
// if (res != null) {
// resultDao.insert(res)
// }
// }
// }
// }
// }
dao.delete(downloadItem.id)
if (logDownloads){
logRepo.update(initialLogDetails + it.out, logItem.id, true)
}
}
}.onFailure {
FileUtil.deleteConfigFiles(request)
withContext(Dispatchers.Main){
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
}
if (this@DownloadWorker.isStopped) return@onFailure
if (it is YoutubeDL.CanceledException) return@onFailure
if (it.message?.contains("JSONDecodeError") == true) {
val cachePath = "${FileUtil.getCachePath(context)}infojsons"
val infoJsonName = MessageDigest.getInstance("MD5").digest(downloadItem.url.toByteArray()).toHexString()
FileUtil.deleteFile("${cachePath}/${infoJsonName}.info.json")
}
if(it.message != null){
if (logDownloads){
logRepo.update(it.message!!, logItem.id)
}else{
logString.append("${it.message}\n")
logItem.content = logString.toString()
val logID = logRepo.insert(logItem)
downloadItem.logID = logID
}
}
tempFileDir.delete()
handler.postDelayed({
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
}, 1000)
Log.e(TAG, context.getString(R.string.failed_download), it)
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
downloadItem.status = DownloadRepository.Status.Error.toString()
runBlocking {
dao.update(downloadItem)
}
notificationUtil.createDownloadErrored(
downloadItem.id,
downloadItem.title.ifEmpty { downloadItem.url },
it.message,
downloadItem.logID,
resources
)
eventBus.post(WorkerProgress(100, it.toString(), downloadItem.id, downloadItem.logID))
}
}
}
if (eligibleDownloads.isNotEmpty()){
eligibleDownloads.forEach {
it.status = DownloadRepository.Status.Active.toString()
priorityItemIDs.remove(it.id)
}
dao.updateMultiple(eligibleDownloads)
}
}
return Result.success()
}
companion object {
val runningYTDLInstances: MutableList<Long> = mutableListOf()
const val TAG = "DownloadWorker"
}
class WorkerProgress(
val progress: Int,
val output: String,
val downloadItemID: Long,
val logItemID: Long?
)
}

View file

@ -22,8 +22,6 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
import com.deniscerri.ytdl.util.FileUtil
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.YoutubeDLRequest
import kotlinx.coroutines.CoroutineScope
@ -124,7 +122,7 @@ class TerminalDownloadWorker(
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
runBlocking {
eventBus.post(DownloadManager.DownloadProgress(progress.toInt(), line, itemId.toLong(), logItem.id))
eventBus.post(DownloadWorker.WorkerProgress(progress.toInt(), line, itemId.toLong(), logItem.id))
}
val title: String = command.take(65)
@ -144,7 +142,7 @@ class TerminalDownloadWorker(
//move file from internal to set download directory
try {
FileUtil.moveFile(File(FileUtil.getCachePath(context) + "/TERMINAL/" + itemId),context, downloadLocation!!, false){ p ->
eventBus.post(DownloadManager.DownloadProgress(p, "", itemId.toLong(), logItem.id))
eventBus.post(DownloadWorker.WorkerProgress(p, "", itemId.toLong(), logItem.id))
}
}catch (e: Exception){
e.printStackTrace()

View file

@ -1,479 +0,0 @@
package com.deniscerri.ytdl.work.downloader
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
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 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.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.flow.distinctUntilChanged
import kotlinx.coroutines.isActive
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 DownloadManager private constructor() {
private var notificationUtil: NotificationUtil = NotificationUtil(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 historyDao: HistoryDao
private lateinit var commandTemplateDao: CommandTemplateDao
private lateinit var logRepo: LogRepository
private lateinit var resultRepo: ResultRepository
private lateinit var ytdlpUtil: YTDLPUtil
private lateinit var alarmScheduler : AlarmScheduler
private lateinit var eventBus: EventBus
private lateinit var resources: Resources
private lateinit var openDownloadQueue: PendingIntent
private lateinit var queueState : Flow<List<DownloadItem>>
private var observeJob : Job? = null
private val handler = Handler(Looper.getMainLooper())
val isRunning get() = observeJob?.isActive == true
fun cancelDownload(id: Long) {
YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.cancelDownloadNotification(id.toInt())
}
fun cancelAll() {
observeJob?.cancel()
observeJob = null
val activeDownloads = dao.getActiveDownloadsList()
activeDownloads.forEach {
cancelDownload(it.id)
}
}
//only call from download worker
fun startDownload(
context: Context, //worker context
priorityItems: List<Long> = listOf(),
continueAfterPriorityIds : Boolean = true
) {
val priorityItemIDs = priorityItems.toMutableList()
//init
notificationUtil = NotificationUtil(context)
dbManager = DBManager.getInstance(context)
dao = dbManager.downloadDao
historyDao = dbManager.historyDao
commandTemplateDao = dbManager.commandTemplateDao
logRepo = LogRepository(dbManager.logDao)
resultRepo = ResultRepository(dbManager.resultDao, commandTemplateDao, context)
ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
alarmScheduler = AlarmScheduler(context)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
eventBus = EventBus.getDefault()
sharedPreferences.edit().putStringSet("ytdlp_active_downloads", setOf()).apply()
val confTmp = Configuration(context.resources.configuration)
val locale = if (Build.VERSION.SDK_INT < 33) {
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
}else{
Locale.getDefault().language
}.run {
split("-")
}.run {
if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
}
confTmp.setLocale(locale)
val metrics = DisplayMetrics()
resources = Resources(context.assets, metrics, confTmp)
openDownloadQueue = PendingIntent.getActivity(
context,
1000000000,
Intent(context, MainActivity::class.java).run {
action = Intent.ACTION_VIEW
putExtra("destination", "Queue")
},
PendingIntent.FLAG_IMMUTABLE
)
val time = System.currentTimeMillis() + 6000
queueState = if (priorityItemIDs.isEmpty()) {
dao.getActiveQueuedScheduledDownloadsUntil(time)
}else {
dao.getActiveQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs)
}
observeJob = CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
supervisorScope {
queueState.distinctUntilChanged().collectLatest { queue ->
val activeDownloads = queue
.asSequence()
.filter { it.status == DownloadRepository.Status.Active.toString() }
.map { it.id }
val activeDownloadsCount = activeDownloads.count()
val queueItems = queue
.asSequence()
.filter { it.status != DownloadRepository.Status.Active.toString() }
val queueCount = queueItems.count()
if (queueCount == 0 && activeDownloadsCount == 0) {
observeJob?.cancel()
return@collectLatest
}
if (!isRunning) {
observeJob?.cancel()
return@collectLatest
}
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (useScheduler){
if (queueItems.none{ it.downloadStartTime > 0L } && activeDownloadsCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
observeJob?.cancel()
return@collectLatest
}
}
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
}
}
}
}
}
@OptIn(ExperimentalStdlibApi::class)
private fun launchDownloadJob(context: Context, 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())
val request = ytdlpUtil.buildYoutubeDLRequest(downloadItem)
val cacheDir = FileUtil.getCachePath(context)
val tempFileDir = File(cacheDir, downloadItem.id.toString())
tempFileDir.delete()
tempFileDir.mkdirs()
val downloadLocation = downloadItem.downloadPath
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !downloadItem.incognito
val commandString = ytdlpUtil.parseYTDLRequestString(request)
val initialLogDetails = "Downloading:\n" +
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
"Command:\n$commandString \n\n"
val logString = StringBuilder(initialLogDetails)
val logItem = LogItem(
0,
downloadItem.title.ifBlank { downloadItem.url },
logString.toString(),
downloadItem.format,
downloadItem.type,
System.currentTimeMillis(),
)
runBlocking {
if (logDownloads) logItem.id = logRepo.insert(logItem)
downloadItem.logID = logItem.id
downloadItem.status = DownloadRepository.Status.Active.toString()
dao.update(downloadItem)
CoroutineScope(Dispatchers.IO).launch {
delay(1500)
//update item if its incomplete
resultRepo.updateDownloadItem(downloadItem)?.apply {
val status = dao.checkStatus(id)
if (status == DownloadRepository.Status.Active){
dao.updateWithoutUpsert(this)
}
}
}
}
runCatching {
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
eventBus.post(DownloadProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID))
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
notificationUtil.updateDownloadNotification(
downloadItem.id.toInt(),
line, progress.toInt(), 0, title,
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
)
CoroutineScope(Dispatchers.IO).launch {
if (logDownloads) {
logRepo.update(line, logItem.id)
logString.append("$line\n")
}
}
}
}.onSuccess {
resultRepo.updateDownloadItem(downloadItem)?.apply {
dao.updateWithoutUpsert(this)
}
//val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
var finalPaths = mutableListOf<String>()
if (noCache){
eventBus.post(DownloadProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID))
val outputSequence = it.out.split("\n")
finalPaths =
outputSequence.asSequence()
.filter { it.startsWith("'/storage") }
.map { it.removeSuffix("\n") }
.map { it.removeSurrounding("'", "'") }
.toMutableList()
finalPaths.addAll(
outputSequence.asSequence()
.filter { it.startsWith("[SplitChapters]") && it.contains("Destination: ") }
.map { it.split("Destination: ")[1] }
.map { it.removeSuffix("\n") }
.toList()
)
finalPaths.sortBy { File(it).lastModified() }
finalPaths = finalPaths.distinct().toMutableList()
FileUtil.scanMedia(finalPaths, context)
}else{
//move file from internal to set download directory
eventBus.post(DownloadProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
try {
finalPaths = withContext(Dispatchers.IO){
FileUtil.moveFile(tempFileDir.absoluteFile,
context, downloadLocation, keepCache){ p ->
eventBus.post(DownloadProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
}
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
if (finalPaths.isNotEmpty()){
eventBus.post(DownloadProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
}
}catch (e: Exception){
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
}
}
val nonMediaExtensions = mutableListOf<String>().apply {
addAll(context.getStringArray(R.array.thumbnail_containers_values))
addAll(context.getStringArray(R.array.sub_formats_values).filter { it.isNotBlank() })
add("description")
add("txt")
}
finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList()
FileUtil.deleteConfigFiles(request)
//put download in history
if (!downloadItem.incognito) {
if (request.hasOption("--download-archive") && finalPaths.isEmpty()) {
handler.postDelayed({
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}, 100)
}else{
if (finalPaths.isNotEmpty()) {
val unixTime = System.currentTimeMillis() / 1000
finalPaths.first().apply {
val file = File(this)
var duration = downloadItem.duration
val d = file.getMediaDuration(context)
if (d > 0) duration = d.toStringDuration(Locale.US)
downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension
downloadItem.duration = duration
}
val historyItem = HistoryItem(0,
downloadItem.url,
downloadItem.title,
downloadItem.author,
downloadItem.duration,
downloadItem.thumb,
downloadItem.type,
unixTime,
finalPaths,
downloadItem.website,
downloadItem.format,
downloadItem.format.filesize,
downloadItem.id,
commandString)
historyDao.insert(historyItem)
}
}
}
withContext(Dispatchers.Main) {
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished(
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.isEmpty()) null else finalPaths, resources
)
}
// if (wasQuickDownloaded && createResultItem){
// runCatching {
// eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
// runBlocking {
// infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
// if (res != null) {
// resultDao.insert(res)
// }
// }
// }
// }
// }
dao.delete(downloadItem.id)
if (logDownloads){
logRepo.update(initialLogDetails + it.out, logItem.id, true)
}
}
}.onFailure {
FileUtil.deleteConfigFiles(request)
withContext(Dispatchers.Main){
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
}
if (!this.isActive) return@onFailure
if (it is YoutubeDL.CanceledException) return@onFailure
if (it.message?.contains("JSONDecodeError") == true) {
val cachePath = "${FileUtil.getCachePath(context)}infojsons"
val infoJsonName = MessageDigest.getInstance("MD5").digest(downloadItem.url.toByteArray()).toHexString()
FileUtil.deleteFile("${cachePath}/${infoJsonName}.info.json")
}
if(it.message != null){
if (logDownloads){
logRepo.update(it.message!!, logItem.id)
}else{
logString.append("${it.message}\n")
logItem.content = logString.toString()
val logID = logRepo.insert(logItem)
downloadItem.logID = logID
}
}
tempFileDir.delete()
handler.postDelayed({
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
}, 1000)
Log.e(TAG, context.getString(R.string.failed_download), it)
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
downloadItem.status = DownloadRepository.Status.Error.toString()
runBlocking {
dao.update(downloadItem)
}
notificationUtil.createDownloadErrored(
downloadItem.id,
downloadItem.title.ifEmpty { downloadItem.url },
it.message,
downloadItem.logID,
resources
)
eventBus.post(DownloadProgress(100, it.toString(), downloadItem.id, downloadItem.logID))
}
}
companion object {
const val TAG = "DownloadManager"
@Volatile
private var instance: DownloadManager? = null
fun getInstance() : DownloadManager {
return instance ?: synchronized(this) {
instance ?: DownloadManager().also {
instance = it
}
}
}
}
class DownloadProgress(
val progress: Int,
val output: String,
val downloadItemID: Long,
val logItemID: Long?
)
}

View file

@ -1,104 +0,0 @@
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"
}
}