Merge pull request #1321 from Bnyro/backup

Unified backup settings
This commit is contained in:
Bnyro 2022-09-18 16:58:19 +02:00 committed by GitHub
commit e16ff990f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 278 additions and 10 deletions

View File

@ -0,0 +1,38 @@
package com.github.libretube.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.BackupRowBinding
class BackupOptionsAdapter(
private val options: List<Int>,
private val onChange: (position: Int, isChecked: Boolean) -> Unit
) : RecyclerView.Adapter<BackupOptionsViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupOptionsViewHolder {
val binding = BackupRowBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return BackupOptionsViewHolder(binding)
}
override fun getItemCount(): Int {
return options.size
}
override fun onBindViewHolder(holder: BackupOptionsViewHolder, position: Int) {
holder.binding.apply {
title.text = root.context?.getString(options[position])
switchWidget.setOnCheckedChangeListener { _, isChecked ->
onChange.invoke(position, isChecked)
}
}
}
}
class BackupOptionsViewHolder(
val binding: BackupRowBinding
) : RecyclerView.ViewHolder(binding.root)

View File

@ -0,0 +1,71 @@
package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R
import com.github.libretube.adapters.BackupOptionsAdapter
import com.github.libretube.databinding.DialogBackupBinding
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.await
import com.github.libretube.obj.BackupFile
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class BackupDialog(
private val createBackupFile: (BackupFile) -> Unit
) : DialogFragment() {
private lateinit var binding: DialogBackupBinding
val backupFile = BackupFile()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val backupOptions = listOf(
R.string.watch_history,
R.string.watch_positions,
R.string.search_history,
R.string.local_subscriptions,
R.string.backup_customInstances
)
val selected = mutableListOf(false, false, false, false, false)
binding = DialogBackupBinding.inflate(layoutInflater)
binding.backupOptionsRecycler.layoutManager = LinearLayoutManager(context)
binding.backupOptionsRecycler.adapter = BackupOptionsAdapter(backupOptions) { position, isChecked ->
selected[position] = isChecked
}
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.backup)
.setView(binding.root)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.backup) { _, _ ->
Thread {
if (selected[0]) {
backupFile.watchHistory =
DatabaseHolder.db.watchHistoryDao().getAll()
}
if (selected[1]) {
backupFile.watchPositions =
DatabaseHolder.db.watchPositionDao().getAll()
}
if (selected[2]) {
backupFile.searchHistory =
DatabaseHolder.db.searchHistoryDao().getAll()
}
if (selected[3]) {
backupFile.localSubscriptions =
DatabaseHolder.db.localSubscriptionDao().getAll()
}
if (selected[4]) {
backupFile.customInstances =
DatabaseHolder.db.customInstanceDao().getAll()
}
}.await()
createBackupFile(backupFile)
}
.create()
}
}

View File

@ -0,0 +1,5 @@
package com.github.libretube.extensions
fun query(block: () -> Unit) {
Thread(block).start()
}

View File

@ -0,0 +1,15 @@
package com.github.libretube.obj
import com.github.libretube.db.obj.CustomInstance
import com.github.libretube.db.obj.LocalSubscription
import com.github.libretube.db.obj.SearchHistoryItem
import com.github.libretube.db.obj.WatchHistoryItem
import com.github.libretube.db.obj.WatchPosition
data class BackupFile(
var watchHistory: List<WatchHistoryItem>? = null,
var watchPositions: List<WatchPosition>? = null,
var searchHistory: List<SearchHistoryItem>? = null,
var localSubscriptions: List<LocalSubscription>? = null,
var customInstances: List<CustomInstance>? = null
)

View File

