refactor: bind playlist repositories to interfaces

This commit is contained in:
Bnyro 2025-01-10 15:42:59 +01:00
parent f54d0fd8a0
commit 2698368aee
6 changed files with 162 additions and 120 deletions

View File

@ -1,16 +1,16 @@
package com.github.libretube.api
import androidx.core.text.isDigitsOnly
import com.github.libretube.api.obj.EditPlaylistBody
import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.obj.PipedImportPlaylist
import com.github.libretube.repo.LocalPlaylistsRepository
import com.github.libretube.repo.PipedPlaylistRepository
import com.github.libretube.repo.PlaylistRepository
import com.github.libretube.util.deArrow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@ -24,14 +24,14 @@ object PlaylistsHelper {
private val token get() = PreferenceHelper.getToken()
val loggedIn: Boolean get() = token.isNotEmpty()
private fun Message.isOk() = this.message == "ok"
private val playlistsRepository: PlaylistRepository
get() = when {
loggedIn -> PipedPlaylistRepository()
else -> LocalPlaylistsRepository()
}
suspend fun getPlaylists(): List<Playlists> = withContext(Dispatchers.IO) {
val playlists = if (loggedIn) {
RetrofitInstance.authApi.getUserPlaylists(token)
} else {
LocalPlaylistsRepository.getPlaylists()
}
val playlists = playlistsRepository.getPlaylists()
sortPlaylists(playlists)
}
@ -52,109 +52,41 @@ object PlaylistsHelper {
suspend fun getPlaylist(playlistId: String): Playlist {
// load locally stored playlists with the auth api
return when (getPrivatePlaylistType(playlistId)) {
PlaylistType.PRIVATE -> RetrofitInstance.authApi.getPlaylist(playlistId)
PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId)
PlaylistType.LOCAL -> LocalPlaylistsRepository.getPlaylist(playlistId)
else -> playlistsRepository.getPlaylist(playlistId)
}.apply {
relatedStreams = relatedStreams.deArrow()
}
}
suspend fun createPlaylist(playlistName: String): String? {
return if (!loggedIn) {
LocalPlaylistsRepository.createPlaylist(playlistName)
} else {
RetrofitInstance.authApi.createPlaylist(
token,
Playlists(name = playlistName)
).playlistId
}
}
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
if (!loggedIn) {
LocalPlaylistsRepository.addToPlaylist(playlistId, *videos)
return true
}
val playlist = EditPlaylistBody(playlistId, videoIds = videos.map { it.url!!.toID() })
return RetrofitInstance.authApi.addToPlaylist(token, playlist).isOk()
}
suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
if (!loggedIn) {
LocalPlaylistsRepository.renamePlaylist(playlistId, newName)
return true
}
val playlist = EditPlaylistBody(playlistId, newName = newName)
return RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk()
}
suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
if (!loggedIn) {
LocalPlaylistsRepository.changePlaylistDescription(playlistId, newDescription)
return true
}
val playlist = EditPlaylistBody(playlistId, description = newDescription)
return RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk()
}
suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean {
if (!loggedIn) {
LocalPlaylistsRepository.removeFromPlaylist(playlistId, index)
return true
}
return RetrofitInstance.authApi.removeFromPlaylist(
PreferenceHelper.getToken(),
EditPlaylistBody(playlistId = playlistId, index = index)
).isOk()
}
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) =
withContext(Dispatchers.IO) {
if (!loggedIn) return@withContext LocalPlaylistsRepository.importPlaylists(playlists)
for (playlist in playlists) {
val playlistId = createPlaylist(playlist.name!!) ?: return@withContext
val streams = playlist.videos.map { StreamItem(url = it) }
addToPlaylist(playlistId, *streams.toTypedArray())
}
}
suspend fun getAllPlaylistsWithVideos(playlistIds: List<String>? = null): List<Playlist> =
withContext(Dispatchers.IO) {
suspend fun getAllPlaylistsWithVideos(playlistIds: List<String>? = null): List<Playlist> {
return withContext(Dispatchers.IO) {
(playlistIds ?: getPlaylists().map { it.id!! })
.map { async { getPlaylist(it) } }
.awaitAll()
}
suspend fun clonePlaylist(playlistId: String): String? {
if (!loggedIn) {
return LocalPlaylistsRepository.clonePlaylist(playlistId)
}
return RetrofitInstance.authApi.clonePlaylist(
token,
EditPlaylistBody(playlistId)
).playlistId
}
suspend fun createPlaylist(playlistName: String) =
playlistsRepository.createPlaylist(playlistName)
suspend fun deletePlaylist(playlistId: String, playlistType: PlaylistType): Boolean {
if (playlistType == PlaylistType.LOCAL) {
LocalPlaylistsRepository.deletePlaylist(playlistId)
return true
}
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) =
playlistsRepository.addToPlaylist(playlistId, *videos)
return runCatching {
RetrofitInstance.authApi.deletePlaylist(
PreferenceHelper.getToken(),
EditPlaylistBody(playlistId)
).isOk()
}.getOrDefault(false)
}
suspend fun renamePlaylist(playlistId: String, newName: String) =
playlistsRepository.renamePlaylist(playlistId, newName)
suspend fun changePlaylistDescription(playlistId: String, newDescription: String) =
playlistsRepository.changePlaylistDescription(playlistId, newDescription)
suspend fun removeFromPlaylist(playlistId: String, index: Int) =
playlistsRepository.removeFromPlaylist(playlistId, index)
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) =
playlistsRepository.importPlaylists(playlists)
suspend fun clonePlaylist(playlistId: String) = playlistsRepository.clonePlaylist(playlistId)
suspend fun deletePlaylist(playlistId: String) = playlistsRepository.deletePlaylist(playlistId)
fun getPrivatePlaylistType(): PlaylistType {
return if (loggedIn) PlaylistType.PRIVATE else PlaylistType.LOCAL

View File

@ -1,6 +1,9 @@
package com.github.libretube.api
package com.github.libretube.repo
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.PlaylistsHelper.MAX_CONCURRENT_IMPORT_CALLS
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.StreamsExtractor
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
@ -10,8 +13,8 @@ import com.github.libretube.extensions.parallelMap
import com.github.libretube.helpers.ProxyHelper
import com.github.libretube.obj.PipedImportPlaylist
object LocalPlaylistsRepository {
suspend fun getPlaylist(playlistId: String): Playlist {
class LocalPlaylistsRepository: PlaylistRepository {
override suspend fun getPlaylist(playlistId: String): Playlist {
val relation = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }
@ -24,7 +27,7 @@ object LocalPlaylistsRepository {
)
}
suspend fun getPlaylists(): List<Playlists> {
override suspend fun getPlaylists(): List<Playlists> {
return DatabaseHolder.Database.localPlaylistsDao().getAll()
.map {
Playlists(
@ -37,7 +40,7 @@ object LocalPlaylistsRepository {
}
}
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) {
override suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }
@ -59,25 +62,31 @@ object LocalPlaylistsRepository {
}
}
}
return true
}
suspend fun renamePlaylist(playlistId: String, newName: String) {
override suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }.playlist
playlist.name = newName
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
return true
}
suspend fun changePlaylistDescription(playlistId: String, newDescription: String) {
override suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }.playlist
playlist.description = newDescription
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
return true
}
suspend fun clonePlaylist(playlistId: String): String? {
override suspend fun clonePlaylist(playlistId: String): String {
val playlist = RetrofitInstance.api.getPlaylist(playlistId)
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name")
PlaylistsHelper.addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray())
@ -93,7 +102,7 @@ object LocalPlaylistsRepository {
return playlistId
}
suspend fun removeFromPlaylist(playlistId: String, index: Int) {
override suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean {
val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId }
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
@ -105,11 +114,13 @@ object LocalPlaylistsRepository {
transaction.videos.getOrNull(1)?.thumbnailUrl.orEmpty()
}
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist)
return true
}
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) {
override suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) {
for (playlist in playlists) {
val playlistId = createPlaylist(playlist.name!!) ?: return
val playlistId = createPlaylist(playlist.name!!)
// if not logged in, all video information needs to become fetched manually
// Only do so with `MAX_CONCURRENT_IMPORT_CALLS` videos at once to prevent performance issues
@ -125,13 +136,15 @@ object LocalPlaylistsRepository {
}
}
suspend fun createPlaylist(playlistName: String): String {
override suspend fun createPlaylist(playlistName: String): String {
val playlist = LocalPlaylist(name = playlistName, thumbnailUrl = "")
return DatabaseHolder.Database.localPlaylistsDao().createPlaylist(playlist).toString()
}
suspend fun deletePlaylist(playlistId: String) {
override suspend fun deletePlaylist(playlistId: String): Boolean {
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId)
return true
}
}

View File

@ -0,0 +1,81 @@
package com.github.libretube.repo
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.EditPlaylistBody
import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.obj.PipedImportPlaylist
class PipedPlaylistRepository: PlaylistRepository {
private fun Message.isOk() = this.message == "ok"
private val token get() = PreferenceHelper.getToken()
override suspend fun getPlaylist(playlistId: String): Playlist {
return RetrofitInstance.authApi.getPlaylist(playlistId)
}
override suspend fun getPlaylists(): List<Playlists> {
return RetrofitInstance.authApi.getUserPlaylists(token)
}
override suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
val playlist = EditPlaylistBody(playlistId, videoIds = videos.map { it.url!!.toID() })
return RetrofitInstance.authApi.addToPlaylist(token, playlist).isOk()
}
override suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
val playlist = EditPlaylistBody(playlistId, newName = newName)
return RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk()
}
override suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
val playlist = EditPlaylistBody(playlistId, description = newDescription)
return RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk()
}
override suspend fun clonePlaylist(playlistId: String): String? {
return RetrofitInstance.authApi.clonePlaylist(
token,
EditPlaylistBody(playlistId)
).playlistId
}
override suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean {
return RetrofitInstance.authApi.removeFromPlaylist(
PreferenceHelper.getToken(),
EditPlaylistBody(playlistId = playlistId, index = index)
).isOk()
}
override suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) {
for (playlist in playlists) {
val playlistId = PlaylistsHelper.createPlaylist(playlist.name!!) ?: return
val streams = playlist.videos.map { StreamItem(url = it) }
PlaylistsHelper.addToPlaylist(playlistId, *streams.toTypedArray())
}
}
override suspend fun createPlaylist(playlistName: String): String? {
return RetrofitInstance.authApi.createPlaylist(
token,
Playlists(name = playlistName)
).playlistId
}
override suspend fun deletePlaylist(playlistId: String): Boolean {
return runCatching {
RetrofitInstance.authApi.deletePlaylist(
PreferenceHelper.getToken(),
EditPlaylistBody(playlistId)
).isOk()
}.getOrDefault(false)
}
}

