fix downloads not continuing in the queue

This commit is contained in:
deniscerri 2025-03-22 21:37:08 +01:00
parent 78fd2427eb
commit 3b40ba7e02
No known key found for this signature in database
GPG key ID: 95C43D517D830350
19 changed files with 446 additions and 415 deletions

View file

@ -503,7 +503,7 @@
<service <service
android:name="androidx.work.impl.foreground.SystemForegroundService" android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="specialUse"/> android:foregroundServiceType="dataSync"/>
</application> </application>
</manifest> </manifest>

View file

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

View file

@ -3,11 +3,7 @@ package com.deniscerri.ytdl.database.repository
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Handler
import android.os.Looper
import android.text.format.DateFormat import android.text.format.DateFormat
import android.widget.Toast
import androidx.core.os.postDelayed
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -16,9 +12,7 @@ import androidx.work.Data
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.await
import com.deniscerri.ytdl.App import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.dao.DownloadDao import com.deniscerri.ytdl.database.dao.DownloadDao
@ -31,7 +25,6 @@ import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.DownloadWorker import com.deniscerri.ytdl.work.DownloadWorker
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale

View file

@ -3,14 +3,11 @@ package com.deniscerri.ytdl.receiver
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.preference.PreferenceManager
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
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.CancelScheduledDownloadWorker import com.deniscerri.ytdl.work.CancelScheduledDownloadWorker
import com.deniscerri.ytdl.work.DownloadWorker
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class CancelScheduleAlarmReceiver : BroadcastReceiver() { class CancelScheduleAlarmReceiver : BroadcastReceiver() {

View file

@ -12,7 +12,6 @@ import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -20,8 +19,6 @@ import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.core.net.toUri
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider

View file

@ -1,80 +1,42 @@
package com.deniscerri.ytdl.ui.downloads package com.deniscerri.ytdl.ui.downloads
import android.animation.AnimatorSet
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.DialogInterface
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom
import androidx.core.view.setMargins
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.room.util.getColumnIndexOrThrow
import androidx.work.WorkManager import androidx.work.WorkManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
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.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.ui.adapter.QueuedDownloadAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.work.DownloadWorker import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe

View file

@ -6,8 +6,6 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.interaction.DragInteraction
import androidx.core.content.edit
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference

View file

@ -15,7 +15,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.TerminalItem import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel

View file

@ -40,20 +40,15 @@ import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
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.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.work.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
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
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
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.properties.Delegates import kotlin.properties.Delegates

View file

@ -1280,22 +1280,24 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
val poTokens = mutableListOf<String>() val poTokens = mutableListOf<String>()
val configuredPlayerClientsRaw = sharedPreferences.getString("youtube_player_clients", "[]")!! val configuredPlayerClientsRaw = sharedPreferences.getString("youtube_player_clients", "[]")!!
val configuredPlayerClients = Gson().fromJson(configuredPlayerClientsRaw, Array<YoutubePlayerClientItem>::class.java).toMutableList() kotlin.runCatching {
val configuredPlayerClients = Gson().fromJson(configuredPlayerClientsRaw, Array<YoutubePlayerClientItem>::class.java).toMutableList()
for (value in configuredPlayerClients) { for (value in configuredPlayerClients) {
if (value.enabled) { if (value.enabled) {
if (!value.useOnlyPoToken) { if (!value.useOnlyPoToken) {
playerClients.add(value.playerClient) playerClients.add(value.playerClient)
} }
var canUsePoToken = true var canUsePoToken = true
if (value.urlRegex.isNotEmpty() && url != null) { if (value.urlRegex.isNotEmpty() && url != null) {
canUsePoToken = value.urlRegex.any { url.matches(it.toRegex()) } canUsePoToken = value.urlRegex.any { url.matches(it.toRegex()) }
} }
if (canUsePoToken) { if (canUsePoToken) {
value.poTokens.forEach { pt -> value.poTokens.forEach { pt ->
poTokens.add("${value.playerClient}.${pt.context}+${pt.token}") poTokens.add("${value.playerClient}.${pt.context}+${pt.token}")
}
} }
} }
} }
@ -1308,24 +1310,26 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
} }
val generatedPoTokensRaw = sharedPreferences.getString("youtube_generated_po_tokens", "[]") val generatedPoTokensRaw = sharedPreferences.getString("youtube_generated_po_tokens", "[]")
val generatedPoTokens = Gson().fromJson(generatedPoTokensRaw,Array<YoutubeGeneratePoTokenItem>::class.java).toMutableList() kotlin.runCatching {
if (generatedPoTokens.isNotEmpty()) { val generatedPoTokens = Gson().fromJson(generatedPoTokensRaw,Array<YoutubeGeneratePoTokenItem>::class.java).toMutableList()
for (value in generatedPoTokens) { if (generatedPoTokens.isNotEmpty()) {
if (value.enabled) { for (value in generatedPoTokens) {
for (cl in value.clients) { if (value.enabled) {
playerClients.add(cl) for (cl in value.clients) {
for (pt in value.poTokens) { playerClients.add(cl)
if (pt.token.isNotBlank()) { for (pt in value.poTokens) {
poTokens.add("${cl}.${pt.context}+${pt.token}") if (pt.token.isNotBlank()) {
poTokens.add("${cl}.${pt.context}+${pt.token}")
}
} }
} }
}
if (dataSyncID.isBlank() && value.useVisitorData) { if (dataSyncID.isBlank() && value.useVisitorData) {
extractorArgs.add("player_skip=webpage,configs") extractorArgs.add("player_skip=webpage,configs")
extractorArgs.add("visitor_data=${value.visitorData}") extractorArgs.add("visitor_data=${value.visitorData}")
} }
}
} }
} }
} }

View file

@ -3,7 +3,7 @@ package com.deniscerri.ytdl.work
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.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.Handler import android.os.Handler

View file

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work package com.deniscerri.ytdl.work
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
@ -26,7 +26,7 @@ class CleanUpLeftoverDownloads(
val notification = notificationUtil.createDeletingLeftoverDownloadsNotification() val notification = notificationUtil.createDeletingLeftoverDownloadsNotification()
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
}else{ }else{
setForegroundAsync(ForegroundInfo(id, notification)) setForegroundAsync(ForegroundInfo(id, notification))
} }

View file

@ -4,7 +4,8 @@ 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.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE 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
@ -16,7 +17,6 @@ import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
@ -24,6 +24,10 @@ import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.DBManager 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.HistoryItem
import com.deniscerri.ytdl.database.models.LogItem import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.repository.DownloadRepository
@ -36,11 +40,23 @@ import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.extractors.YTDLPUtil import com.deniscerri.ytdl.util.extractors.YTDLPUtil
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.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File
@ -48,45 +64,68 @@ import java.security.MessageDigest
import java.util.Locale import java.util.Locale
class DownloadWorker( class DownloadWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
private val context: Context, private lateinit var dao : DownloadDao
workerParams: WorkerParameters private lateinit var notificationUtil: NotificationUtil
) : CoroutineWorker(context, workerParams) { private lateinit var dbManager: DBManager
@OptIn(ExperimentalStdlibApi::class) 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 sharedPreferences : SharedPreferences
private lateinit var resources: Resources
private lateinit var workManager: WorkManager
private lateinit var eventBus: EventBus
private lateinit var queueState : Flow<List<DownloadItem>>
private val handler = Handler(Looper.getMainLooper())
private val openDownloadQueue: PendingIntent = PendingIntent.getActivity(
context,
1000000000,
Intent(context, MainActivity::class.java).run {
action = Intent.ACTION_VIEW
putExtra("destination", "Queue")
},
PendingIntent.FLAG_IMMUTABLE
)
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
},
)
}
private fun cancelSelf() {
workManager.cancelWorkById(this@DownloadWorker.id)
}
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if (isStopped) return Result.success() //init
val workManager = WorkManager.getInstance(context) notificationUtil = NotificationUtil(App.instance)
val currentWork = withContext(Dispatchers.IO) { dbManager = DBManager.getInstance(context)
workManager.getWorkInfosByTag("download").get() dao = dbManager.downloadDao
} historyDao = dbManager.historyDao
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) { commandTemplateDao = dbManager.commandTemplateDao
return Result.success() logRepo = LogRepository(dbManager.logDao)
} resultRepo = ResultRepository(dbManager.resultDao, commandTemplateDao, context)
ytdlpUtil = YTDLPUtil(context, commandTemplateDao)
val notificationUtil = NotificationUtil(App.instance) alarmScheduler = AlarmScheduler(context)
val dbManager = DBManager.getInstance(context) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val dao = dbManager.downloadDao eventBus = EventBus.getDefault()
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 confTmp = Configuration(context.resources.configuration)
val locale = if (Build.VERSION.SDK_INT < 33) { val locale = if (Build.VERSION.SDK_INT < 33) {
@ -100,240 +139,265 @@ class DownloadWorker(
} }
confTmp.setLocale(locale) confTmp.setLocale(locale)
val metrics = DisplayMetrics() val metrics = DisplayMetrics()
val resources = Resources(context.assets, metrics, confTmp) resources = Resources(context.assets, metrics, confTmp)
val openQueueIntent = Intent(context, MainActivity::class.java) workManager = WorkManager.getInstance(context)
openQueueIntent.setAction(Intent.ACTION_VIEW) if (workManager.isRunning("download")) return Result.Failure()
openQueueIntent.putExtra("destination", "Queue")
val openDownloadQueue = PendingIntent.getActivity(
context,
1000000000,
openQueueIntent,
PendingIntent.FLAG_IMMUTABLE
)
val workNotif = notificationUtil.createDefaultWorkerNotification() setForegroundSafely()
val notificationID = 1000000000
if (Build.VERSION.SDK_INT > 33) { val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList()
setForegroundAsync(ForegroundInfo(notificationID, workNotif, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
}else{ val time = System.currentTimeMillis() + 6000
setForegroundAsync(ForegroundInfo(notificationID, workNotif)) queueState = if (priorityItemIDs.isEmpty()) {
dao.getActiveQueuedScheduledDownloadsUntil(time)
}else {
dao.getActiveQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs)
} }
queuedItems.collectLatest { items -> val downloadJobs = mutableMapOf<Long, Job>()
if (this@DownloadWorker.isStopped) return@collectLatest
runningYTDLInstances.clear() queueState.collectLatest { queue ->
val activeDownloads = dao.getActiveDownloadsList() val runningCount = queue.asSequence()
activeDownloads.forEach { .filter { it.status == DownloadRepository.Status.Active.toString() }
runningYTDLInstances.add(it.id) .count()
}
val running = ArrayList(runningYTDLInstances) val queueItems = queue.asSequence()
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) .filter { it.status != DownloadRepository.Status.Active.toString() }
if (items.isEmpty() && running.isEmpty()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) val queueCount = queueItems.count()
if (queueCount == 0 && runningCount == 0) {
cancelSelf()
return@collectLatest return@collectLatest
} }
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (useScheduler){ if (useScheduler){
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) { if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) cancelSelf()
return@collectLatest return@collectLatest
} }
} }
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) { val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id) if (concurrentDownloads > 0) {
return@collectLatest //free spots are open
} if (priorityItemIDs.isNotEmpty()) {
if (!continueAfterPriorityIds && queueItems.none { priorityItemIDs.contains(it.id) }) {
//dont queue any more items if only required priority items
cancelSelf()
return@collectLatest
}
}
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size // Use supervisorScope to cancel child jobs when the downloader job is cancelled
val eligibleDownloads = if (priorityItemIDs.isNotEmpty()) { supervisorScope {
val tmp = priorityItemIDs.take(concurrentDownloads) val queuedDownloads = queueItems
items.filter { it.id !in running && tmp.contains(it.id) } .toList()
}else{ .take(concurrentDownloads)
items.take(concurrentDownloads).filter { it.id !in running }
}
eligibleDownloads.forEach{downloadItem -> val queuedIDs = queuedDownloads.map { it.id }
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url }) val downloadJobsToStop = downloadJobs.filter { it.key !in queuedIDs }
notificationUtil.notify(downloadItem.id.toInt(), notification) downloadJobsToStop.forEach { (download, job) ->
job.cancel()
CoroutineScope(Dispatchers.IO).launch { downloadJobs.remove(download)
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 downloadsToStart = queuedDownloads.filter { it.id !in downloadJobs }
val tempFileDir = File(cacheDir, downloadItem.id.toString()) downloadsToStart.forEach { download ->
tempFileDir.delete() downloadJobs[download.id] = launchDownloadJob(download)
tempFileDir.mkdirs() }
}
}
val downloadLocation = downloadItem.downloadPath }
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !downloadItem.incognito return Result.Success()
}
val commandString = ytdlpUtil.parseYTDLRequestString(request)
val initialLogDetails = "Downloading:\n" + @OptIn(DelicateCoroutinesApi::class)
"Title: ${downloadItem.title}\n" + private fun launchIO(block: suspend CoroutineScope.() -> Unit): Job =
"URL: ${downloadItem.url}\n" + GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block)
"Type: ${downloadItem.type}\n" +
"Command:\n$commandString \n\n" @OptIn(ExperimentalStdlibApi::class)
val logString = StringBuilder(initialLogDetails) private fun launchDownloadJob(downloadItem: DownloadItem) = launchIO {
val logItem = LogItem( val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
0, notificationUtil.notify(downloadItem.id.toInt(), notification)
downloadItem.title.ifBlank { downloadItem.url },
logString.toString(), val writtenPath = downloadItem.format.format_note.contains("-P ")
downloadItem.format, val noCache = writtenPath || (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite())
downloadItem.type,
System.currentTimeMillis(), 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(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()
runBlocking { if (finalPaths.isNotEmpty()){
if (logDownloads) logItem.id = logRepo.insert(logItem) eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id, downloadItem.logID))
downloadItem.logID = logItem.id }
dao.update(downloadItem) }catch (e: Exception){
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
} }
}
val eventBus = EventBus.getDefault()
runCatching { val nonMediaExtensions = mutableListOf<String>().apply {
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString()) addAll(context.getStringArray(R.array.thumbnail_containers_values))
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> addAll(context.getStringArray(R.array.sub_formats_values).filter { it.isNotBlank() })
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id, downloadItem.logID)) add("description")
val title: String = downloadItem.title.ifEmpty { downloadItem.url } add("txt")
notificationUtil.updateDownloadNotification( }
downloadItem.id.toInt(), finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList()
line, progress.toInt(), 0, title, FileUtil.deleteConfigFiles(request)
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
) //put download in history
CoroutineScope(Dispatchers.IO).launch { if (!downloadItem.incognito) {
if (logDownloads) { if (request.hasOption("--download-archive") && finalPaths.isEmpty()) {
logRepo.update(line, logItem.id) handler.postDelayed({
logString.append("$line\n") 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)
} }
}.onSuccess { }
resultRepo.updateDownloadItem(downloadItem)?.apply { }
dao.updateWithoutUpsert(this)
}
//val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
var finalPaths = mutableListOf<String>()
if (noCache){ withContext(Dispatchers.Main) {
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id, downloadItem.logID)) notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
val outputSequence = it.out.split("\n") notificationUtil.createDownloadFinished(
finalPaths = downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.isEmpty()) null else finalPaths, resources
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){ // if (wasQuickDownloaded && createResultItem){
// runCatching { // runCatching {
@ -348,80 +412,63 @@ class DownloadWorker(
// } // }
// } // }
dao.delete(downloadItem.id) dao.delete(downloadItem.id)
if (logDownloads){ if (logDownloads){
logRepo.update(initialLogDetails + it.out, logItem.id, true) 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()){ }.onFailure {
eligibleDownloads.forEach { FileUtil.deleteConfigFiles(request)
it.status = DownloadRepository.Status.Active.toString() withContext(Dispatchers.Main){
priorityItemIDs.remove(it.id) notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
}
dao.updateMultiple(eligibleDownloads)
} }
if (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))
} }
return Result.success()
} }
companion object { companion object {
val runningYTDLInstances: MutableList<Long> = mutableListOf()
const val TAG = "DownloadWorker" const val TAG = "DownloadWorker"
} }

