diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3efd5cc8..270dd72f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -503,7 +503,7 @@
+ android:foregroundServiceType="dataSync"/>
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt
index 181b91f6..f39520f8 100644
--- a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt
@@ -107,19 +107,24 @@ interface DownloadDao {
@Query("""
SELECT * FROM downloads
- WHERE status in ('Queued', 'Scheduled') AND downloadStartTime <= :currentTime
+ WHERE status in ('Active', 'Queued', 'Scheduled') AND downloadStartTime <= :currentTime
ORDER BY downloadStartTime, id
- LIMIT 20
+ LIMIT 10
""")
- fun getQueuedScheduledDownloadsUntil(currentTime: Long) : Flow>
+ fun getActiveQueuedScheduledDownloadsUntil(currentTime: Long) : Flow>
@Query("""
SELECT * FROM downloads
- WHERE id in (:priorityItems) AND status in ('Queued', 'Scheduled') AND downloadStartTime <= :currentTime
- ORDER BY downloadStartTime, id
- LIMIT 20
+ WHERE status in ('Active','Queued', 'Scheduled') AND downloadStartTime <= :currentTime
+ ORDER BY
+ CASE
+ WHEN id in (:priorityItems) THEN 0
+ ELSE 1
+ END,
+ downloadStartTime, id
+ LIMIT 10
""")
- fun getQueuedScheduledDownloadsUntilWithPriority(currentTime: Long, priorityItems: List) : Flow>
+ fun getActiveQueuedScheduledDownloadsUntilWithPriority(currentTime: Long, priorityItems: List) : Flow>
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
fun getQueuedDownloadsList() : List
diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt
index 5d89e3c3..5894c125 100644
--- a/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt
@@ -3,11 +3,7 @@ package com.deniscerri.ytdl.database.repository
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
-import android.os.Handler
-import android.os.Looper
import android.text.format.DateFormat
-import android.widget.Toast
-import androidx.core.os.postDelayed
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.preference.PreferenceManager
@@ -16,9 +12,7 @@ import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
-import androidx.work.WorkInfo
import androidx.work.WorkManager
-import androidx.work.await
import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.dao.DownloadDao
@@ -31,7 +25,6 @@ import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.DownloadWorker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
import java.io.File
import java.text.SimpleDateFormat
import java.util.Locale
diff --git a/app/src/main/java/com/deniscerri/ytdl/receiver/CancelScheduleAlarmReceiver.kt b/app/src/main/java/com/deniscerri/ytdl/receiver/CancelScheduleAlarmReceiver.kt
index 590bf6eb..36d00d42 100644
--- a/app/src/main/java/com/deniscerri/ytdl/receiver/CancelScheduleAlarmReceiver.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/receiver/CancelScheduleAlarmReceiver.kt
@@ -3,14 +3,11 @@ package com.deniscerri.ytdl.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import androidx.preference.PreferenceManager
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
-import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.deniscerri.ytdl.work.CancelScheduledDownloadWorker
-import com.deniscerri.ytdl.work.DownloadWorker
import java.util.concurrent.TimeUnit
class CancelScheduleAlarmReceiver : BroadcastReceiver() {
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt
index 333d0334..2aecdb96 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt
@@ -12,7 +12,6 @@ import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
-import android.os.Environment
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
@@ -20,8 +19,6 @@ import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.TextView
-import android.widget.Toast
-import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt
index b16c20e7..dd50f196 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt
@@ -1,80 +1,42 @@
package com.deniscerri.ytdl.ui.downloads
-import android.animation.AnimatorSet
import android.annotation.SuppressLint
import android.app.Activity
-import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.res.ColorStateList
-import android.graphics.Canvas
-import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
-import android.view.animation.AccelerateDecelerateInterpolator
-import android.widget.LinearLayout
-import android.widget.ListView
import android.widget.RelativeLayout
import android.widget.TextView
-import android.widget.Toast
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.res.ResourcesCompat
-import androidx.core.os.bundleOf
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.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
-import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
-import androidx.room.util.getColumnIndexOrThrow
import androidx.work.WorkManager
-import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.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.toListString
-import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil
-import com.deniscerri.ytdl.util.UiUtil
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.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.progressindicator.LinearProgressIndicator
-import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
-import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt
index 6f7f3ad5..7adccfab 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt
@@ -6,8 +6,6 @@ import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.core.content.edit
import androidx.navigation.fragment.findNavController
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt
index 75bfcff7..d759960b 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalDownloadsListFragment.kt
@@ -15,7 +15,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import androidx.work.WorkManager
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalFragment.kt
index 7f643383..dc157150 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/TerminalFragment.kt
@@ -40,20 +40,15 @@ import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil
-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
-import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.slider.Slider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
import kotlin.properties.Delegates
diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt
index ad992f1e..569d3c0e 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt
@@ -1280,22 +1280,24 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
val poTokens = mutableListOf()
val configuredPlayerClientsRaw = sharedPreferences.getString("youtube_player_clients", "[]")!!
- val configuredPlayerClients = Gson().fromJson(configuredPlayerClientsRaw, Array::class.java).toMutableList()
+ kotlin.runCatching {
+ val configuredPlayerClients = Gson().fromJson(configuredPlayerClientsRaw, Array::class.java).toMutableList()
- for (value in configuredPlayerClients) {
- if (value.enabled) {
- if (!value.useOnlyPoToken) {
- playerClients.add(value.playerClient)
- }
+ for (value in configuredPlayerClients) {
+ if (value.enabled) {
+ if (!value.useOnlyPoToken) {
+ playerClients.add(value.playerClient)
+ }
- var canUsePoToken = true
- if (value.urlRegex.isNotEmpty() && url != null) {
- canUsePoToken = value.urlRegex.any { url.matches(it.toRegex()) }
- }
+ var canUsePoToken = true
+ if (value.urlRegex.isNotEmpty() && url != null) {
+ canUsePoToken = value.urlRegex.any { url.matches(it.toRegex()) }
+ }
- if (canUsePoToken) {
- value.poTokens.forEach { pt ->
- poTokens.add("${value.playerClient}.${pt.context}+${pt.token}")
+ if (canUsePoToken) {
+ value.poTokens.forEach { pt ->
+ 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 generatedPoTokens = Gson().fromJson(generatedPoTokensRaw,Array::class.java).toMutableList()
- if (generatedPoTokens.isNotEmpty()) {
- for (value in generatedPoTokens) {
- if (value.enabled) {
- for (cl in value.clients) {
- playerClients.add(cl)
- for (pt in value.poTokens) {
- if (pt.token.isNotBlank()) {
- poTokens.add("${cl}.${pt.context}+${pt.token}")
+ kotlin.runCatching {
+ val generatedPoTokens = Gson().fromJson(generatedPoTokensRaw,Array::class.java).toMutableList()
+ if (generatedPoTokens.isNotEmpty()) {
+ for (value in generatedPoTokens) {
+ if (value.enabled) {
+ for (cl in value.clients) {
+ playerClients.add(cl)
+ for (pt in value.poTokens) {
+ if (pt.token.isNotBlank()) {
+ poTokens.add("${cl}.${pt.context}+${pt.token}")
+ }
}
}
- }
- if (dataSyncID.isBlank() && value.useVisitorData) {
- extractorArgs.add("player_skip=webpage,configs")
- extractorArgs.add("visitor_data=${value.visitorData}")
- }
+ if (dataSyncID.isBlank() && value.useVisitorData) {
+ extractorArgs.add("player_skip=webpage,configs")
+ extractorArgs.add("visitor_data=${value.visitorData}")
+ }
+ }
}
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/AutoGenerateWebPoTokenWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/AutoGenerateWebPoTokenWorker.kt
index 3019f431..724e84bf 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/AutoGenerateWebPoTokenWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/AutoGenerateWebPoTokenWorker.kt
@@ -3,7 +3,7 @@ package com.deniscerri.ytdl.work
import android.app.PendingIntent
import android.content.Context
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.Environment
import android.os.Handler
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt b/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt
index 5834a096..9ca46769 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt
@@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
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 androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
@@ -26,7 +26,7 @@ class CleanUpLeftoverDownloads(
val notification = notificationUtil.createDeletingLeftoverDownloadsNotification()
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{
setForegroundAsync(ForegroundInfo(id, notification))
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt
index 7f5833d1..091b534e 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt
@@ -4,7 +4,8 @@ import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
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.Resources
import android.os.Build
@@ -16,7 +17,6 @@ import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
-import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
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.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
@@ -36,11 +40,23 @@ 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.CoroutineStart
+import kotlinx.coroutines.DelicateCoroutinesApi
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.flow.Flow
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.runBlocking
+import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import java.io.File
@@ -48,45 +64,68 @@ import java.security.MessageDigest
import java.util.Locale
-class DownloadWorker(
- private val context: Context,
- workerParams: WorkerParameters
-) : CoroutineWorker(context, workerParams) {
- @OptIn(ExperimentalStdlibApi::class)
+class DownloadWorker(private val context: Context,workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
+ private lateinit var dao : DownloadDao
+ private lateinit var notificationUtil: NotificationUtil
+ private lateinit var dbManager: DBManager
+ private lateinit var 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>
+
+ 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")
override suspend fun doWork(): Result {
- if (isStopped) return Result.success()
- val workManager = WorkManager.getInstance(context)
- val currentWork = withContext(Dispatchers.IO) {
- workManager.getWorkInfosByTag("download").get()
- }
- if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) {
- return Result.success()
- }
-
- 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)
+ //init
+ notificationUtil = NotificationUtil(App.instance)
+ 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()
val confTmp = Configuration(context.resources.configuration)
val locale = if (Build.VERSION.SDK_INT < 33) {
@@ -100,240 +139,265 @@ class DownloadWorker(
}
confTmp.setLocale(locale)
val metrics = DisplayMetrics()
- val resources = Resources(context.assets, metrics, confTmp)
+ 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
- )
+ workManager = WorkManager.getInstance(context)
+ if (workManager.isRunning("download")) return Result.Failure()
- val workNotif = notificationUtil.createDefaultWorkerNotification()
- val notificationID = 1000000000
- if (Build.VERSION.SDK_INT > 33) {
- setForegroundAsync(ForegroundInfo(notificationID, workNotif, FOREGROUND_SERVICE_TYPE_SPECIAL_USE))
- }else{
- setForegroundAsync(ForegroundInfo(notificationID, workNotif))
+ setForegroundSafely()
+
+ val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList()
+ val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
+ val time = System.currentTimeMillis() + 6000
+ queueState = if (priorityItemIDs.isEmpty()) {
+ dao.getActiveQueuedScheduledDownloadsUntil(time)
+ }else {
+ dao.getActiveQueuedScheduledDownloadsUntilWithPriority(time, priorityItemIDs)
}
- queuedItems.collectLatest { items ->
- if (this@DownloadWorker.isStopped) return@collectLatest
+ val downloadJobs = mutableMapOf()
- runningYTDLInstances.clear()
- val activeDownloads = dao.getActiveDownloadsList()
- activeDownloads.forEach {
- runningYTDLInstances.add(it.id)
- }
+ queueState.collectLatest { queue ->
+ val runningCount = queue.asSequence()
+ .filter { it.status == DownloadRepository.Status.Active.toString() }
+ .count()
- val running = ArrayList(runningYTDLInstances)
- val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
- if (items.isEmpty() && running.isEmpty()) {
- WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
+ val queueItems = queue.asSequence()
+ .filter { it.status != DownloadRepository.Status.Active.toString() }
+
+ val queueCount = queueItems.count()
+ if (queueCount == 0 && runningCount == 0) {
+ cancelSelf()
return@collectLatest
}
+ val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (useScheduler){
- if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
- WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
+ if (queueItems.none{ it.downloadStartTime > 0L } && runningCount == 0 && !alarmScheduler.isDuringTheScheduledTime()) {
+ cancelSelf()
return@collectLatest
}
}
- if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) {
- WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
- return@collectLatest
- }
+ val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - runningCount
+ if (concurrentDownloads > 0) {
+ //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
- 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 }
- }
+ // Use supervisorScope to cancel child jobs when the downloader job is cancelled
+ supervisorScope {
+ val queuedDownloads = queueItems
+ .toList()
+ .take(concurrentDownloads)
- 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 queuedIDs = queuedDownloads.map { it.id }
+ val downloadJobsToStop = downloadJobs.filter { it.key !in queuedIDs }
+ downloadJobsToStop.forEach { (download, job) ->
+ job.cancel()
+ downloadJobs.remove(download)
}
- val cacheDir = FileUtil.getCachePath(context)
- val tempFileDir = File(cacheDir, downloadItem.id.toString())
- tempFileDir.delete()
- tempFileDir.mkdirs()
+ val downloadsToStart = queuedDownloads.filter { it.id !in downloadJobs }
+ downloadsToStart.forEach { download ->
+ downloadJobs[download.id] = launchDownloadJob(download)
+ }
+ }
+ }
- 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" +
- "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(),
+
+ @OptIn(DelicateCoroutinesApi::class)
+ private fun launchIO(block: suspend CoroutineScope.() -> Unit): Job =
+ GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block)
+
+ @OptIn(ExperimentalStdlibApi::class)
+ private fun launchDownloadJob(downloadItem: DownloadItem) = launchIO {
+ val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
+ notificationUtil.notify(downloadItem.id.toInt(), notification)
+
+ val writtenPath = downloadItem.format.format_note.contains("-P ")
+ val 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(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()
+
+ 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 (logDownloads) logItem.id = logRepo.insert(logItem)
- downloadItem.logID = logItem.id
- dao.update(downloadItem)
+ 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 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")
- }
+ val nonMediaExtensions = mutableListOf().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)
}
- }.onSuccess {
- resultRepo.updateDownloadItem(downloadItem)?.apply {
- dao.updateWithoutUpsert(this)
- }
- //val wasQuickDownloaded = resultDao.getCountInt() == 0
- runBlocking {
- var finalPaths = mutableListOf()
+ }
+ }
- 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().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
- )
- }
+ 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 {
@@ -348,80 +412,63 @@ class DownloadWorker(
// }
// }
- dao.delete(downloadItem.id)
+ 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 (logDownloads){
+ logRepo.update(initialLogDetails + it.out, logItem.id, true)
}
}
- if (eligibleDownloads.isNotEmpty()){
- eligibleDownloads.forEach {
- it.status = DownloadRepository.Status.Active.toString()
- priorityItemIDs.remove(it.id)
- }
- dao.updateMultiple(eligibleDownloads)
+ }.onFailure {
+ FileUtil.deleteConfigFiles(request)
+ withContext(Dispatchers.Main){
+ notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
}
+ 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 {
- val runningYTDLInstances: MutableList = mutableListOf()
const val TAG = "DownloadWorker"
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/MoveCacheFilesWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/MoveCacheFilesWorker.kt
index c14add11..fd2400c6 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/MoveCacheFilesWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/MoveCacheFilesWorker.kt
@@ -3,7 +3,7 @@ package com.deniscerri.ytdl.work
import android.app.PendingIntent
import android.content.Context
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.Environment
import android.os.Handler
@@ -42,7 +42,7 @@ class MoveCacheFilesWorker(
val notification = notificationUtil.createMoveCacheFilesNotification(pendingIntent, NotificationUtil.DOWNLOAD_MISC_CHANNEL_ID)
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{
setForegroundAsync(ForegroundInfo(id, notification))
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt
index a071fba1..57725142 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/ObserveSourceWorker.kt
@@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
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.util.Log
import androidx.preference.PreferenceManager
@@ -57,7 +57,7 @@ class ObserveSourceWorker(
val workerID = System.currentTimeMillis().toInt()
val notification = notificationUtil.createObserveSourcesNotification(item.name)
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{
setForegroundAsync(ForegroundInfo(workerID, notification))
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt
index 28528e59..1fe1b8a2 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt
@@ -3,7 +3,7 @@ package com.deniscerri.ytdl.work
import android.app.PendingIntent
import android.content.Context
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.Handler
import android.os.Looper
@@ -53,7 +53,7 @@ class TerminalDownloadWorker(
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), NotificationUtil.DOWNLOAD_TERMINAL_RUNNING_NOTIFICATION_ID)
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{
setForegroundAsync(ForegroundInfo(itemId, notification))
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt
index 39ced447..07d0764c 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsDataWorker.kt
@@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
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 androidx.work.ForegroundInfo
import androidx.work.Worker
@@ -32,7 +32,7 @@ class UpdateMultipleDownloadsDataWorker(
val notification = notificationUtil.createDataUpdateNotification()
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{
setForegroundAsync(ForegroundInfo(workID, notification))
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt
index 9b8adc15..cd06064d 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt
@@ -1,7 +1,7 @@
package com.deniscerri.ytdl.work
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 androidx.work.ForegroundInfo
import androidx.work.Worker
@@ -33,7 +33,7 @@ class UpdateMultipleDownloadsFormatsWorker(
val notification = notificationUtil.createFormatsUpdateNotification()
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{
setForegroundAsync(ForegroundInfo(workID, notification))
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/WorkManagerExtensions.kt b/app/src/main/java/com/deniscerri/ytdl/work/WorkManagerExtensions.kt
new file mode 100644
index 00000000..68f4a128
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdl/work/WorkManagerExtensions.kt
@@ -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")
+ }
+}
\ No newline at end of file