View File

@ -0,0 +1,19 @@
package com.github.libretube.repo
import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.Playlists
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.obj.PipedImportPlaylist
interface PlaylistRepository {
suspend fun getPlaylist(playlistId: String): Playlist
suspend fun getPlaylists(): List<Playlists>
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean
suspend fun renamePlaylist(playlistId: String, newName: String): Boolean
suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean
suspend fun clonePlaylist(playlistId: String): String?
suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>)
suspend fun createPlaylist(playlistName: String): String?
suspend fun deletePlaylist(playlistId: String): Boolean
}

View File

@ -10,8 +10,6 @@ import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.constants.IntentData
import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.serializable
import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -21,14 +19,14 @@ import kotlinx.coroutines.withContext
class DeletePlaylistDialog : DialogFragment() {
private lateinit var playlistId: String
private lateinit var playlistType: PlaylistType
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
playlistId = it.getString(IntentData.playlistId)!!
playlistType = it.serializable(IntentData.playlistType)!!
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.deletePlaylist)
@ -39,7 +37,7 @@ class DeletePlaylistDialog : DialogFragment() {
.apply {
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
val success = PlaylistsHelper.deletePlaylist(playlistId, playlistType)
val success = PlaylistsHelper.deletePlaylist(playlistId)
context.toastFromMainDispatcher(
if (success) R.string.success else R.string.fail
)

View File

@ -123,8 +123,7 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() {
R.string.deletePlaylist -> {
val newDeletePlaylistDialog = DeletePlaylistDialog()
newDeletePlaylistDialog.arguments = bundleOf(
IntentData.playlistId to playlistId,
IntentData.playlistType to playlistType
IntentData.playlistId to playlistId
)
newDeletePlaylistDialog.show(mFragmentManager, null)
}