mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
Merge pull request #6932 from Bnyro/master
refactor: move local playlists logic to LocalPlaylistsRepository
This commit is contained in:
commit
2562706872
@ -0,0 +1,137 @@
|
|||||||
|
package com.github.libretube.api
|
||||||
|
|
||||||
|
import com.github.libretube.api.PlaylistsHelper.MAX_CONCURRENT_IMPORT_CALLS
|
||||||
|
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.db.DatabaseHolder
|
||||||
|
import com.github.libretube.db.obj.LocalPlaylist
|
||||||
|
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 {
|
||||||
|
val relation = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
.first { it.playlist.id.toString() == playlistId }
|
||||||
|
|
||||||
|
return Playlist(
|
||||||
|
name = relation.playlist.name,
|
||||||
|
description = relation.playlist.description,
|
||||||
|
thumbnailUrl = ProxyHelper.rewriteUrl(relation.playlist.thumbnailUrl),
|
||||||
|
videos = relation.videos.size,
|
||||||
|
relatedStreams = relation.videos.map { it.toStreamItem() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getPlaylists(): List<Playlists> {
|
||||||
|
return DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
.map {
|
||||||
|
Playlists(
|
||||||
|
id = it.playlist.id.toString(),
|
||||||
|
name = it.playlist.name,
|
||||||
|
shortDescription = it.playlist.description,
|
||||||
|
thumbnail = ProxyHelper.rewriteUrl(it.playlist.thumbnailUrl),
|
||||||
|
videos = it.videos.size.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem) {
|
||||||
|
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
.first { it.playlist.id.toString() == playlistId }
|
||||||
|
|
||||||
|
for (video in videos) {
|
||||||
|
val localPlaylistItem = video.toLocalPlaylistItem(playlistId)
|
||||||
|
// avoid duplicated videos in a playlist
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao()
|
||||||
|
.deletePlaylistItemsByVideoId(playlistId, localPlaylistItem.videoId)
|
||||||
|
|
||||||
|
// add the new video to the database
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
|
||||||
|
|
||||||
|
val playlist = localPlaylist.playlist
|
||||||
|
if (playlist.thumbnailUrl.isEmpty()) {
|
||||||
|
// set the new playlist thumbnail URL
|
||||||
|
localPlaylistItem.thumbnailUrl?.let {
|
||||||
|
playlist.thumbnailUrl = it
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun renamePlaylist(playlistId: String, newName: String) {
|
||||||
|
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
.first { it.playlist.id.toString() == playlistId }.playlist
|
||||||
|
playlist.name = newName
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun changePlaylistDescription(playlistId: String, newDescription: String) {
|
||||||
|
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
.first { it.playlist.id.toString() == playlistId }.playlist
|
||||||
|
playlist.description = newDescription
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clonePlaylist(playlistId: String): String? {
|
||||||
|
val playlist = RetrofitInstance.api.getPlaylist(playlistId)
|
||||||
|
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null
|
||||||
|
|
||||||
|
PlaylistsHelper.addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray())
|
||||||
|
|
||||||
|
var nextPage = playlist.nextpage
|
||||||
|
while (nextPage != null) {
|
||||||
|
nextPage = runCatching {
|
||||||
|
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage!!).apply {
|
||||||
|
PlaylistsHelper.addToPlaylist(newPlaylist, *relatedStreams.toTypedArray())
|
||||||
|
}.nextpage
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlistId
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeFromPlaylist(playlistId: String, index: Int) {
|
||||||
|
val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
.first { it.playlist.id.toString() == playlistId }
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
|
||||||
|
transaction.videos[index]
|
||||||
|
)
|
||||||
|
// set a new playlist thumbnail if the first video got removed
|
||||||
|
if (index == 0) {
|
||||||
|
transaction.playlist.thumbnailUrl =
|
||||||
|
transaction.videos.getOrNull(1)?.thumbnailUrl.orEmpty()
|
||||||
|
}
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) {
|
||||||
|
for (playlist in playlists) {
|
||||||
|
val playlistId = createPlaylist(playlist.name!!) ?: return
|
||||||
|
|
||||||
|
// 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
|
||||||
|
for (videoIdList in playlist.videos.chunked(MAX_CONCURRENT_IMPORT_CALLS)) {
|
||||||
|
val streams = videoIdList.parallelMap {
|
||||||
|
runCatching { StreamsExtractor.extractStreams(it) }
|
||||||
|
.getOrNull()
|
||||||
|
?.toStreamItem(it)
|
||||||
|
}.filterNotNull()
|
||||||
|
|
||||||
|
PlaylistsHelper.addToPlaylist(playlistId, *streams.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createPlaylist(playlistName: String): String {
|
||||||
|
val playlist = LocalPlaylist(name = playlistName, thumbnailUrl = "")
|
||||||
|
return DatabaseHolder.Database.localPlaylistsDao().createPlaylist(playlist).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deletePlaylist(playlistId: String) {
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId)
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,9 @@ import com.github.libretube.api.obj.Playlist
|
|||||||
import com.github.libretube.api.obj.Playlists
|
import com.github.libretube.api.obj.Playlists
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.db.DatabaseHolder
|
|
||||||
import com.github.libretube.db.obj.LocalPlaylist
|
|
||||||
import com.github.libretube.enums.PlaylistType
|
import com.github.libretube.enums.PlaylistType
|
||||||
import com.github.libretube.extensions.parallelMap
|
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.helpers.PreferenceHelper
|
import com.github.libretube.helpers.PreferenceHelper
|
||||||
import com.github.libretube.helpers.ProxyHelper
|
|
||||||
import com.github.libretube.obj.PipedImportPlaylist
|
import com.github.libretube.obj.PipedImportPlaylist
|
||||||
import com.github.libretube.util.deArrow
|
import com.github.libretube.util.deArrow
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -24,7 +20,7 @@ import kotlinx.coroutines.withContext
|
|||||||
object PlaylistsHelper {
|
object PlaylistsHelper {
|
||||||
private val pipedPlaylistRegex =
|
private val pipedPlaylistRegex =
|
||||||
"[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}".toRegex()
|
"[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}".toRegex()
|
||||||
private const val MAX_CONCURRENT_IMPORT_CALLS = 5
|
const val MAX_CONCURRENT_IMPORT_CALLS = 5
|
||||||
|
|
||||||
private val token get() = PreferenceHelper.getToken()
|
private val token get() = PreferenceHelper.getToken()
|
||||||
val loggedIn: Boolean get() = token.isNotEmpty()
|
val loggedIn: Boolean get() = token.isNotEmpty()
|
||||||
@ -34,16 +30,7 @@ object PlaylistsHelper {
|
|||||||
val playlists = if (loggedIn) {
|
val playlists = if (loggedIn) {
|
||||||
RetrofitInstance.authApi.getUserPlaylists(token)
|
RetrofitInstance.authApi.getUserPlaylists(token)
|
||||||
} else {
|
} else {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().getAll()
|
LocalPlaylistsRepository.getPlaylists()
|
||||||
.map {
|
|
||||||
Playlists(
|
|
||||||
id = it.playlist.id.toString(),
|
|
||||||
name = it.playlist.name,
|
|
||||||
shortDescription = it.playlist.description,
|
|
||||||
thumbnail = ProxyHelper.rewriteUrl(it.playlist.thumbnailUrl),
|
|
||||||
videos = it.videos.size.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sortPlaylists(playlists)
|
sortPlaylists(playlists)
|
||||||
}
|
}
|
||||||
@ -67,17 +54,7 @@ object PlaylistsHelper {
|
|||||||
return when (getPrivatePlaylistType(playlistId)) {
|
return when (getPrivatePlaylistType(playlistId)) {
|
||||||
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 -> LocalPlaylistsRepository.getPlaylist(playlistId)
|
||||||
val relation = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
|
||||||
.first { it.playlist.id.toString() == playlistId }
|
|
||||||
return Playlist(
|
|
||||||
name = relation.playlist.name,
|
|
||||||
description = relation.playlist.description,
|
|
||||||
thumbnailUrl = ProxyHelper.rewriteUrl(relation.playlist.thumbnailUrl),
|
|
||||||
videos = relation.videos.size,
|
|
||||||
relatedStreams = relation.videos.map { it.toStreamItem() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.apply {
|
}.apply {
|
||||||
relatedStreams = relatedStreams.deArrow()
|
relatedStreams = relatedStreams.deArrow()
|
||||||
}
|
}
|
||||||
@ -85,8 +62,7 @@ object PlaylistsHelper {
|
|||||||
|
|
||||||
suspend fun createPlaylist(playlistName: String): String? {
|
suspend fun createPlaylist(playlistName: String): String? {
|
||||||
return if (!loggedIn) {
|
return if (!loggedIn) {
|
||||||
val playlist = LocalPlaylist(name = playlistName, thumbnailUrl = "")
|
LocalPlaylistsRepository.createPlaylist(playlistName)
|
||||||
DatabaseHolder.Database.localPlaylistsDao().createPlaylist(playlist).toString()
|
|
||||||
} else {
|
} else {
|
||||||
RetrofitInstance.authApi.createPlaylist(
|
RetrofitInstance.authApi.createPlaylist(
|
||||||
token,
|
token,
|
||||||
@ -97,27 +73,7 @@ object PlaylistsHelper {
|
|||||||
|
|
||||||
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
|
suspend fun addToPlaylist(playlistId: String, vararg videos: StreamItem): Boolean {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
LocalPlaylistsRepository.addToPlaylist(playlistId, *videos)
|
||||||
.first { it.playlist.id.toString() == playlistId }
|
|
||||||
|
|
||||||
for (video in videos) {
|
|
||||||
val localPlaylistItem = video.toLocalPlaylistItem(playlistId)
|
|
||||||
// avoid duplicated videos in a playlist
|
|
||||||
DatabaseHolder.Database.localPlaylistsDao()
|
|
||||||
.deletePlaylistItemsByVideoId(playlistId, localPlaylistItem.videoId)
|
|
||||||
|
|
||||||
// add the new video to the database
|
|
||||||
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
|
|
||||||
|
|
||||||
val playlist = localPlaylist.playlist
|
|
||||||
if (playlist.thumbnailUrl.isEmpty()) {
|
|
||||||
// set the new playlist thumbnail URL
|
|
||||||
localPlaylistItem.thumbnailUrl?.let {
|
|
||||||
playlist.thumbnailUrl = it
|
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,74 +82,45 @@ object PlaylistsHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
|
suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
|
||||||
return if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
LocalPlaylistsRepository.renamePlaylist(playlistId, newName)
|
||||||
.first { it.playlist.id.toString() == playlistId }.playlist
|
return true
|
||||||
playlist.name = newName
|
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
val playlist = EditPlaylistBody(playlistId, newName = newName)
|
|
||||||
RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val playlist = EditPlaylistBody(playlistId, newName = newName)
|
||||||
|
return RetrofitInstance.authApi.renamePlaylist(token, playlist).isOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
|
suspend fun changePlaylistDescription(playlistId: String, newDescription: String): Boolean {
|
||||||
return if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
val playlist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
LocalPlaylistsRepository.changePlaylistDescription(playlistId, newDescription)
|
||||||
.first { it.playlist.id.toString() == playlistId }.playlist
|
return true
|
||||||
playlist.description = newDescription
|
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(playlist)
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
val playlist = EditPlaylistBody(playlistId, description = newDescription)
|
|
||||||
RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val playlist = EditPlaylistBody(playlistId, description = newDescription)
|
||||||
|
return RetrofitInstance.authApi.changePlaylistDescription(token, playlist).isOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean {
|
suspend fun removeFromPlaylist(playlistId: String, index: Int): Boolean {
|
||||||
return if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
LocalPlaylistsRepository.removeFromPlaylist(playlistId, index)
|
||||||
.first { it.playlist.id.toString() == playlistId }
|
return true
|
||||||
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
|
|
||||||
transaction.videos[index]
|
|
||||||
)
|
|
||||||
// set a new playlist thumbnail if the first video got removed
|
|
||||||
if (index == 0) {
|
|
||||||
transaction.playlist.thumbnailUrl =
|
|
||||||
transaction.videos.getOrNull(1)?.thumbnailUrl.orEmpty()
|
|
||||||
}
|
}
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(transaction.playlist)
|
|
||||||
true
|
return RetrofitInstance.authApi.removeFromPlaylist(
|
||||||
} else {
|
|
||||||
RetrofitInstance.authApi.removeFromPlaylist(
|
|
||||||
PreferenceHelper.getToken(),
|
PreferenceHelper.getToken(),
|
||||||
EditPlaylistBody(playlistId = playlistId, index = index)
|
EditPlaylistBody(playlistId = playlistId, index = index)
|
||||||
).isOk()
|
).isOk()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) =
|
suspend fun importPlaylists(playlists: List<PipedImportPlaylist>) =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
if (!loggedIn) return@withContext LocalPlaylistsRepository.importPlaylists(playlists)
|
||||||
|
|
||||||
for (playlist in playlists) {
|
for (playlist in playlists) {
|
||||||
val playlistId = createPlaylist(playlist.name!!) ?: return@withContext
|
val playlistId = createPlaylist(playlist.name!!) ?: return@withContext
|
||||||
// if logged in, add the playlists by their ID via an api call
|
|
||||||
if (loggedIn) {
|
|
||||||
val streams = playlist.videos.map { StreamItem(url = it) }
|
val streams = playlist.videos.map { StreamItem(url = it) }
|
||||||
addToPlaylist(playlistId, *streams.toTypedArray())
|
addToPlaylist(playlistId, *streams.toTypedArray())
|
||||||
} else {
|
|
||||||
// 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
|
|
||||||
for (videoIdList in playlist.videos.chunked(MAX_CONCURRENT_IMPORT_CALLS)) {
|
|
||||||
val streams = videoIdList.parallelMap {
|
|
||||||
runCatching { StreamsExtractor.extractStreams(it) }
|
|
||||||
.getOrNull()
|
|
||||||
?.toStreamItem(it)
|
|
||||||
}.filterNotNull()
|
|
||||||
|
|
||||||
addToPlaylist(playlistId, *streams.toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,20 +133,7 @@ object PlaylistsHelper {
|
|||||||
|
|
||||||
suspend fun clonePlaylist(playlistId: String): String? {
|
suspend fun clonePlaylist(playlistId: String): String? {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
val playlist = RetrofitInstance.api.getPlaylist(playlistId)
|
return LocalPlaylistsRepository.clonePlaylist(playlistId)
|
||||||
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null
|
|
||||||
|
|
||||||
addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray())
|
|
||||||
|
|
||||||
var nextPage = playlist.nextpage
|
|
||||||
while (nextPage != null) {
|
|
||||||
nextPage = runCatching {
|
|
||||||
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage!!).apply {
|
|
||||||
addToPlaylist(newPlaylist, *relatedStreams.toTypedArray())
|
|
||||||
}.nextpage
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
return playlistId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RetrofitInstance.authApi.clonePlaylist(
|
return RetrofitInstance.authApi.clonePlaylist(
|
||||||
@ -230,8 +144,7 @@ object PlaylistsHelper {
|
|||||||
|
|
||||||
suspend fun deletePlaylist(playlistId: String, playlistType: PlaylistType): Boolean {
|
suspend fun deletePlaylist(playlistId: String, playlistType: PlaylistType): Boolean {
|
||||||
if (playlistType == PlaylistType.LOCAL) {
|
if (playlistType == PlaylistType.LOCAL) {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistById(playlistId)
|
LocalPlaylistsRepository.deletePlaylist(playlistId)
|
||||||
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByPlaylistId(playlistId)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user