@ -10,6 +10,8 @@ import androidx.preference.Preference
import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.dialogs.BackupDialog
import com.github.libretube.obj.BackupFile
import com.github.libretube.util.BackupHelper
import com.github.libretube.util.ImageHelper
import com.github.libretube.util.PreferenceHelper
@ -18,24 +20,41 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
class AdvancedSettings : MaterialPreferenceFragment() {
/**
* result listeners for importing and exporting subscriptions
*/
private lateinit var getContent: ActivityResultLauncher<String>
private lateinit var createFile: ActivityResultLauncher<String>
// backup and restore prefs
private lateinit var getPrefFile: ActivityResultLauncher<String>
private lateinit var createPrefFile: ActivityResultLauncher<String>
// backup and restore database
private lateinit var getBackupFile: ActivityResultLauncher<String>
private lateinit var createBackupFile: ActivityResultLauncher<String>
private var backupFile = BackupFile()
override fun onCreate(savedInstanceState: Bundle?) {
getContent =
getPrefFile =
registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
BackupHelper(requireContext()).restoreSharedPreferences(uri)
}
createFile = registerForActivityResult(
createPrefFile = registerForActivityResult(
CreateDocument("application/json")
) { uri: Uri? ->
BackupHelper(requireContext()).backupSharedPreferences(uri)
}
getBackupFile =
registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
BackupHelper(requireContext()).restoreAdvancedBackup(uri)
}
createBackupFile = registerForActivityResult(
CreateDocument("application/json")
) { uri: Uri? ->
BackupHelper(requireContext()).advancedBackup(uri, backupFile)
}
super.onCreate(savedInstanceState)
}
@ -59,18 +78,34 @@ class AdvancedSettings : MaterialPreferenceFragment() {
val backupSettings = findPreference<Preference>(PreferenceKeys.BACKUP_SETTINGS)
backupSettings?.setOnPreferenceClickListener {
createFile.launch("preferences.xml")
createPrefFile.launch("preferences.xml")
true
}
val restoreSettings = findPreference<Preference>(PreferenceKeys.RESTORE_SETTINGS)
restoreSettings?.setOnPreferenceClickListener {
getContent.launch("*/*")
getPrefFile.launch("*/*")
// reset the token
PreferenceHelper.setToken("")
activity?.recreate()
true
}
val advancesBackup = findPreference<Preference>("backup")
advancesBackup?.setOnPreferenceClickListener {
BackupDialog {
backupFile = it
createBackupFile.launch("backup.json")
}
.show(childFragmentManager, null)
true
}
val restoreAdvancedBackup = findPreference<Preference>("restore")
restoreAdvancedBackup?.setOnPreferenceClickListener {
getBackupFile.launch("application/json")
true
}
}
private fun showResetDialog() {

View File

@ -4,6 +4,10 @@ import android.content.Context
import android.net.Uri
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.query
import com.github.libretube.obj.BackupFile
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.ObjectInputStream
@ -64,4 +68,54 @@ class BackupHelper(private val context: Context) {
e.printStackTrace()
}
}
/**
* Backup the database
*/
fun advancedBackup(uri: Uri?, backupFile: BackupFile) {
if (uri == null) return
try {
context.contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { fileOutputStream ->
fileOutputStream.write(
ObjectMapper().writeValueAsBytes(backupFile)
)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* Restore a database backup
*/
fun restoreAdvancedBackup(uri: Uri?) {
if (uri == null) return
val mapper = ObjectMapper()
val json = context.contentResolver.openInputStream(uri)?.use {
it.bufferedReader().use { reader -> reader.readText() }
}.orEmpty()
val backupFile = mapper.readValue(json, BackupFile::class.java)
query {
DatabaseHolder.db.watchHistoryDao().insertAll(
*backupFile.watchHistory?.toTypedArray().orEmpty()
)
DatabaseHolder.db.searchHistoryDao().insertAll(
*backupFile.searchHistory?.toTypedArray().orEmpty()
)
DatabaseHolder.db.watchPositionDao().insertAll(
*backupFile.watchPositions?.toTypedArray().orEmpty()
)
DatabaseHolder.db.localSubscriptionDao().insertAll(
*backupFile.localSubscriptions?.toTypedArray().orEmpty()
)
DatabaseHolder.db.customInstanceDao().insertAll(
*backupFile.customInstances?.toTypedArray().orEmpty()
)
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="30dp"
android:paddingVertical="5dp">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:textSize="16sp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/backupOptionsRecycler"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -324,6 +324,9 @@
<string name="skip_segment">Skip segment</string>
<string name="sb_skip_manual">Skip manually</string>
<string name="sb_skip_manual_summary">Don\'t skip segments automatically, always prompt before.</string>
<string name="local_subscriptions">Local subscriptions</string>
<string name="preferences">Preferences</string>
<string name="backup_customInstances">Custom Instances</string>
<!-- Notification channel strings -->
<string name="download_channel_name">Download Service</string>

View File

@ -32,7 +32,7 @@
</PreferenceCategory>
<PreferenceCategory app:title="@string/backup_restore">
<PreferenceCategory app:title="@string/preferences">
<Preference
android:icon="@drawable/ic_backup"
@ -46,4 +46,18 @@
</PreferenceCategory>
<PreferenceCategory app:title="@string/backup_restore">
<Preference
android:icon="@drawable/ic_backup"
app:key="backup"
app:title="@string/backup" />
<Preference
android:icon="@drawable/ic_restore"
app:key="restore"
app:title="@string/restore" />
</PreferenceCategory>
</PreferenceScreen>