implemented updating formats in the background worker

This commit is contained in:
deniscerri 2023-07-30 18:40:15 +02:00
parent 444f0cc582
commit 7191dc00f2
No known key found for this signature in database
GPG key ID: 95C43D517D830350
11 changed files with 220 additions and 54 deletions

View file

@ -60,7 +60,7 @@ import kotlin.random.nextInt
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
private val dbManager: DBManager
private val repository : DownloadRepository
val repository : DownloadRepository
private val sharedPreferences: SharedPreferences
private val commandTemplateDao: CommandTemplateDao
private val infoUtil : InfoUtil
@ -339,7 +339,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
private fun getFormat(formats: List<Format>, type: Type) : Format {
fun getFormat(formats: List<Format>, type: Type) : Format {
when(type) {
Type.audio -> {
return cloneFormat (
@ -539,7 +539,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
.build()
workManager.enqueueUniqueWork(
Random.nextInt(1000000000).toString(),
it.id.toString(),
ExistingWorkPolicy.REPLACE,
workRequest
)

View file

@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.NotificationUtil
@ -19,14 +20,17 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
if (message != null) {
runCatching {
val notificationUtil = NotificationUtil(c)
WorkManager.getInstance(c).cancelUniqueWork(id.toString())
notificationUtil.cancelDownloadNotification(id)
YoutubeDL.getInstance().destroyProcessById(id.toString())
val dbManager = DBManager.getInstance(c)
CoroutineScope(Dispatchers.IO).launch{
val item = dbManager.downloadDao.getDownloadById(id.toLong())
item.status = DownloadRepository.Status.Cancelled.toString()
dbManager.downloadDao.update(item)
runCatching {
val item = dbManager.downloadDao.getDownloadById(id.toLong())
item.status = DownloadRepository.Status.Cancelled.toString()
dbManager.downloadDao.update(item)
}
}
}

View file

@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.HomeAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
@ -50,6 +51,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -232,6 +234,18 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
}
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
CoroutineScope(Dispatchers.IO).launch {
val ids = arguments?.getLongArray("downloadIds")
val items = mutableListOf<DownloadItem>()
ids?.forEach {
items.add(downloadViewModel.getItemByID(it))
}
val bottomSheet = DownloadMultipleBottomSheetDialog(items.toMutableList())
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
}
}
}
override fun onResume() {
@ -239,7 +253,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
if(arguments?.getString("url") == null){
resultViewModel.checkTrending()
}else{
arguments
arguments?.remove("url")
arguments?.remove("showDownloadsWithUpdatedFormats")
}
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
lifecycleScope.launch {
@ -612,7 +628,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
downloadViewModel.turnResultItemsToDownloadItems(resultsList!!)
}
if (sharedPreferences!!.getBoolean("download_card", true)) {
val bottomSheet = DownloadMultipleBottomSheetDialog(resultsList!!, downloadList.toMutableList())
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} else {
downloadViewModel.queueDownloads(downloadList)
@ -673,7 +689,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
if (selectedObjects!!.size == 1){
showSingleDownloadSheet(selectedObjects!![0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
}else{
val bottomSheet = DownloadMultipleBottomSheetDialog(selectedObjects!!, downloadList.toMutableList())
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
}
} else {

View file

@ -33,11 +33,14 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@ -45,6 +48,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
@ -62,7 +66,7 @@ import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>, private var items: MutableList<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
@ -193,6 +197,35 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
}
override fun onContinueOnBackground(items: List<DownloadItem?>) {
requireActivity().lifecycleScope.launch {
val ids = mutableListOf<Long>()
runBlocking {
items.map { it!!.status = DownloadRepository.Status.Saved.toString() }
items.forEach {
ids.add(downloadViewModel.repository.insert(it!!))
}
}
val id = System.currentTimeMillis().toInt()
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()
.setInputData(
Data.Builder()
.putLongArray("ids", ids.toLongArray())
.putInt("id", id)
.build())
.addTag("updateFormats")
.build()
WorkManager.getInstance(requireContext()).enqueueUniqueWork(
id.toString(),
ExistingWorkPolicy.REPLACE,
workRequest
)
}
}
}
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->

View file

@ -22,6 +22,7 @@ import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.card.MaterialCardView
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -45,6 +46,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
private lateinit var audioTitle : TextView
private lateinit var sortBy : FormatSorting
private lateinit var continueInBackgroundSnackBar : Snackbar
private lateinit var view: View
enum class FormatSorting {
filesize, container, id
}
@ -62,7 +67,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
@SuppressLint("RestrictedApi")
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null)
view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null)
dialog.setContentView(view)
sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
@ -121,6 +126,16 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
refreshBtn.setOnClickListener {
lifecycleScope.launch {
if (items.size > 10){
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_INDEFINITE)
continueInBackgroundSnackBar.setAction(R.string.ok) {
listener.onContinueOnBackground(items)
this@FormatSelectionBottomSheetDialog.dismiss()
}
continueInBackgroundSnackBar.show()
}
chosenFormats = emptyList()
try {
refreshBtn.isEnabled = false
@ -333,6 +348,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
interface OnFormatClickListener{
fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>)
fun onContinueOnBackground(items : List<DownloadItem?>) {}
}
class FormatTuple internal constructor(

View file

@ -157,7 +157,7 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
downloadItems.add(i)
}
val bottomSheet = DownloadMultipleBottomSheetDialog(items, downloadItems)
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadItems)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
}

View file

@ -138,7 +138,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
it.forEach{item ->
WorkManager.getInstance(requireContext())
.getWorkInfosByTagLiveData(item.id.toString())
.getWorkInfosForUniqueWorkLiveData(item.id.toString())
.observe(viewLifecycleOwner){ list ->
list.forEach {work ->
if (work == null) return@observe

View file

@ -19,7 +19,6 @@ import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
import com.deniscerri.ytdlnis.receiver.ResumeActivity
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
import java.io.File
import kotlin.math.log
class NotificationUtil(var context: Context) {
@ -342,12 +341,95 @@ class NotificationUtil(var context: Context) {
}
}
fun showYTDLUpdateNotification() : Notification{
fun createYTDLUpdateNotification() : Notification{
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
notificationBuilder.setContentTitle("Updating YT-DLP...")
return notificationBuilder.build()
}
fun createFormatsUpdateNotification(workID: Int): Notification {
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
val cancelIntent = Intent(context, CancelDownloadNotificationReceiver::class.java)
cancelIntent.putExtra("cancel", "")
cancelIntent.putExtra("workID", workID)
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
context,
workID,
cancelIntent,
PendingIntent.FLAG_IMMUTABLE
)
return notificationBuilder
.setContentTitle(context.getString(R.string.update_formats_background))
.setOngoing(true)
.setCategory(Notification.CATEGORY_PROGRESS)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
android.R.drawable.stat_sys_download
)
)
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.clearActions()
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
.build()
}
fun updateFormatUpdateNotification(
id: Int,
progress: Int,
queue: Int,
) {
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
val contentText = """${queue - progress} ${context.getString(R.string.items_left)}"""
try {
notificationBuilder.setProgress(queue, progress, false)
.setContentTitle(context.getString(R.string.update_formats_background))
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
notificationManager.notify(id, notificationBuilder.build())
} catch (e: Exception) {
e.printStackTrace()
}
}
fun showFormatsUpdatedNotification(downloadIds: List<Long>) {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
val bundle = Bundle()
bundle.putBoolean("showDownloadsWithUpdatedFormats", true)
bundle.putLongArray("downloadIds", downloadIds.toLongArray())
val openMultipleDownloads = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.homeFragment)
.setArguments(bundle)
.createPendingIntent()
notificationBuilder
.setContentTitle(context.getString(R.string.finished_download_notification_channel_name))
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
R.drawable.ic_launcher_foreground_large
)
)
.setContentIntent(openMultipleDownloads)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions()
notificationManager.notify(FORMAT_UPDATING_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
}
companion object {
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
@ -355,6 +437,8 @@ class NotificationUtil(var context: Context) {
const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3
const val DOWNLOAD_RESUME_NOTIFICATION_ID = 4
const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5
const val FORMAT_UPDATING_NOTIFICATION_ID = 6
const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7
const val DOWNLOAD_MISC_CHANNEL_ID = "4"
const val DOWNLOAD_MISC_NOTIFICATION_ID = 4
private const val PROGRESS_MAX = 100

View file

@ -5,6 +5,7 @@ import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavDeepLinkBuilder
import androidx.preference.PreferenceManager
import androidx.work.CoroutineWorker
@ -13,6 +14,7 @@ import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.Converters
import com.deniscerri.ytdlnis.database.DBManager
@ -23,13 +25,16 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.repository.LogRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.NotificationUtil.Companion.FORMAT_UPDATING_NOTIFICATION_ID
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
@ -41,24 +46,56 @@ class UpdatePlaylistFormatsWorker(
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val dbManager = DBManager.getInstance(context)
val resultDao = dbManager.resultDao
val dao = dbManager.downloadDao
val resDao = dbManager.resultDao
val infoUtil = InfoUtil(context)
val converters = Converters()
val urls = inputData.getStringArray("urls")!!.toMutableList()
val vm = DownloadViewModel(App.instance)
val notificationUtil = NotificationUtil(context)
val ids = inputData.getLongArray("ids")!!.toMutableList()
val workID = inputData.getInt("id", 0)
if (workID == 0) return Result.failure()
infoUtil.getFormatsMultiple(urls){
val formatStrings = mutableListOf<String>()
it.forEach { i -> formatStrings.add(converters.formatToString(i)) }
setProgressAsync(workDataOf("formats" to formatStrings.toTypedArray()))
val notification = notificationUtil.createFormatsUpdateNotification(workID)
val foregroundInfo = ForegroundInfo(FORMAT_UPDATING_NOTIFICATION_ID, notification)
setForeground(foregroundInfo)
CoroutineScope(Dispatchers.IO).launch {
val result = resultDao.getResultByURL(urls.removeFirst())
val formats = infoUtil.getFormats(result.url)
result.formats = ArrayList(formats)
resultDao.update(result)
var count = 0
ids.forEach {
if (!isStopped){
val d = dao.getDownloadById(it)
val r = resDao.getResultByURL(d.url)
if (d.allFormats.isNotEmpty()){
count++
return@forEach
}
runCatching {
d.allFormats.clear()
d.allFormats.addAll(infoUtil.getFormats(d.url))
d.format = vm.getFormat(d.allFormats,d.type)
r.formats.clear()
r.formats.addAll(d.allFormats)
dao.update(d)
resDao.update(r)
}
count++
notificationUtil.updateFormatUpdateNotification(FORMAT_UPDATING_NOTIFICATION_ID, count, ids.size)
}else{
notificationUtil.cancelDownloadNotification(FORMAT_UPDATING_NOTIFICATION_ID)
}
}
runBlocking {
notificationUtil.showFormatsUpdatedNotification(ids)
}
return Result.success()
}
}

View file

@ -1,37 +1,11 @@
package com.deniscerri.ytdlnis.work
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.navigation.NavDeepLinkBuilder
import androidx.preference.PreferenceManager
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.repository.LogRepository
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
class UpdateYTDLWorker(
@ -39,7 +13,7 @@ class UpdateYTDLWorker(
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val notification = NotificationUtil(context).showYTDLUpdateNotification()
val notification = NotificationUtil(context).createYTDLUpdateNotification()
val foregroundInfo = ForegroundInfo(System.currentTimeMillis().toInt(), notification)
setForeground(foregroundInfo)
UpdateUtil(context).updateYoutubeDL()

View file

@ -300,4 +300,6 @@
<string name="use_cookies">Use Cookies</string>
<string name="embed_metadata">Embed Metadata</string>
<string name="embed_metadata_summary">Embed metadata to the video file. Also embeds chapters/infojson if present</string>
<string name="formats_updated">Formats have been updated</string>
<string name="update_formats_background">Update Formats in Background</string>
</resources>