Skip to content
Snippets Groups Projects
Commit 2565c8b2 authored by Geoffrey Métais's avatar Geoffrey Métais Committed by Geoffrey Métais
Browse files

removeItems function for batch media deletion

parent f258985c
No related branches found
No related tags found
1 merge request!127Batch delete
......@@ -288,8 +288,10 @@ abstract class BaseBrowserFragment : MediaBrowserFragment<BrowserModel>(), IRefr
val mw = item as? MediaWrapper ?: return false
val cancel = Runnable { viewModel.refresh() }
val deleteAction = Runnable {
deleteMedia(mw, false, cancel)
viewModel.remove(mw)
launch {
deleteMedia(mw, false, cancel)
viewModel.remove(mw)
}
}
val resId = if (mw.type == MediaWrapper.TYPE_DIR) R.string.confirm_delete_folder else R.string.confirm_delete
UiTools.snackerConfirm(view, getString(resId, mw.title), Runnable { if (Util.checkWritePermission(requireActivity(), mw, deleteAction)) deleteAction.run() })
......
......@@ -42,16 +42,22 @@ import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.MediaWrapper
import org.videolan.medialibrary.media.Playlist
import org.videolan.tools.isStarted
import org.videolan.vlc.R
import org.videolan.vlc.VLCApplication
import org.videolan.vlc.gui.AudioPlayerContainerActivity
import org.videolan.vlc.gui.ContentActivity
import org.videolan.vlc.gui.InfoActivity
import org.videolan.vlc.gui.helpers.UiTools
import org.videolan.vlc.gui.helpers.UiTools.snackerConfirm
import org.videolan.vlc.gui.helpers.hf.WriteExternalDelegate.Companion.getExtWritePermission
import org.videolan.vlc.gui.view.SwipeRefreshLayout
import org.videolan.vlc.interfaces.Filterable
import org.videolan.vlc.media.MediaUtils
import org.videolan.vlc.util.*
import org.videolan.vlc.util.AndroidDevices
import org.videolan.vlc.util.FileUtils
import org.videolan.vlc.util.TAG_ITEM
import org.videolan.vlc.util.Util
import org.videolan.vlc.viewmodels.MedialibraryViewModel
import org.videolan.vlc.viewmodels.SortableModel
import java.lang.Runnable
......@@ -146,12 +152,27 @@ abstract class MediaBrowserFragment<T : SortableModel> : Fragment(), ActionMode.
abstract fun onRefresh()
open fun clear() {}
protected open fun removeItems(items: List<MediaLibraryItem>, title: String) {
val v = view ?: return
snackerConfirm(v, title) {
for (item in items) {
if (!activity.isStarted()) break
when(item) {
is MediaWrapper -> if (getExtWritePermission(item.uri)) deleteMedia(item)
is Playlist -> withContext(Dispatchers.IO) { item.delete() }
}
}
}
}
protected open fun removeItem(item: MediaLibraryItem): Boolean {
view ?: return false
when {
item.itemType == MediaLibraryItem.TYPE_PLAYLIST -> UiTools.snackerConfirm(view!!, getString(R.string.confirm_delete_playlist, item.title), Runnable { MediaUtils.deletePlaylist(item as Playlist) })
item.itemType == MediaLibraryItem.TYPE_MEDIA -> {
val deleteAction = Runnable { deleteMedia(item, false, null) }
val deleteAction = Runnable {
if (activity.isStarted()) launch { deleteMedia(item, false, null) }
}
val resid = if ((item as MediaWrapper).type == MediaWrapper.TYPE_DIR) R.string.confirm_delete_folder else R.string.confirm_delete
UiTools.snackerConfirm(view!!, getString(resid, item.getTitle()), Runnable { if (Util.checkWritePermission(requireActivity(), item, deleteAction)) deleteAction.run() })
}
......@@ -160,34 +181,32 @@ abstract class MediaBrowserFragment<T : SortableModel> : Fragment(), ActionMode.
return true
}
protected fun deleteMedia(mw: MediaLibraryItem, refresh: Boolean, failCB: Runnable?) {
runIO(Runnable {
val foldersToReload = LinkedList<String>()
val mediaPaths = LinkedList<String>()
for (media in mw.tracks) {
val path = media.uri.path
val parentPath = FileUtils.getParent(path)
if (FileUtils.deleteFile(media.uri)) {
if (media.id > 0L && !foldersToReload.contains(parentPath)) {
if (parentPath != null) {
foldersToReload.add(parentPath)
}
protected suspend fun deleteMedia(mw: MediaLibraryItem, refresh: Boolean = false, failCB: Runnable? = null) = withContext(Dispatchers.IO) {
val foldersToReload = LinkedList<String>()
val mediaPaths = LinkedList<String>()
for (media in mw.tracks) {
val path = media.uri.path
val parentPath = FileUtils.getParent(path)
if (FileUtils.deleteFile(media.uri)) {
if (media.id > 0L && !foldersToReload.contains(parentPath)) {
if (parentPath != null) {
foldersToReload.add(parentPath)
}
mediaPaths.add(media.location)
} else
onDeleteFailed(media)
}
for (folder in foldersToReload) mediaLibrary.reload(folder)
if (activity != null) {
launch {
if (mediaPaths.isEmpty()) {
failCB?.run()
return@launch
}
if (refresh) onRefresh()
}
mediaPaths.add(media.location)
} else
onDeleteFailed(media)
}
for (folder in foldersToReload) mediaLibrary.reload(folder)
if (activity.isStarted()) {
launch {
if (mediaPaths.isEmpty()) {
failCB?.run()
return@launch
}
if (refresh) onRefresh()
}
})
}
}
private fun onDeleteFailed(media: MediaWrapper) {
......
......@@ -58,8 +58,10 @@ import androidx.databinding.BindingAdapter
import androidx.fragment.app.FragmentActivity
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.media.MediaLibraryItem
......@@ -152,6 +154,14 @@ object UiTools {
snack.view.elevation = view.resources.getDimensionPixelSize(R.dimen.audio_player_elevation).toFloat()
snack.show()
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
fun CoroutineScope.snackerConfirm(view: View, message: String, action: suspend() -> Unit) {
val snack = Snackbar.make(view, message, Snackbar.LENGTH_LONG)
.setAction(android.R.string.ok) { launch { action.invoke() } }
if (AndroidUtil.isLolliPopOrLater)
snack.view.elevation = view.resources.getDimensionPixelSize(R.dimen.audio_player_elevation).toFloat()
snack.show()
}
/**
......
......@@ -10,6 +10,7 @@ import android.provider.DocumentsContract
import android.text.TextUtils
import androidx.appcompat.app.AlertDialog
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.launch
import org.videolan.libvlc.util.AndroidUtil
......@@ -95,21 +96,22 @@ class WriteExternalDelegate : BaseHeadlessFragment() {
internal const val KEY_STORAGE_PATH = "VLC/storage_path"
private const val REQUEST_CODE_STORAGE_ACCESS = 42
fun askForExtWrite(activity: FragmentActivity?, uri: Uri, cb: Runnable? = null) {
fun askForExtWrite(activity: FragmentActivity, uri: Uri, cb: Runnable? = null) {
AppScope.launch {
if (getExtWritePermission(activity, uri)) cb?.run()
if (activity.getExtWritePermission(uri)) cb?.run()
}
}
suspend fun getExtWritePermission(activity: FragmentActivity?, uri: Uri) : Boolean {
if (activity === null) return false
suspend fun FragmentActivity.getExtWritePermission(uri: Uri) : Boolean {
val storage = FileUtils.getMediaStorage(uri) ?: return false
val fragment = WriteExternalDelegate()
fragment.arguments = Bundle(1).apply { putString(KEY_STORAGE_PATH, storage) }
activity.supportFragmentManager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss()
supportFragmentManager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss()
return fragment.awaitGrant()
}
suspend fun Fragment.getExtWritePermission(uri: Uri) = activity?.getExtWritePermission(uri) ?: false
fun needsWritePermission(uri: Uri) : Boolean {
val path = uri.path ?: return false
return AndroidUtil.isLolliPopOrLater && "file" == uri.scheme
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment