Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make LibreTube app backups import-compatible with Piped #5667

Merged
merged 1 commit into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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")

Check failure on line 15 in app/src/main/java/com/github/libretube/db/obj/LocalSubscription.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Missing spacing before ":" Raw Output: app/src/main/java/com/github/libretube/db/obj/LocalSubscription.kt:15:35: error: Missing spacing before ":" (standard:colon-spacing)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down
41 changes: 8 additions & 33 deletions app/src/main/java/com/github/libretube/helpers/ImportHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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/", "")
}
}

Expand All @@ -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/", "")
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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())

Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}
}
32 changes: 29 additions & 3 deletions app/src/main/java/com/github/libretube/obj/BackupFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,43 @@
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,

Check failure on line 23 in app/src/main/java/com/github/libretube/obj/BackupFile.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected blank line(s) in value parameter list Raw Output: app/src/main/java/com/github/libretube/obj/BackupFile.kt:23:1: error: Unexpected blank line(s) in value parameter list (standard:no-blank-line-in-list)
//
// 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(),

Check failure on line 32 in app/src/main/java/com/github/libretube/obj/BackupFile.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected blank line(s) in value parameter list Raw Output: app/src/main/java/com/github/libretube/obj/BackupFile.kt:32:1: error: Unexpected blank line(s) in value parameter list (standard:no-blank-line-in-list)
//
// Preferences, stored as a key value map
//
var preferences: List<PreferenceItem>? = emptyList(),
var channelGroups: List<SubscriptionGroup>? = emptyList()

Check failure on line 37 in app/src/main/java/com/github/libretube/obj/BackupFile.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected blank line(s) in value parameter list Raw Output: app/src/main/java/com/github/libretube/obj/BackupFile.kt:37:1: error: Unexpected blank line(s) in value parameter list (standard:no-blank-line-in-list)
//
// Database objects with compatibility for Piped imports/exports
//
@JsonNames("groups", "channelGroups")
var groups: List<SubscriptionGroup>? = emptyList(),

Check failure on line 43 in app/src/main/java/com/github/libretube/obj/BackupFile.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected blank line(s) in value parameter list Raw Output: app/src/main/java/com/github/libretube/obj/BackupFile.kt:43:1: error: Unexpected blank line(s) in value parameter list (standard:no-blank-line-in-list)
@JsonNames("subscriptions", "localSubscriptions")
var subscriptions: List<LocalSubscription>? = emptyList(),

Check failure on line 46 in app/src/main/java/com/github/libretube/obj/BackupFile.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Unexpected blank line(s) in value parameter list Raw Output: app/src/main/java/com/github/libretube/obj/BackupFile.kt:46:1: error: Unexpected blank line(s) in value parameter list (standard:no-blank-line-in-list)
// playlists are exported in two different formats because the formats differ too much unfortunately
var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(),
var playlists: List<PipedImportPlaylist>? = emptyList(),
)
Original file line number Diff line number Diff line change
@@ -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"

Check failure on line 11 in app/src/main/java/com/github/libretube/obj/FreetubeSubscription.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Missing trailing comma before ")" Raw Output: app/src/main/java/com/github/libretube/obj/FreetubeSubscription.kt:11:79: error: Missing trailing comma before ")" (standard:trailing-comma-on-declaration-site)
)
11 changes: 0 additions & 11 deletions app/src/main/java/com/github/libretube/obj/PipedBackupFile.kt

This file was deleted.

This file was deleted.

10 changes: 10 additions & 0 deletions app/src/main/java/com/github/libretube/obj/PipedPlaylistFile.kt
Original file line number Diff line number Diff line change
@@ -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()

Check failure on line 9 in app/src/main/java/com/github/libretube/obj/PipedPlaylistFile.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 Missing trailing comma before ")" Raw Output: app/src/main/java/com/github/libretube/obj/PipedPlaylistFile.kt:9:59: error: Missing trailing comma before ")" (standard:trailing-comma-on-declaration-site)
)
12 changes: 10 additions & 2 deletions app/src/main/java/com/github/libretube/ui/dialogs/BackupDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
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
Expand All @@ -40,7 +42,7 @@
})

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 = {
Expand All @@ -53,10 +55,16 @@

data object LocalPlaylists : BackupOption(R.string.local_playlists, onSelected = {
it.localPlaylists = Database.localPlaylistsDao().getAll()
it.playlists = it.localPlaylists?.map { (playlist, playlistVideos) ->

Check failure on line 58 in app/src/main/java/com/github/libretube/ui/dialogs/BackupDialog.kt

View workflow job for this annotation

GitHub Actions / Check Code Quality

[ktlint] reported by reviewdog 🐶 A multiline expression should start on a new line Raw Output: app/src/main/java/com/github/libretube/ui/dialogs/BackupDialog.kt:58:28: error: A multiline expression should start on a new line (standard:multiline-expression-wrapping)
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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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
Expand Down
14 changes: 0 additions & 14 deletions app/src/main/res/xml/import_export_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading