mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
Merge pull request #5667 from Bnyro/master
feat: make LibreTube app backups import-compatible with Piped
This commit is contained in:
commit
19bc8025c3
@ -1,11 +1,16 @@
|
||||
package com.github.libretube.db.obj
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.github.libretube.ui.dialogs.ShareDialog
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@Entity(tableName = "localSubscription")
|
||||
data class LocalSubscription(
|
||||
@PrimaryKey val channelId: String = ""
|
||||
)
|
||||
@PrimaryKey val channelId: String,
|
||||
@Ignore val url: String = "",
|
||||
) {
|
||||
constructor(channelId: String): this(channelId, "${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/$channelId")
|
||||
}
|
||||
|
@ -2,12 +2,19 @@ package com.github.libretube.db.obj
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Entity(tableName = "subscriptionGroups")
|
||||
data class SubscriptionGroup(
|
||||
@PrimaryKey var name: String,
|
||||
@PrimaryKey
|
||||
@SerialName("groupName")
|
||||
@JsonNames("groupName", "name")
|
||||
var name: String,
|
||||
var channels: List<String> = listOf(),
|
||||
var index: Int = 0
|
||||
)
|
||||
|
@ -49,10 +49,10 @@ object BackupHelper {
|
||||
Database.watchHistoryDao().insertAll(backupFile.watchHistory.orEmpty())
|
||||
Database.searchHistoryDao().insertAll(backupFile.searchHistory.orEmpty())
|
||||
Database.watchPositionDao().insertAll(backupFile.watchPositions.orEmpty())
|
||||
Database.localSubscriptionDao().insertAll(backupFile.localSubscriptions.orEmpty())
|
||||
Database.localSubscriptionDao().insertAll(backupFile.subscriptions.orEmpty())
|
||||
Database.customInstanceDao().insertAll(backupFile.customInstances.orEmpty())
|
||||
Database.playlistBookmarkDao().insertAll(backupFile.playlistBookmarks.orEmpty())
|
||||
Database.subscriptionGroupsDao().insertAll(backupFile.channelGroups.orEmpty())
|
||||
Database.subscriptionGroupsDao().insertAll(backupFile.groups.orEmpty())
|
||||
|
||||
backupFile.localPlaylists?.forEach {
|
||||
// the playlist will be created with an id of 0, so that Room will auto generate a
|
||||
@ -72,6 +72,7 @@ object BackupHelper {
|
||||
*/
|
||||
private fun restorePreferences(context: Context, preferences: List<PreferenceItem>?) {
|
||||
if (preferences == null) return
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
|
||||
// clear the previous settings
|
||||
clear()
|
||||
|
@ -9,7 +9,6 @@ import com.github.libretube.api.PlaylistsHelper
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.api.SubscriptionHelper
|
||||
import com.github.libretube.db.DatabaseHolder.Database
|
||||
import com.github.libretube.db.obj.SubscriptionGroup
|
||||
import com.github.libretube.enums.ImportFormat
|
||||
import com.github.libretube.extensions.TAG
|
||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||
@ -19,8 +18,8 @@ import com.github.libretube.obj.FreetubeSubscriptions
|
||||
import com.github.libretube.obj.NewPipeSubscription
|
||||
import com.github.libretube.obj.NewPipeSubscriptions
|
||||
import com.github.libretube.obj.PipedImportPlaylist
|
||||
import com.github.libretube.obj.PipedBackupFile
|
||||
import com.github.libretube.obj.PipedChannelGroup
|
||||
import com.github.libretube.obj.PipedPlaylistFile
|
||||
import com.github.libretube.ui.dialogs.ShareDialog
|
||||
import java.util.Date
|
||||
import java.util.stream.Collectors
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
@ -63,7 +62,7 @@ object ImportHelper {
|
||||
JsonHelper.json.decodeFromStream<NewPipeSubscriptions>(it)
|
||||
}
|
||||
subscriptions?.subscriptions.orEmpty().map {
|
||||
it.url.replace("https://www.youtube.com/channel/", "")
|
||||
it.url.replace("${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/", "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +71,7 @@ object ImportHelper {
|
||||
JsonHelper.json.decodeFromStream<FreetubeSubscriptions>(it)
|
||||
}
|
||||
subscriptions?.subscriptions.orEmpty().map {
|
||||
it.url.replace("https://www.youtube.com/channel/", "")
|
||||
it.url.replace("${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/", "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ object ImportHelper {
|
||||
when (importFormat) {
|
||||
ImportFormat.NEWPIPE -> {
|
||||
val newPipeChannels = subs.map {
|
||||
NewPipeSubscription(it.name, 0, "https://www.youtube.com${it.url}")
|
||||
NewPipeSubscription(it.name, 0, "${ShareDialog.YOUTUBE_FRONTEND_URL}${it.url}")
|
||||
}
|
||||
val newPipeSubscriptions = NewPipeSubscriptions(subscriptions = newPipeChannels)
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
@ -117,7 +116,7 @@ object ImportHelper {
|
||||
|
||||
ImportFormat.FREETUBE -> {
|
||||
val freeTubeChannels = subs.map {
|
||||
FreetubeSubscription(it.name, "", "https://www.youtube.com${it.url}")
|
||||
FreetubeSubscription(it.name, "", "${ShareDialog.YOUTUBE_FRONTEND_URL}${it.url}")
|
||||
}
|
||||
val freeTubeSubscriptions = FreetubeSubscriptions(subscriptions = freeTubeChannels)
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
@ -141,7 +140,7 @@ object ImportHelper {
|
||||
when (importFormat) {
|
||||
ImportFormat.PIPED -> {
|
||||
val playlistFile = activity.contentResolver.openInputStream(uri)?.use {
|
||||
JsonHelper.json.decodeFromStream<PipedBackupFile>(it)
|
||||
JsonHelper.json.decodeFromStream<PipedPlaylistFile>(it)
|
||||
}
|
||||
importPlaylists.addAll(playlistFile?.playlists.orEmpty())
|
||||
|
||||
@ -231,7 +230,7 @@ object ImportHelper {
|
||||
when (importFormat) {
|
||||
ImportFormat.PIPED -> {
|
||||
val playlists = PlaylistsHelper.exportPipedPlaylists()
|
||||
val playlistFile = PipedBackupFile("Piped", 1, playlists = playlists)
|
||||
val playlistFile = PipedPlaylistFile(playlists = playlists)
|
||||
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
JsonHelper.json.encodeToStream(playlistFile, it)
|
||||
@ -251,28 +250,4 @@ object ImportHelper {
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun importGroups(activity: Activity, uri: Uri) {
|
||||
val pipedFile = activity.contentResolver.openInputStream(uri)?.use {
|
||||
JsonHelper.json.decodeFromStream<PipedBackupFile>(it)
|
||||
} ?: return
|
||||
|
||||
pipedFile.groups.forEach {
|
||||
val group = SubscriptionGroup(it.groupName, it.channels)
|
||||
Database.subscriptionGroupsDao().createGroup(group)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun exportGroups(activity: Activity, uri: Uri) {
|
||||
val channelGroups = Database.subscriptionGroupsDao().getAll().map {
|
||||
PipedChannelGroup(it.name, it.channels)
|
||||
}
|
||||
val pipedFile = PipedBackupFile("Piped", 1, groups = channelGroups)
|
||||
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
JsonHelper.json.encodeToStream(pipedFile, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,17 +8,43 @@ import com.github.libretube.db.obj.SearchHistoryItem
|
||||
import com.github.libretube.db.obj.SubscriptionGroup
|
||||
import com.github.libretube.db.obj.WatchHistoryItem
|
||||
import com.github.libretube.db.obj.WatchPosition
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
data class BackupFile(
|
||||
//
|
||||
// some stuff for compatibility with Piped imports
|
||||
//
|
||||
val format: String = "Piped",
|
||||
val version: Int = 1,
|
||||
|
||||
//
|
||||
// only compatible with LibreTube itself, database objects
|
||||
//
|
||||
var watchHistory: List<WatchHistoryItem>? = emptyList(),
|
||||
var watchPositions: List<WatchPosition>? = emptyList(),
|
||||
var searchHistory: List<SearchHistoryItem>? = emptyList(),
|
||||
var localSubscriptions: List<LocalSubscription>? = emptyList(),
|
||||
var customInstances: List<CustomInstance>? = emptyList(),
|
||||
var playlistBookmarks: List<PlaylistBookmark>? = emptyList(),
|
||||
var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(),
|
||||
|
||||
//
|
||||
// Preferences, stored as a key value map
|
||||
//
|
||||
var preferences: List<PreferenceItem>? = emptyList(),
|
||||
var channelGroups: List<SubscriptionGroup>? = emptyList()
|
||||
|
||||
//
|
||||
// Database objects with compatibility for Piped imports/exports
|
||||
//
|
||||
@JsonNames("groups", "channelGroups")
|
||||
var groups: List<SubscriptionGroup>? = emptyList(),
|
||||
|
||||
@JsonNames("subscriptions", "localSubscriptions")
|
||||
var subscriptions: List<LocalSubscription>? = emptyList(),
|
||||
|
||||
// playlists are exported in two different formats because the formats differ too much unfortunately
|
||||
var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(),
|
||||
var playlists: List<PipedImportPlaylist>? = emptyList(),
|
||||
)
|
||||
|
@ -1,11 +1,12 @@
|
||||
package com.github.libretube.obj
|
||||
|
||||
import com.github.libretube.ui.dialogs.ShareDialog
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class FreetubeSubscription(
|
||||
val name: String,
|
||||
@SerialName("id") val serviceId: String,
|
||||
val url: String = "https://www.youtube.com/channel/$serviceId"
|
||||
@SerialName("id") val channelId: String,
|
||||
val url: String = "${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/$channelId"
|
||||
)
|
||||
|
@ -1,11 +0,0 @@
|
||||
package com.github.libretube.obj
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PipedBackupFile(
|
||||
val format: String,
|
||||
val version: Int,
|
||||
val playlists: List<PipedImportPlaylist> = emptyList(),
|
||||
val groups: List<PipedChannelGroup> = emptyList()
|
||||
)
|
@ -1,9 +0,0 @@
|
||||
package com.github.libretube.obj
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PipedChannelGroup(
|
||||
val groupName: String,
|
||||
val channels: List<String>
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package com.github.libretube.obj
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PipedPlaylistFile(
|
||||
val format: String = "Piped",
|
||||
val version: Int = 1,
|
||||
val playlists: List<PipedImportPlaylist> = emptyList()
|
||||
)
|
@ -13,7 +13,9 @@ import com.github.libretube.constants.IntentData
|
||||
import com.github.libretube.db.DatabaseHolder.Database
|
||||
import com.github.libretube.helpers.PreferenceHelper
|
||||
import com.github.libretube.obj.BackupFile
|
||||
import com.github.libretube.obj.PipedImportPlaylist
|
||||
import com.github.libretube.obj.PreferenceItem
|
||||
import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -40,7 +42,7 @@ class BackupDialog : DialogFragment() {
|
||||
})
|
||||
|
||||
data object LocalSubscriptions : BackupOption(R.string.local_subscriptions, onSelected = {
|
||||
it.localSubscriptions = Database.localSubscriptionDao().getAll()
|
||||
it.subscriptions = Database.localSubscriptionDao().getAll()
|
||||
})
|
||||
|
||||
data object CustomInstances : BackupOption(R.string.backup_customInstances, onSelected = {
|
||||
@ -53,10 +55,16 @@ class BackupDialog : DialogFragment() {
|
||||
|
||||
data object LocalPlaylists : BackupOption(R.string.local_playlists, onSelected = {
|
||||
it.localPlaylists = Database.localPlaylistsDao().getAll()
|
||||
it.playlists = it.localPlaylists?.map { (playlist, playlistVideos) ->
|
||||
val videos = playlistVideos.map { item ->
|
||||
"${ShareDialog.YOUTUBE_FRONTEND_URL}/watch?v=${item.videoId}"
|
||||
}
|
||||
PipedImportPlaylist(playlist.name, "playlist", "private", videos)
|
||||
}
|
||||
})
|
||||
|
||||
data object SubscriptionGroups : BackupOption(R.string.channel_groups, onSelected = {
|
||||
it.channelGroups = Database.subscriptionGroupsDao().getAll()
|
||||
it.groups = Database.subscriptionGroupsDao().getAll()
|
||||
})
|
||||
|
||||
data object Preferences : BackupOption(R.string.preferences, onSelected = { file ->
|
||||
|
@ -114,22 +114,6 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val getChannelGroupsFile = registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) {
|
||||
it?.forEach { uri ->
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
ImportHelper.importGroups(requireActivity(), uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val createChannelGroupsFile = registerForActivityResult(ActivityResultContracts.CreateDocument(JSON)) {
|
||||
it?.let { uri ->
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
ImportHelper.exportGroups(requireActivity(), uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createImportFormatDialog(
|
||||
@StringRes titleStringId: Int,
|
||||
items: List<String>,
|
||||
@ -195,18 +179,6 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
true
|
||||
}
|
||||
|
||||
val importChannelGroups = findPreference<Preference>("import_groups")
|
||||
importChannelGroups?.setOnPreferenceClickListener {
|
||||
getChannelGroupsFile.launch(arrayOf(JSON))
|
||||
true
|
||||
}
|
||||
|
||||
val exportChannelGroups = findPreference<Preference>("export_groups")
|
||||
exportChannelGroups?.setOnPreferenceClickListener {
|
||||
createChannelGroupsFile.launch("piped-channel-groups.json")
|
||||
true
|
||||
}
|
||||
|
||||
childFragmentManager.setFragmentResultListener(
|
||||
BACKUP_DIALOG_REQUEST_KEY,
|
||||
this
|
||||
|
@ -31,20 +31,6 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/channel_groups">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_download_filled"
|
||||
app:key="import_groups"
|
||||
app:title="@string/import_groups" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_upload"
|
||||
app:key="export_groups"
|
||||
app:title="@string/export_groups" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/app_backup">
|
||||
|
||||
<Preference
|
||||
|
Loading…
x
Reference in New Issue
Block a user