mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Safer API and fixed syncing resume watching
This commit is contained in:
parent
4b7fc62237
commit
9e85359ad3
8 changed files with 257 additions and 201 deletions
|
@ -44,7 +44,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
|
||||
// used for active backup
|
||||
val BackupApis
|
||||
get() = listOf<BackupAPI<*>>(
|
||||
get() = listOf<SafeBackupAPI>(
|
||||
googleDriveApi, pcloudApi
|
||||
)
|
||||
|
||||
|
|
|
@ -7,9 +7,8 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
|||
import com.lagradost.cloudstream3.AcraApplication
|
||||
import com.lagradost.cloudstream3.mvvm.debugException
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.LOG_KEY
|
||||
import com.lagradost.cloudstream3.syncproviders.IBackupAPI.Companion.compareJson
|
||||
import com.lagradost.cloudstream3.syncproviders.IBackupAPI.Companion.mergeBackup
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils.getBackup
|
||||
|
@ -22,7 +21,7 @@ import org.skyscreamer.jsonassert.JSONCompare
|
|||
import org.skyscreamer.jsonassert.JSONCompareMode
|
||||
import org.skyscreamer.jsonassert.JSONCompareResult
|
||||
import kotlin.system.measureTimeMillis
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
interface RemoteFile {
|
||||
class Error(val message: String? = null, val throwable: Throwable? = null) : RemoteFile
|
||||
|
@ -30,8 +29,83 @@ interface RemoteFile {
|
|||
class Success(val remoteData: String) : RemoteFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe wrapper for the backup api to be used outside the class without fear
|
||||
* of causing crashes.
|
||||
*/
|
||||
interface SafeBackupAPI {
|
||||
/**
|
||||
* @return true if the service is ready for uploads and downloads.
|
||||
* This includes a login check.
|
||||
*/
|
||||
suspend fun getIsReady(): Boolean
|
||||
suspend fun scheduleDownload(runNow: Boolean = false)
|
||||
fun getIsLoggedIn(): Boolean
|
||||
fun scheduleUpload()
|
||||
fun scheduleUpload(changedKey: String, isSettings: Boolean)
|
||||
|
||||
/**
|
||||
* Warns the service that an upload is incoming. Used to prevent simultaneous download and upload.
|
||||
*/
|
||||
fun setIsUploadingSoon()
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy interface to implement remote sync by only implementing the download and upload part of the service.
|
||||
* @see BackupAPI for how the methods get used
|
||||
*/
|
||||
interface IBackupAPI<LOGIN_DATA> {
|
||||
/**
|
||||
* Gets the user login info for uploading and downloading the backup.
|
||||
* If null no backup or download will be run.
|
||||
*/
|
||||
suspend fun getLoginData(): LOGIN_DATA?
|
||||
|
||||
/**
|
||||
* Additional check if the backup operation should be run.
|
||||
* Return false here to deny any backup work.
|
||||
*/
|
||||
suspend fun isReady(): Boolean = true
|
||||
|
||||
/**
|
||||
* Get the backup file as a string from the remote storage.
|
||||
* @see RemoteFile.Success
|
||||
* @see RemoteFile.Error
|
||||
* @see RemoteFile.NotFound
|
||||
*/
|
||||
suspend fun getRemoteFile(context: Context, loginData: LOGIN_DATA): RemoteFile
|
||||
|
||||
suspend fun uploadFile(
|
||||
context: Context,
|
||||
backupJson: String,
|
||||
loginData: LOGIN_DATA
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for the IBackupAPI which implements a scheduler, logging and checks.
|
||||
* This makes the individual backup service implementations easier and more lightweight.
|
||||
*/
|
||||
abstract class BackupAPI<LOGIN_DATA>(defIndex: Int) : IBackupAPI<LOGIN_DATA>,
|
||||
AccountManager(defIndex) {
|
||||
AccountManager(defIndex), SafeBackupAPI {
|
||||
data class JSONComparison(
|
||||
val failed: Boolean,
|
||||
val result: JSONCompareResult?
|
||||
)
|
||||
|
||||
data class PreferencesSchedulerData<T>(
|
||||
val syncPrefs: SharedPreferences,
|
||||
val storeKey: String,
|
||||
val oldValue: T,
|
||||
val newValue: T,
|
||||
val source: BackupUtils.RestoreSource
|
||||
)
|
||||
|
||||
data class SharedPreferencesWithListener(
|
||||
val self: SharedPreferences,
|
||||
val scheduler: Scheduler<PreferencesSchedulerData<*>>
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val LOG_KEY = "BACKUP"
|
||||
|
||||
|
@ -39,8 +113,118 @@ abstract class BackupAPI<LOGIN_DATA>(defIndex: Int) : IBackupAPI<LOGIN_DATA>,
|
|||
// cloud project per user so there is no way to hit quota. Later we should implement
|
||||
// some kind of adaptive throttling which will increase decrease throttle time based
|
||||
// on factors like: live devices, quota limits, etc
|
||||
val UPLOAD_THROTTLE = 30.seconds
|
||||
val DOWNLOAD_THROTTLE = 120.seconds
|
||||
val UPLOAD_THROTTLE = 5.minutes
|
||||
val DOWNLOAD_THROTTLE = 5.minutes
|
||||
|
||||
const val SYNC_HISTORY_PREFIX = "_hs/"
|
||||
|
||||
fun SharedPreferences.logHistoryChanged(
|
||||
path: String,
|
||||
source: BackupUtils.RestoreSource
|
||||
) {
|
||||
edit().putLong("${source.syncPrefix}$path", System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
fun compareJson(old: String, new: String): JSONComparison {
|
||||
var result: JSONCompareResult?
|
||||
|
||||
val executionTime = measureTimeMillis {
|
||||
result = try {
|
||||
JSONCompare.compareJSON(old, new, JSONCompareMode.NON_EXTENSIBLE)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val failed = result?.failed() ?: true
|
||||
Log.d(
|
||||
LOG_KEY,
|
||||
"JSON comparison took $executionTime ms, compareFailed=$failed, result=$result"
|
||||
)
|
||||
|
||||
return JSONComparison(failed, result)
|
||||
}
|
||||
|
||||
private fun getSyncKeys(data: BackupUtils.BackupFile) =
|
||||
data.syncMeta._Long.orEmpty().filter { it.key.startsWith(SYNC_HISTORY_PREFIX) }
|
||||
|
||||
/**
|
||||
* Merges the backup data with the app data.
|
||||
* @param overwrite if true it overwrites all data same as restoring from a backup.
|
||||
* if false it only updates outdated keys. Should be true on first initialization.
|
||||
*/
|
||||
private fun mergeBackup(context: Context, incomingData: String, overwrite: Boolean) {
|
||||
val newData = DataStore.mapper.readValue<BackupUtils.BackupFile>(incomingData)
|
||||
if (overwrite) {
|
||||
Log.d(LOG_KEY, "overwriting data")
|
||||
context.restore(newData)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val keysToUpdate = getKeysToUpdate(context.getBackup(), newData)
|
||||
if (keysToUpdate.isEmpty()) {
|
||||
Log.d(LOG_KEY, "remote data is up to date, sync not needed")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(LOG_KEY, incomingData)
|
||||
context.restore(newData, keysToUpdate)
|
||||
}
|
||||
|
||||
private fun getKeysToUpdate(
|
||||
currentData: BackupUtils.BackupFile,
|
||||
newData: BackupUtils.BackupFile
|
||||
): Set<String> {
|
||||
val currentSync = getSyncKeys(currentData)
|
||||
val newSync = getSyncKeys(newData)
|
||||
|
||||
val changedKeys = newSync.filter {
|
||||
val localTimestamp = currentSync[it.key] ?: 0L
|
||||
it.value > localTimestamp
|
||||
}.keys
|
||||
|
||||
val onlyLocalKeys = currentSync.keys.filter { !newSync.containsKey(it) }
|
||||
val missingKeys = getAllMissingKeys(currentData, newData)
|
||||
|
||||
return (missingKeys + onlyLocalKeys + changedKeys).toSet()
|
||||
}
|
||||
|
||||
private fun getAllMissingKeys(
|
||||
old: BackupUtils.BackupFile,
|
||||
new: BackupUtils.BackupFile
|
||||
): List<String> = BackupUtils.RestoreSource
|
||||
.values()
|
||||
.filter { it != BackupUtils.RestoreSource.SYNC }
|
||||
.fold(mutableListOf()) { acc, source ->
|
||||
acc.addAll(getMissingKeysPrefixed(source, old, new))
|
||||
acc
|
||||
}
|
||||
|
||||
private fun getMissingKeysPrefixed(
|
||||
restoreSource: BackupUtils.RestoreSource,
|
||||
old: BackupUtils.BackupFile,
|
||||
new: BackupUtils.BackupFile
|
||||
): List<String> {
|
||||
val oldSource = old.getData(restoreSource)
|
||||
val newSource = new.getData(restoreSource)
|
||||
val prefixToMatch = restoreSource.syncPrefix
|
||||
|
||||
return listOf(
|
||||
*getMissing(oldSource._Bool, newSource._Bool),
|
||||
*getMissing(oldSource._Long, newSource._Long),
|
||||
*getMissing(oldSource._Float, newSource._Float),
|
||||
*getMissing(oldSource._Int, newSource._Int),
|
||||
*getMissing(oldSource._String, newSource._String),
|
||||
*getMissing(oldSource._StringSet, newSource._StringSet),
|
||||
).map {
|
||||
prefixToMatch + it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getMissing(old: Map<String, *>?, new: Map<String, *>?): Array<String> =
|
||||
(new.orEmpty().keys - old.orEmpty().keys).toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +261,7 @@ abstract class BackupAPI<LOGIN_DATA>(defIndex: Int) : IBackupAPI<LOGIN_DATA>,
|
|||
}
|
||||
}
|
||||
|
||||
var willUploadSoon: Boolean? = null
|
||||
private var willUploadSoon: Boolean? = null
|
||||
private var uploadJob: Job? = null
|
||||
|
||||
private fun shouldUploadBackup(): Boolean {
|
||||
|
@ -87,19 +271,23 @@ abstract class BackupAPI<LOGIN_DATA>(defIndex: Int) : IBackupAPI<LOGIN_DATA>,
|
|||
return compareJson(lastBackupJson ?: "", newBackup).failed
|
||||
}
|
||||
|
||||
fun scheduleUpload() {
|
||||
if (!shouldUploadBackup()) {
|
||||
willUploadSoon = false
|
||||
Log.d(LOG_KEY, "${this.name}: upload not required, data is same")
|
||||
return
|
||||
}
|
||||
override fun scheduleUpload() {
|
||||
normalSafeApiCall {
|
||||
if (!shouldUploadBackup()) {
|
||||
willUploadSoon = false
|
||||
Log.d(LOG_KEY, "${this.name}: upload not required, data is same")
|
||||
return@normalSafeApiCall
|
||||
}
|
||||
|
||||
upload()
|
||||
upload()
|
||||
}
|
||||
}
|
||||
|
||||
// changedKey and isSettings is currently unused, might be useful for more efficient update checker.
|
||||
fun scheduleUpload(changedKey: String, isSettings: Boolean) {
|
||||
scheduleUpload()
|
||||
override fun scheduleUpload(changedKey: String, isSettings: Boolean) {
|
||||
normalSafeApiCall {
|
||||
scheduleUpload()
|
||||
}
|
||||
}
|
||||
|
||||
private fun upload() {
|
||||
|
@ -170,7 +358,10 @@ abstract class BackupAPI<LOGIN_DATA>(defIndex: Int) : IBackupAPI<LOGIN_DATA>,
|
|||
}
|
||||
|
||||
is RemoteFile.Error -> {
|
||||
Log.d(LOG_KEY, "${this.name}: getRemoteFile failed with message: ${remoteFile.message}.")
|
||||
Log.d(
|
||||
LOG_KEY,
|
||||
"${this.name}: getRemoteFile failed with message: ${remoteFile.message}."
|
||||
)
|
||||
remoteFile.throwable?.let { error -> logError(error) }
|
||||
null
|
||||
}
|
||||
|
@ -195,162 +386,26 @@ abstract class BackupAPI<LOGIN_DATA>(defIndex: Int) : IBackupAPI<LOGIN_DATA>,
|
|||
lastBackupJson = remoteData
|
||||
mergeBackup(context, remoteData, overwrite)
|
||||
}
|
||||
}
|
||||
|
||||
interface IBackupAPI<LOGIN_DATA> {
|
||||
data class JSONComparison(
|
||||
val failed: Boolean,
|
||||
val result: JSONCompareResult?
|
||||
)
|
||||
|
||||
data class PreferencesSchedulerData<T>(
|
||||
val syncPrefs: SharedPreferences,
|
||||
val storeKey: String,
|
||||
val oldValue: T,
|
||||
val newValue: T,
|
||||
val source: BackupUtils.RestoreSource
|
||||
)
|
||||
|
||||
data class SharedPreferencesWithListener(
|
||||
val self: SharedPreferences,
|
||||
val scheduler: Scheduler<PreferencesSchedulerData<*>>
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the user login info for uploading and downloading the backup.
|
||||
* If null no backup or download will be run.
|
||||
*/
|
||||
suspend fun getLoginData(): LOGIN_DATA?
|
||||
|
||||
/**
|
||||
* Additional check if the backup operation should be run.
|
||||
* Return false here to deny any backup work.
|
||||
*/
|
||||
suspend fun isReady(): Boolean = true
|
||||
|
||||
/**
|
||||
* Get the backup file as a string from the remote storage.
|
||||
* @see RemoteFile.Success
|
||||
* @see RemoteFile.Error
|
||||
* @see RemoteFile.NotFound
|
||||
*/
|
||||
suspend fun getRemoteFile(context: Context, loginData: LOGIN_DATA): RemoteFile
|
||||
|
||||
suspend fun uploadFile(
|
||||
context: Context,
|
||||
backupJson: String,
|
||||
loginData: LOGIN_DATA
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val SYNC_HISTORY_PREFIX = "_hs/"
|
||||
|
||||
fun SharedPreferences.logHistoryChanged(path: String, source: BackupUtils.RestoreSource) {
|
||||
edit().putLong("${source.syncPrefix}$path", System.currentTimeMillis())
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun compareJson(old: String, new: String): JSONComparison {
|
||||
var result: JSONCompareResult?
|
||||
|
||||
val executionTime = measureTimeMillis {
|
||||
result = try {
|
||||
JSONCompare.compareJSON(old, new, JSONCompareMode.NON_EXTENSIBLE)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val failed = result?.failed() ?: true
|
||||
Log.d(
|
||||
LOG_KEY,
|
||||
"JSON comparison took $executionTime ms, compareFailed=$failed, result=$result"
|
||||
)
|
||||
|
||||
return JSONComparison(failed, result)
|
||||
}
|
||||
|
||||
private fun getSyncKeys(data: BackupUtils.BackupFile) =
|
||||
data.syncMeta._Long.orEmpty().filter { it.key.startsWith(SYNC_HISTORY_PREFIX) }
|
||||
|
||||
/**
|
||||
* Merges the backup data with the app data.
|
||||
* @param overwrite if true it overwrites all data same as restoring from a backup.
|
||||
* if false it only updates outdated keys. Should be true on first initialization.
|
||||
*/
|
||||
fun mergeBackup(context: Context, incomingData: String, overwrite: Boolean) {
|
||||
val newData = DataStore.mapper.readValue<BackupUtils.BackupFile>(incomingData)
|
||||
if (overwrite) {
|
||||
Log.d(LOG_KEY, "overwriting data")
|
||||
context.restore(newData)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val keysToUpdate = getKeysToUpdate(context.getBackup(), newData)
|
||||
if (keysToUpdate.isEmpty()) {
|
||||
Log.d(LOG_KEY, "remote data is up to date, sync not needed")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Log.d(LOG_KEY, incomingData)
|
||||
context.restore(newData, keysToUpdate)
|
||||
}
|
||||
|
||||
private fun getKeysToUpdate(
|
||||
currentData: BackupUtils.BackupFile,
|
||||
newData: BackupUtils.BackupFile
|
||||
): Set<String> {
|
||||
val currentSync = getSyncKeys(currentData)
|
||||
val newSync = getSyncKeys(newData)
|
||||
|
||||
val changedKeys = newSync.filter {
|
||||
val localTimestamp = currentSync[it.key] ?: 0L
|
||||
it.value > localTimestamp
|
||||
}.keys
|
||||
|
||||
val onlyLocalKeys = currentSync.keys.filter { !newSync.containsKey(it) }
|
||||
val missingKeys = getAllMissingKeys(currentData, newData)
|
||||
|
||||
return (missingKeys + onlyLocalKeys + changedKeys).toSet()
|
||||
}
|
||||
|
||||
private fun getAllMissingKeys(
|
||||
old: BackupUtils.BackupFile,
|
||||
new: BackupUtils.BackupFile
|
||||
): List<String> = BackupUtils.RestoreSource
|
||||
.values()
|
||||
.filter { it != BackupUtils.RestoreSource.SYNC }
|
||||
.fold(mutableListOf()) { acc, source ->
|
||||
acc.addAll(getMissingKeysPrefixed(source, old, new))
|
||||
acc
|
||||
}
|
||||
|
||||
private fun getMissingKeysPrefixed(
|
||||
restoreSource: BackupUtils.RestoreSource,
|
||||
old: BackupUtils.BackupFile,
|
||||
new: BackupUtils.BackupFile
|
||||
): List<String> {
|
||||
val oldSource = old.getData(restoreSource)
|
||||
val newSource = new.getData(restoreSource)
|
||||
val prefixToMatch = restoreSource.syncPrefix
|
||||
|
||||
return listOf(
|
||||
*getMissing(oldSource._Bool, newSource._Bool),
|
||||
*getMissing(oldSource._Long, newSource._Long),
|
||||
*getMissing(oldSource._Float, newSource._Float),
|
||||
*getMissing(oldSource._Int, newSource._Int),
|
||||
*getMissing(oldSource._String, newSource._String),
|
||||
*getMissing(oldSource._StringSet, newSource._StringSet),
|
||||
).map {
|
||||
prefixToMatch + it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getMissing(old: Map<String, *>?, new: Map<String, *>?): Array<String> =
|
||||
(new.orEmpty().keys - old.orEmpty().keys)
|
||||
.toTypedArray()
|
||||
// ------ SafeBackupAPI wrappers ------
|
||||
override suspend fun getIsReady(): Boolean {
|
||||
return suspendSafeApiCall {
|
||||
isReady()
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIsLoggedIn(): Boolean {
|
||||
return normalSafeApiCall { loginInfo() } != null
|
||||
}
|
||||
|
||||
override fun setIsUploadingSoon() {
|
||||
willUploadSoon = true
|
||||
}
|
||||
|
||||
override suspend fun scheduleDownload(runNow: Boolean) {
|
||||
suspendSafeApiCall {
|
||||
scheduleDownload(runNow, false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -85,9 +85,9 @@ class GoogleDriveApi(index: Int) :
|
|||
accountId, key.value, value
|
||||
)
|
||||
|
||||
private fun clearValue(key: K) = removeKey(accountId, key.value)
|
||||
private fun clearValue(key: K) = removeKey(accountId, key.value)
|
||||
|
||||
private inline fun <reified T : Any> getValue(key: K) = getKey<T>(
|
||||
private inline fun <reified T : Any> getValue(key: K) = getKey<T>(
|
||||
accountId, key.value
|
||||
)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.databinding.AccountManagmentBinding
|
||||
import com.lagradost.cloudstream3.databinding.AccountSwitchBinding
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.googleDriveApi
|
||||
|
@ -150,7 +151,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
|||
title =
|
||||
getString(R.string.login_format).format(api.name, getString(R.string.account))
|
||||
setOnPreferenceClickListener {
|
||||
val info = api.loginInfo()
|
||||
val info = normalSafeApiCall { api.loginInfo() }
|
||||
if (info != null) {
|
||||
showLoginInfo(activity, api, info)
|
||||
} else {
|
||||
|
|
|
@ -24,9 +24,9 @@ import com.lagradost.cloudstream3.databinding.MainSettingsBinding
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.BackupApis
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
@ -62,7 +62,8 @@ class SettingsFragment : Fragment() {
|
|||
|
||||
fun Fragment?.setUpToolbar(title: String) {
|
||||
if (this == null) return
|
||||
val settingsToolbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) ?: return
|
||||
val settingsToolbar =
|
||||
view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) ?: return
|
||||
|
||||
settingsToolbar.apply {
|
||||
setTitle(title)
|
||||
|
@ -76,7 +77,8 @@ class SettingsFragment : Fragment() {
|
|||
|
||||
fun Fragment?.setUpToolbar(@StringRes title: Int) {
|
||||
if (this == null) return
|
||||
val settingsToolbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) ?: return
|
||||
val settingsToolbar =
|
||||
view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) ?: return
|
||||
|
||||
settingsToolbar.apply {
|
||||
setTitle(title)
|
||||
|
@ -213,7 +215,7 @@ class SettingsFragment : Fragment() {
|
|||
|
||||
// Only show the button if the api does not require login, requires login, but the user is logged in
|
||||
forceSyncDataBtt.isVisible = BackupApis.any { api ->
|
||||
api !is AuthAPI || api.loginInfo() != null
|
||||
api.getIsLoggedIn()
|
||||
}
|
||||
|
||||
forceSyncDataBtt.setOnClickListener {
|
||||
|
@ -223,7 +225,8 @@ class SettingsFragment : Fragment() {
|
|||
showToast(activity, txt(R.string.syncing_data), Toast.LENGTH_SHORT)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
forceSyncDataBtt.tooltipText = txt(R.string.sync_data).asString(forceSyncDataBtt.context)
|
||||
forceSyncDataBtt.tooltipText =
|
||||
txt(R.string.sync_data).asString(forceSyncDataBtt.context)
|
||||
}
|
||||
|
||||
// Default focus on TV
|
||||
|
|
|
@ -54,7 +54,7 @@ object BackupUtils {
|
|||
DATA, SETTINGS, SYNC;
|
||||
|
||||
val prefix = "$name/"
|
||||
val syncPrefix = "${IBackupAPI.SYNC_HISTORY_PREFIX}$prefix"
|
||||
val syncPrefix = "${BackupAPI.SYNC_HISTORY_PREFIX}$prefix"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +80,6 @@ object BackupUtils {
|
|||
|
||||
OPEN_SUBTITLES_USER_KEY,
|
||||
"nginx_user", // Nginx user key
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
DOWNLOAD_EPISODE_CACHE
|
||||
)
|
||||
|
||||
|
@ -329,7 +328,7 @@ object BackupUtils {
|
|||
var prefixToRemove = prefixToMatch
|
||||
|
||||
if (restoreSource == RestoreSource.SYNC) {
|
||||
prefixToMatch = IBackupAPI.SYNC_HISTORY_PREFIX
|
||||
prefixToMatch = BackupAPI.SYNC_HISTORY_PREFIX
|
||||
prefixToRemove = ""
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKeyClass
|
|||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKeyClass
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
import com.lagradost.cloudstream3.syncproviders.IBackupAPI
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
|
||||
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
|
||||
|
@ -149,7 +149,7 @@ object DataStore {
|
|||
|
||||
ioSafe {
|
||||
backupScheduler.work(
|
||||
IBackupAPI.PreferencesSchedulerData(
|
||||
BackupAPI.PreferencesSchedulerData(
|
||||
getSyncPrefs(),
|
||||
path,
|
||||
oldValueExists,
|
||||
|
@ -184,7 +184,7 @@ object DataStore {
|
|||
|
||||
ioSafe {
|
||||
backupScheduler.work(
|
||||
IBackupAPI.PreferencesSchedulerData(
|
||||
BackupAPI.PreferencesSchedulerData(
|
||||
getSyncPrefs(),
|
||||
path,
|
||||
oldValue,
|
||||
|
|
|
@ -6,14 +6,12 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.IBackupAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.IBackupAPI.Companion.logHistoryChanged
|
||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.logHistoryChanged
|
||||
import com.lagradost.cloudstream3.ui.home.HOME_BOOKMARK_VALUE_LIST
|
||||
import com.lagradost.cloudstream3.ui.player.PLAYBACK_SPEED_KEY
|
||||
import com.lagradost.cloudstream3.ui.player.RESIZE_MODE_KEY
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils.nonTransferableKeys
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class Scheduler<INPUT>(
|
||||
private val throttleTimeMs: Long,
|
||||
|
@ -40,7 +38,7 @@ class Scheduler<INPUT>(
|
|||
Regex("""^\d+/$RESULT_DUB/"""),
|
||||
)
|
||||
|
||||
fun createBackupScheduler() = Scheduler<IBackupAPI.PreferencesSchedulerData<*>>(
|
||||
fun createBackupScheduler() = Scheduler<BackupAPI.PreferencesSchedulerData<*>>(
|
||||
BackupAPI.UPLOAD_THROTTLE.inWholeMilliseconds,
|
||||
onWork = { input ->
|
||||
AccountManager.BackupApis.forEach { api ->
|
||||
|
@ -52,13 +50,13 @@ class Scheduler<INPUT>(
|
|||
},
|
||||
beforeWork = { _ ->
|
||||
AccountManager.BackupApis.filter { api ->
|
||||
api.isReady()
|
||||
api.getIsReady()
|
||||
}.forEach {
|
||||
it.willUploadSoon = true
|
||||
it.setIsUploadingSoon()
|
||||
}
|
||||
},
|
||||
canWork = { input ->
|
||||
val hasSomeActiveManagers = AccountManager.BackupApis.any { it.isReady() }
|
||||
val hasSomeActiveManagers = AccountManager.BackupApis.any { it.getIsReady() }
|
||||
if (!hasSomeActiveManagers) {
|
||||
return@Scheduler false
|
||||
}
|
||||
|
@ -97,14 +95,14 @@ class Scheduler<INPUT>(
|
|||
fun SharedPreferences.attachBackupListener(
|
||||
source: BackupUtils.RestoreSource = BackupUtils.RestoreSource.SETTINGS,
|
||||
syncPrefs: SharedPreferences
|
||||
): IBackupAPI.SharedPreferencesWithListener {
|
||||
): BackupAPI.SharedPreferencesWithListener {
|
||||
val scheduler = createBackupScheduler()
|
||||
|
||||
var lastValue = all
|
||||
registerOnSharedPreferenceChangeListener { sharedPreferences, storeKey ->
|
||||
ioSafe {
|
||||
scheduler.work(
|
||||
IBackupAPI.PreferencesSchedulerData(
|
||||
BackupAPI.PreferencesSchedulerData(
|
||||
syncPrefs,
|
||||
storeKey,
|
||||
lastValue[storeKey],
|
||||
|
@ -116,10 +114,10 @@ class Scheduler<INPUT>(
|
|||
lastValue = sharedPreferences.all
|
||||
}
|
||||
|
||||
return IBackupAPI.SharedPreferencesWithListener(this, scheduler)
|
||||
return BackupAPI.SharedPreferencesWithListener(this, scheduler)
|
||||
}
|
||||
|
||||
fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences): IBackupAPI.SharedPreferencesWithListener {
|
||||
fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences): BackupAPI.SharedPreferencesWithListener {
|
||||
return attachBackupListener(BackupUtils.RestoreSource.SETTINGS, syncPrefs)
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +170,7 @@ class Scheduler<INPUT>(
|
|||
|
||||
runnable = Runnable {
|
||||
Log.d(BackupAPI.LOG_KEY, "[$id] schedule success")
|
||||
runBlocking {
|
||||
ioSafe {
|
||||
onWork(input)
|
||||
}
|
||||
}.also { run ->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue