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?
)
data class PreferencesSchedulerData(
val prefs: SharedPreferences,
data class PreferencesSchedulerData<T>(
val syncPrefs: SharedPreferences,
val storeKey: String,
val isSettings: Boolean
val oldValue: T,
val newValue: T,
val source: BackupUtils.RestoreSource
)
data class SharedPreferencesWithListener(
val self: SharedPreferences,
val scheduler: Scheduler<PreferencesSchedulerData>
val scheduler: Scheduler<PreferencesSchedulerData<*>>
)
companion object {

View file

@ -39,16 +39,16 @@ import java.util.Date
*
* | State | Priority | Description
* |---------:|:--------:|---------------------------------------------------------------------
* | Progress | 1 | Check if data was really changed when calling backupscheduler.work then
* | | | dont update sync meta if not needed
* | Progress | 4 | Implement backup before user quits application
* | Waiting | 2 | Add button to manually trigger sync
* | 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
* | Someday | 3 | Add option to use proper OAuth through Google Services One Tap
* | Someday | 5 | Encrypt data on Drive (low priority)
* | Solved | 1 | Racing conditions when multiple devices in use
* | 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) :
InAppOAuth2APIManager(index),

View file

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

View file

@ -7,6 +7,7 @@ import android.util.Log
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.BackupAPI
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
@ -18,17 +19,26 @@ class Scheduler<INPUT>(
companion object {
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,
onWork = { input ->
if (input == null) {
throw IllegalStateException()
}
input.syncPrefs.logHistoryChanged(input.storeKey, input.source)
AccountManager.BackupApis.forEach {
it.addToQueue(
input.storeKey,
input.isSettings
input.source == BackupUtils.RestoreSource.SETTINGS
)
}
},
@ -37,13 +47,17 @@ class Scheduler<INPUT>(
throw IllegalStateException()
}
val invalidKeys = listOf(
VideoDownloadManager.KEY_DOWNLOAD_INFO,
PLAYBACK_SPEED_KEY,
RESIZE_MODE_KEY
)
val hasInvalidKey = invalidSchedulerKeys.contains(input.storeKey)
if (hasInvalidKey) {
return@Scheduler false
}
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
// if you need to directly access `context.getSharedPreferences` (without using DataStore) and dont forget to turn it off
fun SharedPreferences.attachBackupListener(
isSettings: Boolean = true,
source: BackupUtils.RestoreSource = BackupUtils.RestoreSource.SETTINGS,
syncPrefs: SharedPreferences
): BackupAPI.SharedPreferencesWithListener {
val scheduler = createBackupScheduler()
registerOnSharedPreferenceChangeListener { _, storeKey ->
val success =
scheduler.work(
BackupAPI.PreferencesSchedulerData(
syncPrefs,
storeKey,
isSettings
)
)
if (success) {
syncPrefs.logHistoryChanged(storeKey, BackupUtils.RestoreSource.SETTINGS)
}
var lastValue = all
registerOnSharedPreferenceChangeListener { sharedPreferences, storeKey ->
scheduler.work(
BackupAPI.PreferencesSchedulerData(
syncPrefs,
storeKey,
lastValue[storeKey],
sharedPreferences.all[storeKey],
source
)
)
lastValue = sharedPreferences.all
}
return BackupAPI.SharedPreferencesWithListener(this, scheduler)
}
fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences): BackupAPI.SharedPreferencesWithListener {
return attachBackupListener(true, syncPrefs)
return attachBackupListener(BackupUtils.RestoreSource.SETTINGS, syncPrefs)
}
}