Add functionality for exporting and importing playlists

This commit is contained in:
Bnyro 2022-12-01 14:37:44 +01:00
parent c1793691ed
commit 5616934817
6 changed files with 60 additions and 24 deletions

View File

@ -6,17 +6,21 @@ import com.github.libretube.R
import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.PlaylistId import com.github.libretube.api.obj.PlaylistId
import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.Playlists
import com.github.libretube.constants.YOUTUBE_FRONTEND_URL
import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.DatabaseHolder
import com.github.libretube.db.obj.LocalPlaylist import com.github.libretube.db.obj.LocalPlaylist
import com.github.libretube.enums.PlaylistType import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toLocalPlaylistItem import com.github.libretube.extensions.toLocalPlaylistItem
import com.github.libretube.extensions.toStreamItem import com.github.libretube.extensions.toStreamItem
import com.github.libretube.extensions.toastFromMainThread import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.obj.ImportPlaylist import com.github.libretube.obj.ImportPlaylist
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.ProxyHelper import com.github.libretube.util.ProxyHelper
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -47,9 +51,9 @@ object PlaylistsHelper {
return playlists return playlists
} }
suspend fun getPlaylist(playlistType: PlaylistType, playlistId: String): Playlist { suspend fun getPlaylist(playlistId: String): Playlist {
// load locally stored playlists with the auth api // load locally stored playlists with the auth api
return when (playlistType) { return when (getPlaylistType()) {
PlaylistType.PRIVATE -> RetrofitInstance.authApi.getPlaylist(playlistId) PlaylistType.PRIVATE -> RetrofitInstance.authApi.getPlaylist(playlistId)
PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId) PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId)
PlaylistType.LOCAL -> { PlaylistType.LOCAL -> {
@ -66,7 +70,10 @@ object PlaylistsHelper {
} }
} }
suspend fun createPlaylist(playlistName: String, appContext: Context, onSuccess: () -> Unit) { suspend fun createPlaylist(
playlistName: String,
appContext: Context
): String? {
if (!loggedIn()) { if (!loggedIn()) {
awaitQuery { awaitQuery {
DatabaseHolder.Database.localPlaylistsDao().createPlaylist( DatabaseHolder.Database.localPlaylistsDao().createPlaylist(
@ -76,8 +83,9 @@ object PlaylistsHelper {
) )
) )
} }
onSuccess.invoke() return awaitQuery {
return DatabaseHolder.Database.localPlaylistsDao().getAll()
}.last().playlist.id.toString()
} }
val response = try { val response = try {
RetrofitInstance.authApi.createPlaylist( RetrofitInstance.authApi.createPlaylist(
@ -86,18 +94,17 @@ object PlaylistsHelper {
) )
} catch (e: IOException) { } catch (e: IOException) {
appContext.toastFromMainThread(R.string.unknown_error) appContext.toastFromMainThread(R.string.unknown_error)
return return null
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG(), e.toString()) Log.e(TAG(), e.toString())
appContext.toastFromMainThread(R.string.server_error) appContext.toastFromMainThread(R.string.server_error)
return return null
} }
if (response.playlistId != null) { if (response.playlistId != null) {
appContext.toastFromMainThread(R.string.playlistCreated) appContext.toastFromMainThread(R.string.playlistCreated)
onSuccess.invoke() return response.playlistId!!
} else {
appContext.toastFromMainThread(R.string.unknown_error)
} }
return null
} }
suspend fun addToPlaylist(playlistId: String, videoId: String): Boolean { suspend fun addToPlaylist(playlistId: String, videoId: String): Boolean {
@ -176,19 +183,51 @@ object PlaylistsHelper {
) )
} }
suspend fun importPlaylists(playlist: List<ImportPlaylist>) { suspend fun importPlaylists(appContext: Context, playlists: List<ImportPlaylist>) {
for (playlist in playlists) {
val playlistId = createPlaylist(playlist.name!!, appContext) ?: continue
runBlocking {
val tasks = playlist.videos.map { videoId ->
async { addToPlaylist(playlistId, videoId.substringAfter("=")) }
}
tasks.forEach {
it.await()
}
}
}
} }
suspend fun exportPlaylists(): List<ImportPlaylist> { suspend fun exportPlaylists(): List<ImportPlaylist> {
val playlists = getPlaylists()
val importLists = mutableListOf<ImportPlaylist>()
runBlocking {
val tasks = playlists.map {
async {
val list = getPlaylist(it.id!!)
importLists.add(
ImportPlaylist(
name = list.name,
type = "playlist",
visibility = "private",
videos = list.relatedStreams.orEmpty().map {
YOUTUBE_FRONTEND_URL + it.url!!.toID()
}
)
)
}
}
tasks.forEach {
it.await()
}
}
return importLists
} }
fun getPrivateType(): PlaylistType { fun getPlaylistType(): PlaylistType {
return if (loggedIn()) PlaylistType.PRIVATE else PlaylistType.LOCAL return if (loggedIn()) PlaylistType.PRIVATE else PlaylistType.LOCAL
} }
fun getPrivateType(playlistId: String): PlaylistType { fun getPlaylistType(playlistId: String): PlaylistType {
if (playlistId.all { it.isDigit() }) return PlaylistType.LOCAL if (playlistId.all { it.isDigit() }) return PlaylistType.LOCAL
if (playlistId.matches(pipedPlaylistRegex)) return PlaylistType.PRIVATE if (playlistId.matches(pipedPlaylistRegex)) return PlaylistType.PRIVATE
return PlaylistType.PUBLIC return PlaylistType.PUBLIC

View File

@ -108,7 +108,7 @@ class HomeFragment : BaseFragment() {
runOnUiThread { runOnUiThread {
makeVisible(binding.playlistsRV, binding.playlistsTV) makeVisible(binding.playlistsRV, binding.playlistsTV)
binding.playlistsRV.layoutManager = LinearLayoutManager(context) binding.playlistsRV.layoutManager = LinearLayoutManager(context)
binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList(), PlaylistsHelper.getPrivateType()) binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList(), PlaylistsHelper.getPlaylistType())
binding.playlistsRV.adapter?.registerAdapterDataObserver(object : binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
RecyclerView.AdapterDataObserver() { RecyclerView.AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {

View File

@ -123,7 +123,7 @@ class LibraryFragment : BaseFragment() {
val playlistsAdapter = PlaylistsAdapter( val playlistsAdapter = PlaylistsAdapter(
playlists.toMutableList(), playlists.toMutableList(),
PlaylistsHelper.getPrivateType() PlaylistsHelper.getPlaylistType()
) )
// listen for playlists to become deleted // listen for playlists to become deleted

View File

@ -99,7 +99,7 @@ class PlaylistFragment : BaseFragment() {
binding.playlistScrollview.visibility = View.GONE binding.playlistScrollview.visibility = View.GONE
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
PlaylistsHelper.getPlaylist(playlistType, playlistId!!) PlaylistsHelper.getPlaylist(playlistId!!)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG(), "IOException, you might not have internet connection") Log.e(TAG(), "IOException, you might not have internet connection")

View File

@ -110,8 +110,8 @@ object PlayingQueue {
fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) { fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
val playlistType = PlaylistsHelper.getPrivateType(playlistId) val playlistType = PlaylistsHelper.getPlaylistType(playlistId)
val playlist = PlaylistsHelper.getPlaylist(playlistType, playlistId) val playlist = PlaylistsHelper.getPlaylist(playlistId)
add( add(
*playlist.relatedStreams *playlist.relatedStreams
.orEmpty() .orEmpty()

View File

@ -82,12 +82,9 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
var success = true var success = true
runBlocking { runBlocking {
val task = async {
SubscriptionHelper.getFeed()
}
// fetch the users feed // fetch the users feed
val videoFeed = try { val videoFeed = try {
task.await() SubscriptionHelper.getFeed()
} catch (e: Exception) { } catch (e: Exception) {
success = false success = false
return@runBlocking return@runBlocking