Initial support for multi deleting downloads

This is probably a horrible way to do this, I just am not sure of the best way to go about this to be honest
This commit is contained in:
Luna712 2024-07-04 17:43:55 -06:00 committed by GitHub
parent c1b5f5c128
commit 7b1f59fdb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 125 additions and 43 deletions

View file

@ -27,6 +27,7 @@ const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
const val DOWNLOAD_ACTION_DOWNLOAD = 4
const val DOWNLOAD_ACTION_LONG_CLICK = 5
const val DOWNLOAD_ACTION_DELETE_MULTIPLE_FILES = 6
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
const val DOWNLOAD_ACTION_LOAD_RESULT = 1
@ -57,6 +58,11 @@ abstract class VisualDownloadCached(
}
}
abstract class DownloadActionEventBase(
open val action: Int,
open val data: VideoDownloadHelper.DownloadEpisodeCached?
)
data class VisualDownloadChildCached(
override val currentBytes: Long,
override val totalBytes: Long,
@ -73,9 +79,14 @@ data class VisualDownloadHeaderCached(
): VisualDownloadCached(currentBytes, totalBytes, data)
data class DownloadClickEvent(
val action: Int,
val data: VideoDownloadHelper.DownloadEpisodeCached
)
override val action: Int,
override val data: VideoDownloadHelper.DownloadEpisodeCached
): DownloadActionEventBase(action, data)
data class DownloadDeleteEvent(
override val action: Int,
val items: List<VideoDownloadHelper.DownloadEpisodeCached?>
): DownloadActionEventBase(action, null)
data class DownloadHeaderClickEvent(
val action: Int,
@ -83,8 +94,8 @@ data class DownloadHeaderClickEvent(
)
class DownloadAdapter(
private val actionCallback: (DownloadActionEventBase) -> Unit,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : ListAdapter<VisualDownloadCached, DownloadAdapter.DownloadViewHolder>(DiffCallback()) {
companion object {
@ -94,8 +105,8 @@ class DownloadAdapter(
inner class DownloadViewHolder(
private val binding: ViewBinding,
private val actionCallback: (DownloadActionEventBase) -> Unit,
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(card: VisualDownloadCached?) {
@ -145,11 +156,11 @@ class DownloadAdapter(
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
}
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, actionCallback)
downloadButton.isVisible = true
episodeHolder.setOnClickListener {
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
actionCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
}
} else {
downloadButton.isVisible = false
@ -214,7 +225,7 @@ class DownloadAdapter(
ContextCompat.getDrawable(downloadButton.context, downloadButton.progressDrawable)
}
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, mediaClickCallback)
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, actionCallback)
downloadButton.isVisible = true
downloadChildEpisodeText.apply {
@ -223,7 +234,7 @@ class DownloadAdapter(
}
downloadChildEpisodeHolder.setOnClickListener {
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
actionCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
}
}
}
@ -236,7 +247,7 @@ class DownloadAdapter(
VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
else -> throw IllegalArgumentException("Invalid view type")
}
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
return DownloadViewHolder(binding, actionCallback, clickCallback)
}
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {

View file

@ -17,19 +17,21 @@ import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.coroutines.MainScope
object DownloadButtonSetup {
fun handleDownloadClick(click: DownloadClickEvent) {
val id = click.data.id
fun handleDownloadClick(click: DownloadActionEventBase) {
when (click.action) {
DOWNLOAD_ACTION_DELETE_FILE -> {
if (click !is DownloadDeleteEvent) return
val id = click.items.firstOrNull()?.id ?: return
activity?.let { ctx ->
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
val dialogClickListener =
DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
VideoDownloadManager.deleteFileAndUpdateSettings(ctx, id)
VideoDownloadManager.deleteFilesAndUpdateSettings(ctx, listOf(id), MainScope())
}
DialogInterface.BUTTON_NEGATIVE -> {
}
@ -41,9 +43,9 @@ object DownloadButtonSetup {
.setMessage(
ctx.getString(R.string.delete_message).format(
ctx.getNameFull(
click.data.name,
click.data.episode,
click.data.season
click.items.firstOrNull()?.name,
click.items.firstOrNull()?.episode,
click.items.firstOrNull()?.season
)
)
)
@ -57,15 +59,17 @@ object DownloadButtonSetup {
}
}
DOWNLOAD_ACTION_PAUSE_DOWNLOAD -> {
val id = click.data?.id ?: return
VideoDownloadManager.downloadEvent.invoke(
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Pause)
Pair(id, VideoDownloadManager.DownloadActionType.Pause)
)
}
DOWNLOAD_ACTION_RESUME_DOWNLOAD -> {
val id = click.data?.id ?: return
activity?.let { ctx ->
if (VideoDownloadManager.downloadStatus.containsKey(id) && VideoDownloadManager.downloadStatus[id] == VideoDownloadManager.DownloadType.IsPaused) {
VideoDownloadManager.downloadEvent.invoke(
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
Pair(id, VideoDownloadManager.DownloadActionType.Resume)
)
} else {
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
@ -73,18 +77,19 @@ object DownloadButtonSetup {
VideoDownloadManager.downloadFromResumeUsingWorker(ctx, pkg)
} else {
VideoDownloadManager.downloadEvent.invoke(
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
Pair(id, VideoDownloadManager.DownloadActionType.Resume)
)
}
}
}
}
DOWNLOAD_ACTION_LONG_CLICK -> {
val id = click.data?.id ?: return
activity?.let { act ->
val length =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
act,
click.data.id
id
)?.fileLength
?: 0
if (length > 0) {
@ -95,19 +100,20 @@ object DownloadButtonSetup {
}
}
DOWNLOAD_ACTION_PLAY_FILE -> {
val id = click.data?.id ?: return
activity?.let { act ->
val info =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
act,
click.data.id
id
) ?: return
val keyInfo = getKey<VideoDownloadManager.DownloadedFileInfo>(
VideoDownloadManager.KEY_DOWNLOAD_INFO,
click.data.id.toString()
id.toString()
) ?: return
val parent = getKey<VideoDownloadHelper.DownloadHeaderCached>(
DOWNLOAD_HEADER_CACHE,
click.data.parentId.toString()
click.data?.parentId.toString()
) ?: return
act.navigate(
@ -117,11 +123,11 @@ object DownloadButtonSetup {
ExtractorUri(
uri = info.path,
id = click.data.id,
parentId = click.data.parentId,
id = id,
parentId = click.data?.parentId,
name = act.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName
season = click.data.season,
episode = click.data.episode,
season = click.data?.season,
episode = click.data?.episode,
headerName = parent.name,
tvType = parent.type,
@ -138,17 +144,47 @@ object DownloadButtonSetup {
// keyInfo.basePath,
// keyInfo.relativePath,
// keyInfo.displayName,
// click.data.parentId,
// click.data.id,
// click.data?.parentId,
// click.data?.id,
// headerName ?: "null",
// if (click.data.episode <= 0) null else click.data.episode,
// click.data.season
// ),
// getViewPos(click.data.id)?.position ?: 0
// getViewPos(click.data?.id)?.position ?: 0
//)
)
}
}
DOWNLOAD_ACTION_DELETE_MULTIPLE_FILES -> {
activity?.let { ctx ->
if (click !is DownloadDeleteEvent) return
val ids: List<Int> = click.items.mapNotNull { it?.id }
.takeIf { it.isNotEmpty() } ?: return@let
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
val dialogClickListener =
DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
VideoDownloadManager.deleteFilesAndUpdateSettings(ctx, ids, MainScope())
}
DialogInterface.BUTTON_NEGATIVE -> {
}
}
}
try {
builder.setTitle(R.string.delete_files)
.setMessage(
ctx.getString(R.string.delete_multiple_message)
)
.setPositiveButton(R.string.delete, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
.show().setDefaultFocus()
} catch (e: Exception) {
logError(e)
}
}
}
}
}
}

View file

@ -102,13 +102,17 @@ class DownloadChildFragment : Fragment() {
}
val adapter = DownloadAdapter(
{},
{ downloadClickEvent ->
handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
{ actionEvent ->
if (actionEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
val downloadDeleteEvent = DownloadDeleteEvent(
action = DOWNLOAD_ACTION_DELETE_FILE,
items = listOf(actionEvent.data)
)
handleDownloadClick(downloadDeleteEvent)
setUpDownloadDeleteListener(folder)
}
}
} else handleDownloadClick(actionEvent)
},
{}
)
binding?.downloadChildList?.apply {

View file

@ -108,14 +108,18 @@ class DownloadFragment : Fragment() {
}
val adapter = DownloadAdapter(
{ actionEvent ->
if (actionEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
val downloadDeleteEvent = DownloadDeleteEvent(
action = DOWNLOAD_ACTION_DELETE_FILE,
items = listOf(actionEvent.data)
)
handleDownloadClick(downloadDeleteEvent)
setUpDownloadDeleteListener()
} else handleDownloadClick(actionEvent)
},
{ click ->
handleItemClick(click)
},
{ downloadClickEvent ->
handleDownloadClick(downloadClickEvent)
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
setUpDownloadDeleteListener()
}
}
)

View file

@ -20,6 +20,7 @@ import androidx.work.WorkManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.api.Log
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
@ -29,6 +30,7 @@ import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.services.VideoDownloadService
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -42,6 +44,8 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@ -1705,7 +1709,28 @@ object VideoDownloadManager {
}
}
fun deleteFileAndUpdateSettings(context: Context, id: Int): Boolean {
fun deleteFilesAndUpdateSettings(context: Context, ids: List<Int>, scope: CoroutineScope) {
scope.launchSafe(Dispatchers.IO) {
val deleteJobs = ids.map { id ->
async {
id to deleteFileAndUpdateSettings(context, id)
}
}
val results = deleteJobs.awaitAll()
val failedDeletes = results.filterNot { it.second }
if (failedDeletes.isNotEmpty()) {
failedDeletes.forEach { (id, _) ->
// TODO show a toast if some failed?
Log.e("FileDeletion", "Failed to delete file with ID: $id")
}
} else {
Log.i("FileDeletion", "All files deleted successfully")
}
}
}
private fun deleteFileAndUpdateSettings(context: Context, id: Int): Boolean {
val success = deleteFile(context, id)
if (success) context.removeKey(KEY_DOWNLOAD_INFO, id.toString())
return success

View file

@ -300,6 +300,7 @@
<string name="episode_short">E</string>
<string name="no_episodes_found">No Episodes found</string>
<string name="delete_file">Delete File</string>
<string name="delete_files">Delete Files</string>
<string name="delete">Delete</string>
<string name="cancel">Cancel</string>
<string name="pause">Pause</string>
@ -311,6 +312,7 @@
<string name="go_back_30">-30</string>
<string name="go_forward_30">+30</string>
<string name="delete_message" formatted="true">This will permanently delete %s\nAre you sure?</string>
<string name="delete_multiple_message" formatted="true">This will permanently delete all of: %s\nAre you sure?</string>
<string name="resume_time_left" formatted="true">%dm\nremaining</string>
<string name="resume_remaining" formatted="true">%s\nremaining</string>
<string name="status_ongoing">Ongoing</string>