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

View File

@ -4,6 +4,10 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager 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.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.ObjectInputStream import java.io.ObjectInputStream
@ -64,4 +68,54 @@ class BackupHelper(private val context: Context) {
e.printStackTrace() 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="skip_segment">Skip segment</string>
<string name="sb_skip_manual">Skip manually</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="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 --> <!-- Notification channel strings -->
<string name="download_channel_name">Download Service</string> <string name="download_channel_name">Download Service</string>

View File

@ -32,7 +32,7 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/backup_restore"> <PreferenceCategory app:title="@string/preferences">
<Preference <Preference
android:icon="@drawable/ic_backup" android:icon="@drawable/ic_backup"
@ -46,4 +46,18 @@
</PreferenceCategory> </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> </PreferenceScreen>