View file

@ -3,7 +3,7 @@ package com.deniscerri.ytdl.work
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.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.Handler import android.os.Handler
@ -42,7 +42,7 @@ class MoveCacheFilesWorker(
val notification = notificationUtil.createMoveCacheFilesNotification(pendingIntent, NotificationUtil.DOWNLOAD_MISC_CHANNEL_ID) val notification = notificationUtil.createMoveCacheFilesNotification(pendingIntent, NotificationUtil.DOWNLOAD_MISC_CHANNEL_ID)
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
}else{ }else{
setForegroundAsync(ForegroundInfo(id, notification)) setForegroundAsync(ForegroundInfo(id, notification))
} }

View file

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work package com.deniscerri.ytdl.work
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -57,7 +57,7 @@ class ObserveSourceWorker(
val workerID = System.currentTimeMillis().toInt() val workerID = System.currentTimeMillis().toInt()
val notification = notificationUtil.createObserveSourcesNotification(item.name) val notification = notificationUtil.createObserveSourcesNotification(item.name)
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
setForegroundAsync(ForegroundInfo(workerID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) setForegroundAsync(ForegroundInfo(workerID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
}else{ }else{
setForegroundAsync(ForegroundInfo(workerID, notification)) setForegroundAsync(ForegroundInfo(workerID, notification))
} }

View file

@ -3,7 +3,7 @@ package com.deniscerri.ytdl.work
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.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -53,7 +53,7 @@ class TerminalDownloadWorker(
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), NotificationUtil.DOWNLOAD_TERMINAL_RUNNING_NOTIFICATION_ID) val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), NotificationUtil.DOWNLOAD_TERMINAL_RUNNING_NOTIFICATION_ID)
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
setForegroundAsync(ForegroundInfo(itemId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) setForegroundAsync(ForegroundInfo(itemId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
}else{ }else{
setForegroundAsync(ForegroundInfo(itemId, notification)) setForegroundAsync(ForegroundInfo(itemId, notification))
} }

View file

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work package com.deniscerri.ytdl.work
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.Worker import androidx.work.Worker
@ -32,7 +32,7 @@ class UpdateMultipleDownloadsDataWorker(
val notification = notificationUtil.createDataUpdateNotification() val notification = notificationUtil.createDataUpdateNotification()
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
setForegroundAsync(ForegroundInfo(workID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) setForegroundAsync(ForegroundInfo(workID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
}else{ }else{
setForegroundAsync(ForegroundInfo(workID, notification)) setForegroundAsync(ForegroundInfo(workID, notification))
} }

View file

@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work package com.deniscerri.ytdl.work
import android.content.Context import android.content.Context
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.os.Build import android.os.Build
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.Worker import androidx.work.Worker
@ -33,7 +33,7 @@ class UpdateMultipleDownloadsFormatsWorker(
val notification = notificationUtil.createFormatsUpdateNotification() val notification = notificationUtil.createFormatsUpdateNotification()
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
setForegroundAsync(ForegroundInfo(workID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)) setForegroundAsync(ForegroundInfo(workID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC))
}else{ }else{
setForegroundAsync(ForegroundInfo(workID, notification)) setForegroundAsync(ForegroundInfo(workID, notification))
} }

View file

@ -0,0 +1,34 @@
package com.deniscerri.ytdl.work
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.delay
val Context.workManager: WorkManager
get() = WorkManager.getInstance(this)
fun WorkManager.isRunning(tag: String): Boolean {
val list = this.getWorkInfosByTag(tag).get()
return list.count { it.state == WorkInfo.State.RUNNING } > 1
}
/**
* Makes this worker run in the context of a foreground service.
*
* Note that this function is a no-op if the process is subject to foreground
* service restrictions.
*
* Moving to foreground service context requires the worker to run a bit longer,
* allowing Service.startForeground() to be called and avoiding system crash.
*/
suspend fun CoroutineWorker.setForegroundSafely() {
try {
setForeground(getForegroundInfo())
delay(500)
} catch (e: IllegalStateException) {
Log.e("ERROR", "Not allowed to set foreground job")
}
}