mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Automatic backups (#592)
Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com>
This commit is contained in:
parent
abbad1bc94
commit
91b195241e
8 changed files with 193 additions and 35 deletions
|
@ -1110,7 +1110,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
if (appVer != lastAppAutoBackup) {
|
||||
setKey("VERSION_NAME", BuildConfig.VERSION_NAME)
|
||||
normalSafeApiCall {
|
||||
backup()
|
||||
backup(this)
|
||||
}
|
||||
normalSafeApiCall {
|
||||
// Recompile oat on new version
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package com.lagradost.cloudstream3.services
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
const val BACKUP_CHANNEL_ID = "cloudstream3.backups"
|
||||
const val BACKUP_WORK_NAME = "work_backup"
|
||||
const val BACKUP_CHANNEL_NAME = "Backups"
|
||||
const val BACKUP_CHANNEL_DESCRIPTION = "Notifications for background backups"
|
||||
const val BACKUP_NOTIFICATION_ID = 938712898 // Random unique
|
||||
|
||||
class BackupWorkManager(val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
companion object {
|
||||
fun enqueuePeriodicWork(context: Context?, intervalHours: Long) {
|
||||
if (context == null) return
|
||||
|
||||
if (intervalHours == 0L) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(BACKUP_WORK_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiresStorageNotLow(true)
|
||||
.build()
|
||||
|
||||
val periodicSyncDataWork =
|
||||
PeriodicWorkRequest.Builder(
|
||||
BackupWorkManager::class.java,
|
||||
intervalHours,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(BACKUP_WORK_NAME)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||
BACKUP_WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE,
|
||||
periodicSyncDataWork
|
||||
)
|
||||
|
||||
// Uncomment below for testing
|
||||
|
||||
// val oneTimeBackupWork =
|
||||
// OneTimeWorkRequest.Builder(BackupWorkManager::class.java)
|
||||
// .addTag(BACKUP_WORK_NAME)
|
||||
// .setConstraints(constraints)
|
||||
// .build()
|
||||
//
|
||||
// WorkManager.getInstance(context).enqueue(oneTimeBackupWork)
|
||||
}
|
||||
}
|
||||
|
||||
private val backupNotificationBuilder =
|
||||
NotificationCompat.Builder(context, BACKUP_CHANNEL_ID)
|
||||
.setColorized(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSilent(true)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(context.getString(R.string.pref_category_backup))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
||||
.setSmallIcon(R.drawable.ic_cloudstream_monochrome_big)
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
context.createNotificationChannel(
|
||||
BACKUP_CHANNEL_ID,
|
||||
BACKUP_CHANNEL_NAME,
|
||||
BACKUP_CHANNEL_DESCRIPTION
|
||||
)
|
||||
|
||||
setForeground(
|
||||
ForegroundInfo(
|
||||
BACKUP_NOTIFICATION_ID,
|
||||
backupNotificationBuilder.build()
|
||||
)
|
||||
)
|
||||
|
||||
BackupUtils.backup(context)
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
}
|
|
@ -19,14 +19,16 @@ import com.lagradost.cloudstream3.app
|
|||
import com.lagradost.cloudstream3.databinding.LogcatBinding
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.network.initClient
|
||||
import com.lagradost.cloudstream3.services.BackupWorkManager
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
|
@ -48,7 +50,30 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
getPref(R.string.backup_key)?.setOnPreferenceClickListener {
|
||||
activity?.backup()
|
||||
BackupUtils.backup(activity)
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
getPref(R.string.automatic_backup_key)?.setOnPreferenceClickListener {
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
val prefNames = resources.getStringArray(R.array.periodic_work_names)
|
||||
val prefValues = resources.getIntArray(R.array.periodic_work_values)
|
||||
val current = settingsManager.getInt(getString(R.string.automatic_backup_key), 0)
|
||||
|
||||
activity?.showDialog(
|
||||
prefNames.toList(),
|
||||
prefValues.indexOf(current),
|
||||
getString(R.string.backup_frequency),
|
||||
true,
|
||||
{}) { index ->
|
||||
settingsManager.edit()
|
||||
.putInt(getString(R.string.automatic_backup_key), prefValues[index]).apply()
|
||||
BackupWorkManager.enqueuePeriodicWork(
|
||||
context ?: AcraApplication.context,
|
||||
prefValues[index].toLong()
|
||||
)
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
|
@ -65,7 +90,7 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
val builder =
|
||||
AlertDialog.Builder(pref.context, R.style.AlertDialogCustom)
|
||||
|
||||
val binding = LogcatBinding.inflate(layoutInflater,null,false )
|
||||
val binding = LogcatBinding.inflate(layoutInflater, null, false)
|
||||
builder.setView(binding.root)
|
||||
|
||||
val dialog = builder.create()
|
||||
|
@ -176,7 +201,8 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it.context)
|
||||
|
||||
val prefNames = resources.getStringArray(R.array.auto_download_plugin)
|
||||
val prefValues = enumValues<AutoDownloadMode>().sortedBy { x -> x.value }.map { x -> x.value }
|
||||
val prefValues =
|
||||
enumValues<AutoDownloadMode>().sortedBy { x -> x.value }.map { x -> x.value }
|
||||
|
||||
val current = settingsManager.getInt(getString(R.string.auto_download_plugins_key), 0)
|
||||
|
||||
|
@ -186,7 +212,8 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
getString(R.string.automatic_plugin_download_mode_title),
|
||||
true,
|
||||
{}) {
|
||||
settingsManager.edit().putInt(getString(R.string.auto_download_plugins_key), prefValues[it]).apply()
|
||||
settingsManager.edit()
|
||||
.putInt(getString(R.string.auto_download_plugins_key), prefValues[it]).apply()
|
||||
(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) }
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.annotation.WorkerThread
|
|||
import androidx.fragment.app.FragmentActivity
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -90,9 +91,11 @@ object BackupUtils {
|
|||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun Context.getBackup(): BackupFile {
|
||||
val allData = getSharedPrefs().all.filter { it.key.isTransferable() }
|
||||
val allSettings = getDefaultSharedPrefs().all.filter { it.key.isTransferable() }
|
||||
private fun getBackup(context: Context?): BackupFile? {
|
||||
if (context == null) return null
|
||||
|
||||
val allData = context.getSharedPrefs().all.filter { it.key.isTransferable() }
|
||||
val allSettings = context.getDefaultSharedPrefs().all.filter { it.key.isTransferable() }
|
||||
|
||||
val allDataSorted = BackupVars(
|
||||
allData.filter { it.value is Boolean } as? Map<String, Boolean>,
|
||||
|
@ -119,46 +122,50 @@ object BackupUtils {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
fun Context.restore(
|
||||
fun restore(
|
||||
context: Context?,
|
||||
backupFile: BackupFile,
|
||||
restoreSettings: Boolean,
|
||||
restoreDataStore: Boolean
|
||||
) {
|
||||
if (context == null) return
|
||||
if (restoreSettings) {
|
||||
restoreMap(backupFile.settings._Bool, true)
|
||||
restoreMap(backupFile.settings._Int, true)
|
||||
restoreMap(backupFile.settings._String, true)
|
||||
restoreMap(backupFile.settings._Float, true)
|
||||
restoreMap(backupFile.settings._Long, true)
|
||||
restoreMap(backupFile.settings._StringSet, true)
|
||||
context.restoreMap(backupFile.settings._Bool, true)
|
||||
context.restoreMap(backupFile.settings._Int, true)
|
||||
context.restoreMap(backupFile.settings._String, true)
|
||||
context.restoreMap(backupFile.settings._Float, true)
|
||||
context.restoreMap(backupFile.settings._Long, true)
|
||||
context.restoreMap(backupFile.settings._StringSet, true)
|
||||
}
|
||||
|
||||
if (restoreDataStore) {
|
||||
restoreMap(backupFile.datastore._Bool)
|
||||
restoreMap(backupFile.datastore._Int)
|
||||
restoreMap(backupFile.datastore._String)
|
||||
restoreMap(backupFile.datastore._Float)
|
||||
restoreMap(backupFile.datastore._Long)
|
||||
restoreMap(backupFile.datastore._StringSet)
|
||||
context.restoreMap(backupFile.datastore._Bool)
|
||||
context.restoreMap(backupFile.datastore._Int)
|
||||
context.restoreMap(backupFile.datastore._String)
|
||||
context.restoreMap(backupFile.datastore._Float)
|
||||
context.restoreMap(backupFile.datastore._Long)
|
||||
context.restoreMap(backupFile.datastore._StringSet)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun FragmentActivity.backup() = ioSafe {
|
||||
fun backup(context: Context?) = ioSafe {
|
||||
if (context == null) return@ioSafe
|
||||
|
||||
var fileStream: OutputStream? = null
|
||||
var printStream: PrintWriter? = null
|
||||
try {
|
||||
if (!checkWrite()) {
|
||||
if (!context.checkWrite()) {
|
||||
showToast(R.string.backup_failed, Toast.LENGTH_LONG)
|
||||
requestRW()
|
||||
context.getActivity()?.requestRW()
|
||||
return@ioSafe
|
||||
}
|
||||
|
||||
val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis()))
|
||||
val ext = "txt"
|
||||
val displayName = "CS3_Backup_${date}"
|
||||
val backupFile = getBackup()
|
||||
val stream = setupStream(this@backup, displayName, null, ext, false)
|
||||
val backupFile = getBackup(context)
|
||||
val stream = setupStream(context, displayName, null, ext, false)
|
||||
|
||||
fileStream = stream.openNew()
|
||||
printStream = PrintWriter(fileStream)
|
||||
|
@ -198,7 +205,8 @@ object BackupUtils {
|
|||
val restoredValue =
|
||||
mapper.readValue<BackupFile>(input)
|
||||
|
||||
activity.restore(
|
||||
restore(
|
||||
activity,
|
||||
restoredValue,
|
||||
restoreSettings = true,
|
||||
restoreDataStore = true
|
||||
|
|
|
@ -71,7 +71,7 @@ object UIHelper {
|
|||
val Int.toDp: Int get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
||||
val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density)
|
||||
|
||||
fun Activity.checkWrite(): Boolean {
|
||||
fun Context.checkWrite(): Boolean {
|
||||
return (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<item>0</item>
|
||||
<item>1</item>
|
||||
</array>
|
||||
|
||||
|
||||
<!-- MainAPI enum: AutoDownloadMode -->
|
||||
<array name="auto_download_plugin">
|
||||
<item>@string/disable</item>
|
||||
|
@ -126,6 +126,26 @@
|
|||
<item>30min</item>
|
||||
</array>
|
||||
|
||||
<string-array name="periodic_work_names">
|
||||
<item>@string/none</item>
|
||||
<item>3h</item>
|
||||
<item>6h</item>
|
||||
<item>12h</item>
|
||||
<item>24h</item>
|
||||
<item>3d</item>
|
||||
<item>7d</item>
|
||||
</string-array>
|
||||
<!-- Values in hours -->
|
||||
<integer-array name="periodic_work_values">
|
||||
<item>0</item>
|
||||
<item>3</item>
|
||||
<item>6</item>
|
||||
<item>12</item>
|
||||
<item>24</item>
|
||||
<item>72</item>
|
||||
<item>168</item>
|
||||
</integer-array>
|
||||
|
||||
<array name="video_buffer_length_values">
|
||||
<item>0</item>
|
||||
<item>60</item>
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
<string name="primary_color_key" translatable="false">primary_color_key</string>
|
||||
<string name="restore_key" translatable="false">restore_key</string>
|
||||
<string name="backup_key" translatable="false">backup_key</string>
|
||||
<string name="automatic_backup_key" translatable="false">automatic_backup_key</string>
|
||||
<string name="prefer_media_type_key" translatable="false">prefer_media_type_key_2</string>
|
||||
<string name="app_theme_key" translatable="false">app_theme_key</string>
|
||||
<string name="episode_sync_enabled_key" translatable="false">episode_sync_enabled_key</string>
|
||||
|
@ -229,6 +230,7 @@
|
|||
<string name="episode_sync_settings_des">Automatically sync your current episode progress</string>
|
||||
<string name="restore_settings">Restore data from backup</string>
|
||||
<string name="backup_settings">Back up data</string>
|
||||
<string name="backup_frequency">Backup frequency</string>
|
||||
<string name="restore_success">Loaded backup file</string>
|
||||
<string name="restore_failed_format" formatted="true">Failed to restore data from file %s</string>
|
||||
<string name="backup_success">Data stored</string>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
android:summaryOn="@string/bug_report_settings_on"
|
||||
android:title="@string/pref_disable_acra" />
|
||||
|
||||
<PreferenceCategory
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_app_updates">
|
||||
<Preference
|
||||
android:title="@string/check_for_update"
|
||||
|
@ -37,27 +37,32 @@
|
|||
app:key="@string/auto_update_key" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_backup">
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_save_as_24"
|
||||
android:key="@string/backup_key"
|
||||
android:title="@string/backup_settings" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_save_as_24"
|
||||
android:key="@string/automatic_backup_key"
|
||||
android:title="@string/backup_frequency" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_restore_page_24"
|
||||
android:key="@string/restore_key"
|
||||
android:title="@string/restore_settings" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_extensions">
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_baseline_extension_24"
|
||||
android:key="@string/auto_update_plugins_key"
|
||||
android:title="@string/automatic_plugin_updates" />
|
||||
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_baseline_extension_24"
|
||||
android:key="@string/auto_download_plugins_key"
|
||||
|
@ -65,7 +70,7 @@
|
|||
android:summary="@string/automatic_plugin_download_summary" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_actions">
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_description_24"
|
||||
|
|
Loading…
Reference in a new issue