feat: add remote sync capability - schedule updates only when value was really changed (true -> true is not a change)

This commit is contained in:
Martin Filo 2023-05-03 22:35:10 +02:00
parent fe36b69758
commit 7145befb55
4 changed files with 69 additions and 40 deletions

View file

@ -25,15 +25,17 @@ interface BackupAPI<LOGIN_DATA> {
val result: JSONCompareResult? val result: JSONCompareResult?
) )
data class PreferencesSchedulerData( data class PreferencesSchedulerData<T>(
val prefs: SharedPreferences, val syncPrefs: SharedPreferences,
val storeKey: String, val storeKey: String,
val isSettings: Boolean val oldValue: T,
val newValue: T,
val source: BackupUtils.RestoreSource
) )
data class SharedPreferencesWithListener( data class SharedPreferencesWithListener(
val self: SharedPreferences, val self: SharedPreferences,
val scheduler: Scheduler<PreferencesSchedulerData> val scheduler: Scheduler<PreferencesSchedulerData<*>>
) )
companion object { companion object {

View file

@ -39,16 +39,16 @@ import java.util.Date
* *
* | State | Priority | Description * | State | Priority | Description
* |---------:|:--------:|--------------------------------------------------------------------- * |---------:|:--------:|---------------------------------------------------------------------
* | Progress | 1 | Check if data was really changed when calling backupscheduler.work then * | Progress | 4 | Implement backup before user quits application
* | | | dont update sync meta if not needed
* | Waiting | 2 | Add button to manually trigger sync * | Waiting | 2 | Add button to manually trigger sync
* | Waiting | 3 | Move "https://chiff.github.io/cloudstream-sync/google-drive" * | Waiting | 3 | Move "https://chiff.github.io/cloudstream-sync/google-drive"
* | Waiting | 4 | Implement backup before user quits application
* | Waiting | 5 | Choose what should be synced and recheck `invalidKeys` in createBackupScheduler * | Waiting | 5 | Choose what should be synced and recheck `invalidKeys` in createBackupScheduler
* | Someday | 3 | Add option to use proper OAuth through Google Services One Tap * | Someday | 3 | Add option to use proper OAuth through Google Services One Tap
* | Someday | 5 | Encrypt data on Drive (low priority) * | Someday | 5 | Encrypt data on Drive (low priority)
* | Solved | 1 | Racing conditions when multiple devices in use * | Solved | 1 | Racing conditions when multiple devices in use
* | Solved | 2 | Restoring backup should update view models * | Solved | 2 | Restoring backup should update view models
* | Solved | 1 | Check if data was really changed when calling backupscheduler.work then
* | | | dont update sync meta if not needed
*/ */
class GoogleDriveApi(index: Int) : class GoogleDriveApi(index: Int) :
InAppOAuth2APIManager(index), InAppOAuth2APIManager(index),

View file

@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.BackupAPI import com.lagradost.cloudstream3.syncproviders.BackupAPI
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.logHistoryChanged
const val DOWNLOAD_HEADER_CACHE = "download_header_cache" const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
@ -106,15 +105,21 @@ object DataStore {
try { try {
val prefs = getSharedPrefs() val prefs = getSharedPrefs()
if (prefs.contains(path)) { if (prefs.contains(path)) {
val oldValueExists = prefs.getString(path, null) != null
val editor: SharedPreferences.Editor = prefs.edit() val editor: SharedPreferences.Editor = prefs.edit()
editor.remove(path) editor.remove(path)
editor.apply() editor.apply()
val success = backupScheduler.work(
backupScheduler.work(BackupAPI.PreferencesSchedulerData(prefs, path, false)) BackupAPI.PreferencesSchedulerData(
if (success) { prefs,
getSyncPrefs().logHistoryChanged(path, BackupUtils.RestoreSource.DATA) path,
} oldValueExists,
false,
BackupUtils.RestoreSource.DATA
)
)
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
@ -132,14 +137,22 @@ object DataStore {
fun <T> Context.setKey(path: String, value: T) { fun <T> Context.setKey(path: String, value: T) {
try { try {
val prefs = getSharedPrefs() val prefs = getSharedPrefs()
val oldValue = prefs.getString(path, null)
val newValue = mapper.writeValueAsString(value)
val editor: SharedPreferences.Editor = prefs.edit() val editor: SharedPreferences.Editor = prefs.edit()
editor.putString(path, mapper.writeValueAsString(value)) editor.putString(path, newValue)
editor.apply() editor.apply()
val success = backupScheduler.work(BackupAPI.PreferencesSchedulerData(prefs,path, false)) backupScheduler.work(
if (success) { BackupAPI.PreferencesSchedulerData(
getSyncPrefs().logHistoryChanged(path, BackupUtils.RestoreSource.DATA) prefs,
} path,
oldValue,
newValue,
BackupUtils.RestoreSource.DATA
)
)
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }

View file

@ -7,6 +7,7 @@ import android.util.Log
import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.BackupAPI import com.lagradost.cloudstream3.syncproviders.BackupAPI
import com.lagradost.cloudstream3.syncproviders.BackupAPI.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.PLAYBACK_SPEED_KEY
import com.lagradost.cloudstream3.ui.player.RESIZE_MODE_KEY import com.lagradost.cloudstream3.ui.player.RESIZE_MODE_KEY
@ -18,17 +19,26 @@ class Scheduler<INPUT>(
companion object { companion object {
var SCHEDULER_ID = 1 var SCHEDULER_ID = 1
fun createBackupScheduler() = Scheduler<BackupAPI.PreferencesSchedulerData>( private val invalidSchedulerKeys = listOf(
VideoDownloadManager.KEY_DOWNLOAD_INFO,
PLAYBACK_SPEED_KEY,
HOME_BOOKMARK_VALUE_LIST,
RESIZE_MODE_KEY
)
fun createBackupScheduler() = Scheduler<BackupAPI.PreferencesSchedulerData<*>>(
BackupAPI.UPLOAD_THROTTLE.inWholeMilliseconds, BackupAPI.UPLOAD_THROTTLE.inWholeMilliseconds,
onWork = { input -> onWork = { input ->
if (input == null) { if (input == null) {
throw IllegalStateException() throw IllegalStateException()
} }
input.syncPrefs.logHistoryChanged(input.storeKey, input.source)
AccountManager.BackupApis.forEach { AccountManager.BackupApis.forEach {
it.addToQueue( it.addToQueue(
input.storeKey, input.storeKey,
input.isSettings input.source == BackupUtils.RestoreSource.SETTINGS
) )
} }
}, },
@ -37,13 +47,17 @@ class Scheduler<INPUT>(
throw IllegalStateException() throw IllegalStateException()
} }
val invalidKeys = listOf( val hasInvalidKey = invalidSchedulerKeys.contains(input.storeKey)
VideoDownloadManager.KEY_DOWNLOAD_INFO, if (hasInvalidKey) {
PLAYBACK_SPEED_KEY, return@Scheduler false
RESIZE_MODE_KEY }
)
return@Scheduler !invalidKeys.contains(input.storeKey) val valueDidNotChange = input.oldValue == input.newValue
if (valueDidNotChange) {
return@Scheduler false
}
return@Scheduler true
} }
) )
@ -51,30 +65,30 @@ class Scheduler<INPUT>(
// which means it is mostly used for settings preferences, therefore we use `isSettings: Boolean = true`, be careful // which means it is mostly used for settings preferences, therefore we use `isSettings: Boolean = true`, be careful
// if you need to directly access `context.getSharedPreferences` (without using DataStore) and dont forget to turn it off // if you need to directly access `context.getSharedPreferences` (without using DataStore) and dont forget to turn it off
fun SharedPreferences.attachBackupListener( fun SharedPreferences.attachBackupListener(
isSettings: Boolean = true, source: BackupUtils.RestoreSource = BackupUtils.RestoreSource.SETTINGS,
syncPrefs: SharedPreferences syncPrefs: SharedPreferences
): BackupAPI.SharedPreferencesWithListener { ): BackupAPI.SharedPreferencesWithListener {
val scheduler = createBackupScheduler() val scheduler = createBackupScheduler()
registerOnSharedPreferenceChangeListener { _, storeKey ->
val success =
scheduler.work(
BackupAPI.PreferencesSchedulerData(
syncPrefs,
storeKey,
isSettings
)
)
if (success) { var lastValue = all
syncPrefs.logHistoryChanged(storeKey, BackupUtils.RestoreSource.SETTINGS) registerOnSharedPreferenceChangeListener { sharedPreferences, storeKey ->
} scheduler.work(
BackupAPI.PreferencesSchedulerData(
syncPrefs,
storeKey,
lastValue[storeKey],
sharedPreferences.all[storeKey],
source
)
)
lastValue = sharedPreferences.all
} }
return BackupAPI.SharedPreferencesWithListener(this, scheduler) return BackupAPI.SharedPreferencesWithListener(this, scheduler)
} }
fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences): BackupAPI.SharedPreferencesWithListener { fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences): BackupAPI.SharedPreferencesWithListener {
return attachBackupListener(true, syncPrefs) return attachBackupListener(BackupUtils.RestoreSource.SETTINGS, syncPrefs)
} }
} }