Skip to content

Commit

Permalink
settings and database backups
Browse files Browse the repository at this point in the history
  • Loading branch information
crackededed committed Sep 19, 2024
1 parent a8acb84 commit 95c40b8
Show file tree
Hide file tree
Showing 28 changed files with 242 additions and 20 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ android {
minSdk = 21
targetSdk = 35
versionCode = 121
versionName = "2.34.4"
versionName = "2.35.0"
}

buildTypes {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<application
android:name=".XtraApp"
android:allowBackup="false"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import android.content.Context
import androidx.core.content.edit
import com.github.andreyasadchy.xtra.util.C
import com.github.andreyasadchy.xtra.util.TwitchApiHelper
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.tokenPrefs

sealed class Account(val id: String?,
val login: String?) {
Expand All @@ -13,7 +13,7 @@ sealed class Account(val id: String?,
private var account: Account? = null

fun get(context: Context): Account {
return account ?: with(context.prefs()) {
return account ?: with(context.tokenPrefs()) {
val helixToken = TwitchApiHelper.getHelixHeaders(context)[C.HEADER_TOKEN].takeUnless { it.isNullOrBlank() }
val gqlToken = TwitchApiHelper.getGQLHeaders(context, true)[C.HEADER_TOKEN].takeUnless { it.isNullOrBlank() }
if (!helixToken.isNullOrBlank() || !gqlToken.isNullOrBlank()) {
Expand All @@ -32,7 +32,7 @@ sealed class Account(val id: String?,

fun set(context: Context, account: Account?) {
this.account = account
context.prefs().edit {
context.tokenPrefs().edit {
if (account != null) {
putString(C.USER_ID, account.id)
putString(C.USERNAME, account.login)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.github.andreyasadchy.xtra.util.isLightTheme
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.shortToast
import com.github.andreyasadchy.xtra.util.toast
import com.github.andreyasadchy.xtra.util.tokenPrefs
import com.github.andreyasadchy.xtra.util.visible
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -92,7 +93,7 @@ class LoginActivity : AppCompatActivity() {
val gqlToken = gqlHeaders[C.HEADER_TOKEN]?.removePrefix("OAuth ")
TwitchApiHelper.checkedValidation = false
Account.set(this, null)
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, null)
putString(C.GQL_HEADERS, null)
putLong(C.INTEGRITY_EXPIRATION, 0)
Expand Down Expand Up @@ -317,7 +318,7 @@ class LoginActivity : AppCompatActivity() {
if (!helixToken.isNullOrBlank()) {
TwitchApiHelper.checkedValidation = true
Account.set(this@LoginActivity, LoggedIn(userId, userLogin))
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, helixToken)
}
}
Expand Down Expand Up @@ -348,7 +349,7 @@ class LoginActivity : AppCompatActivity() {
if (!helixToken.isNullOrBlank()) {
TwitchApiHelper.checkedValidation = true
Account.set(this@LoginActivity, LoggedIn(userId, userLogin))
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, helixToken)
}
}
Expand Down Expand Up @@ -392,7 +393,7 @@ class LoginActivity : AppCompatActivity() {
TwitchApiHelper.checkedValidation = true
Account.set(this@LoginActivity, LoggedIn(userId, userLogin))
if (!gqlToken.isNullOrBlank()) {
prefs().edit {
tokenPrefs().edit {
if (prefs().getBoolean(C.ENABLE_INTEGRITY, false)) {
putLong(C.INTEGRITY_EXPIRATION, 0)
putString(C.GQL_HEADERS, JSONObject(mapOf(
Expand All @@ -405,7 +406,7 @@ class LoginActivity : AppCompatActivity() {
}
}
if (!helixToken.isNullOrBlank()) {
prefs().edit {
tokenPrefs().edit {
putString(C.TOKEN, helixToken)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.github.andreyasadchy.xtra.util.C
import com.github.andreyasadchy.xtra.util.TwitchApiHelper
import com.github.andreyasadchy.xtra.util.getAlertDialogBuilder
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.tokenPrefs
import org.json.JSONObject

class IntegrityDialog : DialogFragment() {
Expand Down Expand Up @@ -60,7 +61,7 @@ class IntegrityDialog : DialogFragment() {

override fun shouldInterceptRequest(view: WebView, webViewRequest: WebResourceRequest): WebResourceResponse? {
if (!webViewRequest.requestHeaders.entries.firstOrNull { it.key.equals("Client-Integrity", true) }?.value.isNullOrBlank()) {
context.prefs().edit {
context.tokenPrefs().edit {
putLong(C.INTEGRITY_EXPIRATION, System.currentTimeMillis() + 57600000)
putString(C.GQL_HEADERS, JSONObject(
if (context.prefs().getBoolean(C.GET_ALL_GQL_HEADERS, false)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import com.github.andreyasadchy.xtra.util.isLightTheme
import com.github.andreyasadchy.xtra.util.isNetworkAvailable
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.shortToast
import com.github.andreyasadchy.xtra.util.tokenPrefs
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
Expand Down Expand Up @@ -188,6 +189,26 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener {
putBoolean(C.FIRST_LAUNCH8, false)
}
}
if (prefs.getBoolean(C.FIRST_LAUNCH9, true)) {
tokenPrefs().edit {
putString(C.USER_ID, prefs.getString(C.USER_ID, null))
putString(C.USERNAME, prefs.getString(C.USERNAME, null))
putString(C.TOKEN, prefs.getString(C.TOKEN, null))
putString(C.GQL_TOKEN2, prefs.getString(C.GQL_TOKEN2, null))
putString(C.GQL_HEADERS, prefs.getString(C.GQL_HEADERS, null))
putLong(C.INTEGRITY_EXPIRATION, prefs.getLong(C.INTEGRITY_EXPIRATION, 0))
}
prefs.edit {
remove(C.USER_ID)
remove(C.USERNAME)
remove(C.TOKEN)
remove(C.GQL_TOKEN)
remove(C.GQL_TOKEN2)
remove(C.GQL_HEADERS)
remove(C.INTEGRITY_EXPIRATION)
putBoolean(C.FIRST_LAUNCH9, false)
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.integrity.collectLatest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
Expand All @@ -29,6 +31,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.SeekBarPreference
Expand All @@ -45,6 +48,7 @@ import com.github.andreyasadchy.xtra.util.applyTheme
import com.github.andreyasadchy.xtra.util.convertDpToPixels
import com.github.andreyasadchy.xtra.util.prefs
import com.github.andreyasadchy.xtra.util.shortToast
import com.github.andreyasadchy.xtra.util.tokenPrefs
import com.google.android.material.appbar.AppBarLayout
import com.woxthebox.draglistview.DragItemAdapter
import com.woxthebox.draglistview.DragListView
Expand Down Expand Up @@ -93,15 +97,39 @@ class SettingsActivity : AppCompatActivity() {
class SettingsFragment : MaterialPreferenceFragment() {

private val viewModel: SettingsViewModel by viewModels()

private var changed = false
private var backupResultLauncher: ActivityResultLauncher<Intent>? = null
private var restoreResultLauncher: ActivityResultLauncher<Intent>? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
changed = savedInstanceState?.getBoolean(KEY_CHANGED) == true
if (changed) {
requireActivity().setResult(Activity.RESULT_OK)
}
backupResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let {
viewModel.backupSettings(it.toString())
}
}
}
restoreResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val list = mutableListOf<String>()
result.data?.clipData?.let { clipData ->
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
item.uri?.let {
list.add(it.toString())
}
}
} ?: result.data?.data?.let {
list.add(it.toString())
}
viewModel.restoreSettings(list)
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -275,6 +303,31 @@ class SettingsActivity : AppCompatActivity() {
true
}

findPreference<Preference>("backup_settings")?.setOnPreferenceClickListener {
backupResultLauncher?.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE))
true
}

findPreference<Preference>("restore_settings")?.setOnPreferenceClickListener {
restoreResultLauncher?.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
})
true
}

findPreference<EditTextPreference>("gql_headers")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.GQL_HEADERS, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.GQL_HEADERS, newValue.toString())
}
true
}
}

findPreference<Preference>("get_integrity_token")?.setOnPreferenceClickListener {
IntegrityDialog.show(childFragmentManager)
true
Expand Down Expand Up @@ -739,6 +792,50 @@ class SettingsActivity : AppCompatActivity() {

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.api_token_preferences, rootKey)

findPreference<EditTextPreference>("user_id")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.USER_ID, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.USER_ID, newValue.toString())
}
true
}
}

findPreference<EditTextPreference>("username")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.USERNAME, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.USERNAME, newValue.toString())
}
true
}
}

findPreference<EditTextPreference>("token")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.TOKEN, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.TOKEN, newValue.toString())
}
true
}
}

findPreference<EditTextPreference>("gql_token2")?.apply {
isPersistent = false
text = requireContext().tokenPrefs().getString(C.GQL_TOKEN2, null)
setOnPreferenceChangeListener { _, newValue ->
requireContext().tokenPrefs().edit {
putString(C.GQL_TOKEN2, newValue.toString())
}
true
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
package com.github.andreyasadchy.xtra.ui.settings

import android.content.Context
import android.content.Intent
import android.util.JsonReader
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.sqlite.db.SimpleSQLiteQuery
import com.github.andreyasadchy.xtra.db.AppDatabase
import com.github.andreyasadchy.xtra.model.offline.OfflineVideo
import com.github.andreyasadchy.xtra.repository.OfflineRepository
import com.github.andreyasadchy.xtra.repository.PlayerRepository
import com.github.andreyasadchy.xtra.ui.main.MainActivity
import com.github.andreyasadchy.xtra.util.m3u8.PlaylistUtils
import com.github.andreyasadchy.xtra.util.m3u8.Segment
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import javax.inject.Inject
import kotlin.math.max
import kotlin.system.exitProcess

@HiltViewModel
class SettingsViewModel @Inject constructor(
@ApplicationContext private val applicationContext: Context,
private val playerRepository: PlayerRepository,
private val offlineRepository: OfflineRepository) : ViewModel() {
private val offlineRepository: OfflineRepository,
private val appDatabase: AppDatabase) : ViewModel() {

fun deletePositions() {
viewModelScope.launch {
Expand Down Expand Up @@ -201,4 +212,50 @@ class SettingsViewModel @Inject constructor(
}
}
}

fun backupSettings(url: String) {
viewModelScope.launch(Dispatchers.IO) {
val directory = DocumentFile.fromTreeUri(applicationContext, url.substringBefore("/document/").toUri())
if (directory != null) {
val preferences = File("${applicationContext.applicationInfo.dataDir}/shared_prefs/${applicationContext.packageName}_preferences.xml")
(directory.findFile(preferences.name) ?: directory.createFile("", preferences.name))?.let {
applicationContext.contentResolver.openOutputStream(it.uri)!!.sink().buffer().use { sink ->
sink.writeAll(preferences.source().buffer())
}
}
appDatabase.query(SimpleSQLiteQuery("PRAGMA wal_checkpoint(FULL)")).use {
it.moveToPosition(-1)
}
val database = applicationContext.getDatabasePath("database")
(directory.findFile(database.name) ?: directory.createFile("", database.name))?.let {
applicationContext.contentResolver.openOutputStream(it.uri)!!.sink().buffer().use { sink ->
sink.writeAll(database.source().buffer())
}
}
}
}
}

fun restoreSettings(list: List<String>) {
viewModelScope.launch(Dispatchers.IO) {
list.take(2).forEach { url ->
if (url.endsWith(".xml")) {
File("${applicationContext.applicationInfo.dataDir}/shared_prefs/${applicationContext.packageName}_preferences.xml").sink().buffer().use { sink ->
sink.writeAll(applicationContext.contentResolver.openInputStream(url.toUri())!!.source().buffer())
}
} else {
val database = applicationContext.getDatabasePath("database")
File(database.parent, "database-shm").delete()
File(database.parent, "database-wal").delete()
database.sink().buffer().use { sink ->
sink.writeAll(applicationContext.contentResolver.openInputStream(url.toUri())!!.source().buffer())
}
applicationContext.startActivity(Intent(applicationContext, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
})
exitProcess(0)
}
}
}
}
}
Loading

0 comments on commit 95c40b8

Please sign in to comment.