mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
feat: add remote sync capability - refactor, refresh ui on restore and improve data restore (pt.2)
This commit is contained in:
parent
e1e039b58c
commit
fe36b69758
21 changed files with 363 additions and 227 deletions
|
@ -264,6 +264,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
val mainPluginsLoadedEvent =
|
val mainPluginsLoadedEvent =
|
||||||
Event<Boolean>() // homepage api, used to speed up time to load for homepage
|
Event<Boolean>() // homepage api, used to speed up time to load for homepage
|
||||||
val afterRepositoryLoadedEvent = Event<Boolean>()
|
val afterRepositoryLoadedEvent = Event<Boolean>()
|
||||||
|
val afterBackupRestoreEvent = Event<Unit>()
|
||||||
|
|
||||||
// kinda shitty solution, but cant com main->home otherwise for popups
|
// kinda shitty solution, but cant com main->home otherwise for popups
|
||||||
val bookmarksUpdatedEvent = Event<Boolean>()
|
val bookmarksUpdatedEvent = Event<Boolean>()
|
||||||
|
|
|
@ -2,8 +2,6 @@ package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||||
|
@ -11,6 +9,7 @@ import com.lagradost.cloudstream3.utils.BackupUtils
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.getBackup
|
import com.lagradost.cloudstream3.utils.BackupUtils.getBackup
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.restore
|
import com.lagradost.cloudstream3.utils.BackupUtils.restore
|
||||||
import com.lagradost.cloudstream3.utils.DataStore
|
import com.lagradost.cloudstream3.utils.DataStore
|
||||||
|
import com.lagradost.cloudstream3.utils.Scheduler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -27,6 +26,7 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
)
|
)
|
||||||
|
|
||||||
data class PreferencesSchedulerData(
|
data class PreferencesSchedulerData(
|
||||||
|
val prefs: SharedPreferences,
|
||||||
val storeKey: String,
|
val storeKey: String,
|
||||||
val isSettings: Boolean
|
val isSettings: Boolean
|
||||||
)
|
)
|
||||||
|
@ -46,41 +46,9 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
// on factors like: live devices, quota limits, etc
|
// on factors like: live devices, quota limits, etc
|
||||||
val UPLOAD_THROTTLE = 10.seconds
|
val UPLOAD_THROTTLE = 10.seconds
|
||||||
val DOWNLOAD_THROTTLE = 60.seconds
|
val DOWNLOAD_THROTTLE = 60.seconds
|
||||||
|
|
||||||
// add to queue may be called frequently
|
// add to queue may be called frequently
|
||||||
private val ioScope = CoroutineScope(Dispatchers.IO)
|
private val ioScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
fun createBackupScheduler() = Scheduler<PreferencesSchedulerData>(
|
|
||||||
UPLOAD_THROTTLE.inWholeMilliseconds
|
|
||||||
) { input ->
|
|
||||||
if (input == null) {
|
|
||||||
throw IllegalStateException()
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountManager.BackupApis.forEach { it.addToQueue(input.storeKey, input.isSettings) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common usage is `val settingsManager = PreferenceManager.getDefaultSharedPreferences(this).attachListener().self`
|
|
||||||
// which means it is mostly used for settings preferences, therefore we use `isSettings: Boolean = true`, be careful
|
|
||||||
// to turn it of if you need to directly access `context.getSharedPreferences` (without using DataStore)
|
|
||||||
|
|
||||||
fun SharedPreferences.attachBackupListener(
|
|
||||||
isSettings: Boolean = true,
|
|
||||||
syncPrefs: SharedPreferences? = null
|
|
||||||
): SharedPreferencesWithListener {
|
|
||||||
val scheduler = createBackupScheduler()
|
|
||||||
registerOnSharedPreferenceChangeListener { _, storeKey ->
|
|
||||||
syncPrefs?.logHistoryChanged(storeKey, BackupUtils.RestoreSource.SETTINGS)
|
|
||||||
scheduler.work(PreferencesSchedulerData(storeKey, isSettings))
|
|
||||||
}
|
|
||||||
|
|
||||||
return SharedPreferencesWithListener(this, scheduler)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences?): SharedPreferencesWithListener {
|
|
||||||
return attachBackupListener(true, syncPrefs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun SharedPreferences.logHistoryChanged(path: String, source: BackupUtils.RestoreSource) {
|
fun SharedPreferences.logHistoryChanged(path: String, source: BackupUtils.RestoreSource) {
|
||||||
edit().putLong("$SYNC_HISTORY_PREFIX${source.prefix}$path", System.currentTimeMillis())
|
edit().putLong("$SYNC_HISTORY_PREFIX${source.prefix}$path", System.currentTimeMillis())
|
||||||
.apply()
|
.apply()
|
||||||
|
@ -89,11 +57,12 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should download data from API and call Context.mergeBackup(incomingData: String). If data
|
* Should download data from API and call Context.mergeBackup(incomingData: String). If data
|
||||||
* does not exist on the api uploadSyncData() is recommended to call
|
* does not exist on the api uploadSyncData() is recommended to call. Should be called with
|
||||||
|
* overwrite=true when user ads new account so it would accept changes from API
|
||||||
* @see Context.mergeBackup
|
* @see Context.mergeBackup
|
||||||
* @see uploadSyncData
|
* @see uploadSyncData
|
||||||
*/
|
*/
|
||||||
fun downloadSyncData()
|
fun downloadSyncData(overwrite: Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should upload data to API and call Context.createBackup(loginData: LOGIN_DATA)
|
* Should upload data to API and call Context.createBackup(loginData: LOGIN_DATA)
|
||||||
|
@ -103,22 +72,24 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
|
|
||||||
|
|
||||||
fun Context.createBackup(loginData: LOGIN_DATA)
|
fun Context.createBackup(loginData: LOGIN_DATA)
|
||||||
fun Context.mergeBackup(incomingData: String) {
|
fun Context.mergeBackup(incomingData: String, overwrite: Boolean) {
|
||||||
val currentData = getBackup()
|
|
||||||
val newData = DataStore.mapper.readValue<BackupUtils.BackupFile>(incomingData)
|
val newData = DataStore.mapper.readValue<BackupUtils.BackupFile>(incomingData)
|
||||||
|
if (overwrite) {
|
||||||
|
Log.d(LOG_KEY, "overwriting data")
|
||||||
|
restore(newData)
|
||||||
|
|
||||||
val keysToUpdate = getKeysToUpdate(currentData, newData)
|
|
||||||
if (keysToUpdate.isEmpty()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
restore(
|
val keysToUpdate = getKeysToUpdate(getBackup(), newData)
|
||||||
newData,
|
if (keysToUpdate.isEmpty()) {
|
||||||
keysToUpdate,
|
Log.d(LOG_KEY, "remote data is up to date, sync not needed")
|
||||||
restoreSettings = true,
|
return
|
||||||
restoreDataStore = true,
|
}
|
||||||
restoreSyncData = true
|
|
||||||
)
|
|
||||||
|
Log.d(LOG_KEY, incomingData)
|
||||||
|
restore(newData, keysToUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadJob: Job?
|
var uploadJob: Job?
|
||||||
|
@ -153,7 +124,7 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
}
|
}
|
||||||
|
|
||||||
val failed = result?.failed() ?: true
|
val failed = result?.failed() ?: true
|
||||||
Log.d(LOG_KEY, "JSON comparison took $executionTime ms, compareFailed=$failed")
|
Log.d(LOG_KEY, "JSON comparison took $executionTime ms, compareFailed=$failed, result=$result")
|
||||||
|
|
||||||
return JSONComparison(failed, result)
|
return JSONComparison(failed, result)
|
||||||
}
|
}
|
||||||
|
@ -161,34 +132,25 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
fun getKeysToUpdate(
|
fun getKeysToUpdate(
|
||||||
currentData: BackupUtils.BackupFile,
|
currentData: BackupUtils.BackupFile,
|
||||||
newData: BackupUtils.BackupFile
|
newData: BackupUtils.BackupFile
|
||||||
): List<String> {
|
): Set<String> {
|
||||||
val currentSync = currentData.syncMeta._Long.orEmpty().filter {
|
val currentSync = getSyncKeys(currentData)
|
||||||
it.key.startsWith(SYNC_HISTORY_PREFIX)
|
val newSync = getSyncKeys(newData)
|
||||||
}
|
|
||||||
|
|
||||||
val newSync = newData.syncMeta._Long.orEmpty().filter {
|
|
||||||
it.key.startsWith(SYNC_HISTORY_PREFIX)
|
|
||||||
}
|
|
||||||
|
|
||||||
val changedKeys = newSync.filter {
|
val changedKeys = newSync.filter {
|
||||||
val localTimestamp = if (currentSync[it.key] != null) {
|
val localTimestamp = currentSync[it.key] ?: 0L
|
||||||
currentSync[it.key]!!
|
|
||||||
} else {
|
|
||||||
0L
|
|
||||||
}
|
|
||||||
|
|
||||||
it.value > localTimestamp
|
it.value > localTimestamp
|
||||||
}.keys
|
}.keys
|
||||||
val onlyLocalKeys = currentSync.keys.filter { !newSync.containsKey(it) }
|
|
||||||
val missingKeys = getMissingKeys(currentData, newData) - changedKeys
|
|
||||||
|
|
||||||
return mutableListOf(
|
val onlyLocalKeys = currentSync.keys.filter { !newSync.containsKey(it) }
|
||||||
*missingKeys.toTypedArray(),
|
val missingKeys = getMissingKeys(currentData, newData)
|
||||||
*onlyLocalKeys.toTypedArray(),
|
|
||||||
*changedKeys.toTypedArray()
|
return (missingKeys + onlyLocalKeys + changedKeys).toSet()
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSyncKeys(data: BackupUtils.BackupFile) =
|
||||||
|
data.syncMeta._Long.orEmpty().filter { it.key.startsWith(SYNC_HISTORY_PREFIX) }
|
||||||
|
|
||||||
|
|
||||||
// 🤮
|
// 🤮
|
||||||
private fun getMissingKeys(
|
private fun getMissingKeys(
|
||||||
old: BackupUtils.BackupFile,
|
old: BackupUtils.BackupFile,
|
||||||
|
@ -211,44 +173,4 @@ interface BackupAPI<LOGIN_DATA> {
|
||||||
private fun getMissing(old: Map<String, *>?, new: Map<String, *>?): Array<String> =
|
private fun getMissing(old: Map<String, *>?, new: Map<String, *>?): Array<String> =
|
||||||
new.orEmpty().keys.subtract(old.orEmpty().keys).toTypedArray()
|
new.orEmpty().keys.subtract(old.orEmpty().keys).toTypedArray()
|
||||||
|
|
||||||
class Scheduler<INPUT>(
|
|
||||||
private val throttleTimeMs: Long,
|
|
||||||
private val onWork: (INPUT?) -> Unit
|
|
||||||
) {
|
|
||||||
private companion object {
|
|
||||||
var SCHEDULER_ID = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
private val id = SCHEDULER_ID++
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
|
||||||
private var runnable: Runnable? = null
|
|
||||||
|
|
||||||
fun work(input: INPUT? = null) {
|
|
||||||
Log.d(LOG_KEY, "[$id] wants to schedule [${input}]")
|
|
||||||
throttle(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun workNow(input: INPUT? = null) {
|
|
||||||
Log.d(LOG_KEY, "[$id] runs immediate [${input}]")
|
|
||||||
stop()
|
|
||||||
onWork(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
runnable?.let {
|
|
||||||
handler.removeCallbacks(it)
|
|
||||||
runnable = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun throttle(input: INPUT?) {
|
|
||||||
stop()
|
|
||||||
|
|
||||||
runnable = Runnable {
|
|
||||||
Log.d(LOG_KEY, "[$id] schedule success")
|
|
||||||
onWork(input)
|
|
||||||
}
|
|
||||||
handler.postDelayed(runnable!!, throttleTimeMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,9 @@ interface InAppOAuth2API : OAuth2API {
|
||||||
abstract class InAppOAuth2APIManager(defIndex: Int) : AccountManager(defIndex), InAppOAuth2API {
|
abstract class InAppOAuth2APIManager(defIndex: Int) : AccountManager(defIndex), InAppOAuth2API {
|
||||||
enum class K {
|
enum class K {
|
||||||
LOGIN_DATA,
|
LOGIN_DATA,
|
||||||
TOKEN;
|
IS_READY,
|
||||||
|
TOKEN,
|
||||||
|
;
|
||||||
|
|
||||||
val value: String = "data_oauth2_$name"
|
val value: String = "data_oauth2_$name"
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.syncproviders.InAppOAuth2APIManager
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.getBackup
|
import com.lagradost.cloudstream3.utils.BackupUtils.getBackup
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.Scheduler
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -38,16 +39,16 @@ import java.util.Date
|
||||||
*
|
*
|
||||||
* | State | Priority | Description
|
* | State | Priority | Description
|
||||||
* |---------:|:--------:|---------------------------------------------------------------------
|
* |---------:|:--------:|---------------------------------------------------------------------
|
||||||
* | Progress | 2 | Restoring backup should update view models
|
* | Progress | 1 | Check if data was really changed when calling backupscheduler.work then
|
||||||
|
* | | | 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 | 3 | We should check what keys should really be restored. If user has multiple
|
|
||||||
* | | | devices with different settings that they want to keep we should respect that
|
|
||||||
* | Waiting | 4 | Implement backup before user quits application
|
* | Waiting | 4 | Implement backup before user quits application
|
||||||
* | Waiting | 5 | Choose what should be synced
|
* | 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
|
||||||
*/
|
*/
|
||||||
class GoogleDriveApi(index: Int) :
|
class GoogleDriveApi(index: Int) :
|
||||||
InAppOAuth2APIManager(index),
|
InAppOAuth2APIManager(index),
|
||||||
|
@ -107,8 +108,9 @@ class GoogleDriveApi(index: Int) :
|
||||||
)
|
)
|
||||||
|
|
||||||
storeValue(K.TOKEN, googleTokenResponse)
|
storeValue(K.TOKEN, googleTokenResponse)
|
||||||
runDownloader(true)
|
runDownloader(runNow = true, overwrite = true)
|
||||||
|
|
||||||
|
storeValue(K.IS_READY, true)
|
||||||
tempAuthFlow = null
|
tempAuthFlow = null
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -147,6 +149,7 @@ class GoogleDriveApi(index: Int) :
|
||||||
switchToNewAccount()
|
switchToNewAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeValue(K.IS_READY, false)
|
||||||
storeValue(K.LOGIN_DATA, data)
|
storeValue(K.LOGIN_DATA, data)
|
||||||
|
|
||||||
val authFlow = GAPI.createAuthFlow(data.clientId, data.secret)
|
val authFlow = GAPI.createAuthFlow(data.clientId, data.secret)
|
||||||
|
@ -211,7 +214,7 @@ class GoogleDriveApi(index: Int) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun downloadSyncData() {
|
override fun downloadSyncData(overwrite: Boolean) {
|
||||||
val ctx = AcraApplication.context ?: return
|
val ctx = AcraApplication.context ?: return
|
||||||
val drive = getDriveService() ?: return
|
val drive = getDriveService() ?: return
|
||||||
val loginData = getLatestLoginData() ?: return
|
val loginData = getLatestLoginData() ?: return
|
||||||
|
@ -220,7 +223,8 @@ class GoogleDriveApi(index: Int) :
|
||||||
val existingFile = if (existingFileId != null) {
|
val existingFile = if (existingFileId != null) {
|
||||||
try {
|
try {
|
||||||
drive.files().get(existingFileId)
|
drive.files().get(existingFileId)
|
||||||
} catch (_: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_KEY, "Could not find file for id $existingFileId", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -232,19 +236,27 @@ class GoogleDriveApi(index: Int) :
|
||||||
val inputStream: InputStream = existingFile.executeMediaAsInputStream()
|
val inputStream: InputStream = existingFile.executeMediaAsInputStream()
|
||||||
val content: String = inputStream.bufferedReader().use { it.readText() }
|
val content: String = inputStream.bufferedReader().use { it.readText() }
|
||||||
Log.d(LOG_KEY, "downloadSyncData merging")
|
Log.d(LOG_KEY, "downloadSyncData merging")
|
||||||
ctx.mergeBackup(content)
|
ctx.mergeBackup(content, overwrite)
|
||||||
return
|
return
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_KEY,"download failed", e)
|
Log.e(LOG_KEY, "download failed", e)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Log.d(LOG_KEY, "downloadSyncData file not exists")
|
|
||||||
uploadSyncData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if failed
|
||||||
|
Log.d(LOG_KEY, "downloadSyncData file not exists")
|
||||||
|
uploadSyncData()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getOrCreateSyncFileId(drive: Drive, loginData: InAppOAuth2API.LoginData): String? {
|
private fun getOrCreateSyncFileId(drive: Drive, loginData: InAppOAuth2API.LoginData): String? {
|
||||||
val existingFileId: String? = loginData.syncFileId ?: drive
|
if (loginData.syncFileId != null) {
|
||||||
|
val verified = drive.files().get(loginData.syncFileId)
|
||||||
|
if (verified != null) {
|
||||||
|
return loginData.syncFileId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val existingFileId: String? = drive
|
||||||
.files()
|
.files()
|
||||||
.list()
|
.list()
|
||||||
.setQ("name='${loginData.fileName}' and trashed=false")
|
.setQ("name='${loginData.fileName}' and trashed=false")
|
||||||
|
@ -253,29 +265,38 @@ class GoogleDriveApi(index: Int) :
|
||||||
?.getOrNull(0)
|
?.getOrNull(0)
|
||||||
?.id
|
?.id
|
||||||
|
|
||||||
if (loginData.syncFileId == null) {
|
if (existingFileId != null) {
|
||||||
if (existingFileId != null) {
|
loginData.syncFileId = existingFileId
|
||||||
loginData.syncFileId = existingFileId
|
storeValue(K.LOGIN_DATA, loginData)
|
||||||
storeValue(K.LOGIN_DATA, loginData)
|
|
||||||
|
|
||||||
return existingFileId
|
return existingFileId
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val verifyId = drive.files().get(existingFileId)
|
return null
|
||||||
return if (verifyId == null) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
existingFileId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun uploadSyncData() {
|
override fun uploadSyncData() {
|
||||||
val ctx = AcraApplication.context ?: return
|
val canUpload = getValue<Boolean>(K.IS_READY)
|
||||||
val loginData = getLatestLoginData() ?: return
|
if (canUpload != true) {
|
||||||
Log.d(LOG_KEY, "uploadSyncData createBackup")
|
Log.d(LOG_KEY, "uploadSyncData is not ready yet")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val ctx = AcraApplication.context
|
||||||
|
val loginData = getLatestLoginData()
|
||||||
|
|
||||||
|
if (ctx == null) {
|
||||||
|
Log.d(LOG_KEY, "uploadSyncData cannot run (ctx)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (loginData == null) {
|
||||||
|
Log.d(LOG_KEY, "uploadSyncData cannot run (loginData)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(LOG_KEY, "uploadSyncData will run")
|
||||||
ctx.createBackup(loginData)
|
ctx.createBackup(loginData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,29 +322,28 @@ class GoogleDriveApi(index: Int) :
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// Internal
|
// Internal
|
||||||
private val continuousDownloader = BackupAPI.Scheduler<Unit>(
|
private val continuousDownloader = Scheduler<Boolean>(
|
||||||
BackupAPI.DOWNLOAD_THROTTLE.inWholeMilliseconds
|
BackupAPI.DOWNLOAD_THROTTLE.inWholeMilliseconds,
|
||||||
) {
|
{ overwrite ->
|
||||||
if (uploadJob?.isActive == true) {
|
if (uploadJob?.isActive == true) {
|
||||||
uploadJob!!.invokeOnCompletion {
|
uploadJob!!.invokeOnCompletion {
|
||||||
Log.d(LOG_KEY, "upload is running, reschedule download")
|
Log.d(LOG_KEY, "upload is running, reschedule download")
|
||||||
|
runDownloader(false, overwrite == true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(LOG_KEY, "downloadSyncData will run")
|
||||||
|
ioSafe {
|
||||||
|
downloadSyncData(overwrite == true)
|
||||||
|
}
|
||||||
runDownloader()
|
runDownloader()
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
Log.d(LOG_KEY, "downloadSyncData will run")
|
|
||||||
ioSafe {
|
|
||||||
downloadSyncData()
|
|
||||||
}
|
|
||||||
runDownloader()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun runDownloader(runNow: Boolean = false) {
|
private fun runDownloader(runNow: Boolean = false, overwrite: Boolean = false) {
|
||||||
if (runNow) {
|
if (runNow) {
|
||||||
continuousDownloader.workNow()
|
continuousDownloader.workNow(overwrite)
|
||||||
} else {
|
} else {
|
||||||
continuousDownloader.work()
|
continuousDownloader.work(overwrite)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterBackupRestoreEvent
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
|
||||||
|
@ -477,16 +478,18 @@ class HomeFragment : Fragment() {
|
||||||
bookmarksUpdatedEvent += ::bookmarksUpdated
|
bookmarksUpdatedEvent += ::bookmarksUpdated
|
||||||
afterPluginsLoadedEvent += ::afterPluginsLoaded
|
afterPluginsLoadedEvent += ::afterPluginsLoaded
|
||||||
mainPluginsLoadedEvent += ::afterMainPluginsLoaded
|
mainPluginsLoadedEvent += ::afterMainPluginsLoaded
|
||||||
|
afterBackupRestoreEvent += ::reloadStored
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
bookmarksUpdatedEvent -= ::bookmarksUpdated
|
bookmarksUpdatedEvent -= ::bookmarksUpdated
|
||||||
afterPluginsLoadedEvent -= ::afterPluginsLoaded
|
afterPluginsLoadedEvent -= ::afterPluginsLoaded
|
||||||
mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
|
mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
|
||||||
|
afterBackupRestoreEvent -= ::reloadStored
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reloadStored() {
|
private fun reloadStored(unused: Unit = Unit) {
|
||||||
homeViewModel.loadResumeWatching()
|
homeViewModel.loadResumeWatching()
|
||||||
val list = EnumSet.noneOf(WatchType::class.java)
|
val list = EnumSet.noneOf(WatchType::class.java)
|
||||||
getKey<IntArray>(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let {
|
getKey<IntArray>(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -21,10 +22,12 @@ import com.lagradost.cloudstream3.APIHolder.allProviders
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.BackupAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||||
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
|
@ -38,6 +41,7 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
import kotlinx.android.synthetic.main.fragment_library.*
|
||||||
|
import org.checkerframework.framework.qual.Unused
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
const val LIBRARY_FOLDER = "library_folder"
|
const val LIBRARY_FOLDER = "library_folder"
|
||||||
|
@ -76,9 +80,15 @@ class LibraryFragment : Fragment() {
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
|
MainActivity.afterBackupRestoreEvent += ::onNewSyncData
|
||||||
return inflater.inflate(R.layout.fragment_library, container, false)
|
return inflater.inflate(R.layout.fragment_library, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
MainActivity.afterBackupRestoreEvent -= ::onNewSyncData
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
viewpager?.currentItem?.let { currentItem ->
|
viewpager?.currentItem?.let { currentItem ->
|
||||||
outState.putInt(VIEWPAGER_ITEM_KEY, currentItem)
|
outState.putInt(VIEWPAGER_ITEM_KEY, currentItem)
|
||||||
|
@ -386,6 +396,21 @@ class LibraryFragment : Fragment() {
|
||||||
(viewpager.adapter as? ViewpagerAdapter)?.rebind()
|
(viewpager.adapter as? ViewpagerAdapter)?.rebind()
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
MainActivity.afterBackupRestoreEvent += ::onNewSyncData
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
MainActivity.afterBackupRestoreEvent -= ::onNewSyncData
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onNewSyncData(unused: Unit) {
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "will reload pages")
|
||||||
|
libraryViewModel.reloadPages(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MenuSearchView(context: Context) : SearchView(context) {
|
class MenuSearchView(context: Context) : SearchView(context) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.mvvm.*
|
import com.lagradost.cloudstream3.mvvm.*
|
||||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage
|
import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage
|
||||||
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding
|
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
|
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
|
||||||
|
|
|
@ -25,7 +25,7 @@ import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.network.initClient
|
import com.lagradost.cloudstream3.network.initClient
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.EasterEggMonke
|
import com.lagradost.cloudstream3.ui.EasterEggMonke
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
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.setPaddingBottom
|
||||||
|
|
|
@ -7,7 +7,7 @@ import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
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.setPaddingBottom
|
||||||
|
|
|
@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.AllLanguagesName
|
||||||
import com.lagradost.cloudstream3.DubStatus
|
import com.lagradost.cloudstream3.DubStatus
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
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.setPaddingBottom
|
||||||
|
|
|
@ -8,7 +8,7 @@ import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchQuality
|
import com.lagradost.cloudstream3.SearchQuality
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
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.setPaddingBottom
|
||||||
|
|
|
@ -14,7 +14,7 @@ import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
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.setPaddingBottom
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
|
|
|
@ -15,7 +15,7 @@ import com.lagradost.cloudstream3.CommonActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
||||||
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
|
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||||
|
|
|
@ -10,7 +10,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_layout.acra_switch
|
import kotlinx.android.synthetic.main.fragment_setup_layout.acra_switch
|
||||||
|
|
|
@ -12,7 +12,7 @@ import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
|
|
|
@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.APIHolder
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
||||||
import com.lagradost.cloudstream3.AllLanguagesName
|
import com.lagradost.cloudstream3.AllLanguagesName
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
|
|
|
@ -27,7 +27,7 @@ import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
@ -14,6 +15,7 @@ import androidx.fragment.app.FragmentActivity
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterBackupRestoreEvent
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.plugins.PLUGINS_KEY
|
import com.lagradost.cloudstream3.plugins.PLUGINS_KEY
|
||||||
|
@ -70,6 +72,7 @@ object BackupUtils {
|
||||||
MAL_UNIXTIME_KEY,
|
MAL_UNIXTIME_KEY,
|
||||||
MAL_USER_KEY,
|
MAL_USER_KEY,
|
||||||
InAppOAuth2APIManager.K.TOKEN.value,
|
InAppOAuth2APIManager.K.TOKEN.value,
|
||||||
|
InAppOAuth2APIManager.K.IS_READY.value,
|
||||||
|
|
||||||
// The plugins themselves are not backed up
|
// The plugins themselves are not backed up
|
||||||
PLUGINS_KEY,
|
PLUGINS_KEY,
|
||||||
|
@ -87,6 +90,16 @@ object BackupUtils {
|
||||||
private var restoreFileSelector: ActivityResultLauncher<Array<String>>? = null
|
private var restoreFileSelector: ActivityResultLauncher<Array<String>>? = null
|
||||||
|
|
||||||
// Kinda hack, but I couldn't think of a better way
|
// Kinda hack, but I couldn't think of a better way
|
||||||
|
data class RestoreMapData(
|
||||||
|
val wantToRestore: MutableSet<String> = mutableSetOf(),
|
||||||
|
val successfulRestore: MutableSet<String> = mutableSetOf()
|
||||||
|
) {
|
||||||
|
fun addAll(data: RestoreMapData) {
|
||||||
|
wantToRestore.addAll(data.wantToRestore)
|
||||||
|
successfulRestore.addAll(data.successfulRestore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class BackupVars(
|
data class BackupVars(
|
||||||
@JsonProperty("_Bool") val _Bool: Map<String, Boolean>?,
|
@JsonProperty("_Bool") val _Bool: Map<String, Boolean>?,
|
||||||
@JsonProperty("_Int") val _Int: Map<String, Int>?,
|
@JsonProperty("_Int") val _Int: Map<String, Int>?,
|
||||||
|
@ -99,8 +112,32 @@ object BackupUtils {
|
||||||
data class BackupFile(
|
data class BackupFile(
|
||||||
@JsonProperty("datastore") val datastore: BackupVars,
|
@JsonProperty("datastore") val datastore: BackupVars,
|
||||||
@JsonProperty("settings") val settings: BackupVars,
|
@JsonProperty("settings") val settings: BackupVars,
|
||||||
@JsonProperty("sync-meta") val syncMeta: BackupVars
|
@JsonProperty("sync-meta") val syncMeta: BackupVars,
|
||||||
)
|
) {
|
||||||
|
fun restore(
|
||||||
|
ctx: Context,
|
||||||
|
source: RestoreSource,
|
||||||
|
restoreKeys: Set<String>? = null
|
||||||
|
): RestoreMapData {
|
||||||
|
val data = getData(source)
|
||||||
|
val successfulRestore = RestoreMapData()
|
||||||
|
|
||||||
|
successfulRestore.addAll(ctx.restoreMap(data._Bool, source, restoreKeys))
|
||||||
|
successfulRestore.addAll(ctx.restoreMap(data._Int, source, restoreKeys))
|
||||||
|
successfulRestore.addAll(ctx.restoreMap(data._String, source, restoreKeys))
|
||||||
|
successfulRestore.addAll(ctx.restoreMap(data._Float, source, restoreKeys))
|
||||||
|
successfulRestore.addAll(ctx.restoreMap(data._Long, source, restoreKeys))
|
||||||
|
successfulRestore.addAll(ctx.restoreMap(data._StringSet, source, restoreKeys))
|
||||||
|
|
||||||
|
return successfulRestore
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getData(source: RestoreSource) = when (source) {
|
||||||
|
RestoreSource.SYNC -> syncMeta
|
||||||
|
RestoreSource.DATA -> datastore
|
||||||
|
RestoreSource.SETTINGS -> settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun Context.getBackup(): BackupFile {
|
fun Context.getBackup(): BackupFile {
|
||||||
|
@ -142,40 +179,38 @@ object BackupUtils {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun Context.restore(backupFile: BackupFile, restoreKeys: Set<String>? = null) = restore(
|
||||||
|
backupFile,
|
||||||
|
restoreKeys,
|
||||||
|
RestoreSource.SYNC,
|
||||||
|
RestoreSource.DATA,
|
||||||
|
RestoreSource.SETTINGS
|
||||||
|
)
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun Context.restore(
|
fun Context.restore(
|
||||||
backupFile: BackupFile,
|
backupFile: BackupFile,
|
||||||
restoreKeys: List<String>? = null,
|
restoreKeys: Set<String>? = null,
|
||||||
restoreSettings: Boolean,
|
vararg restoreSources: RestoreSource
|
||||||
restoreDataStore: Boolean,
|
|
||||||
restoreSyncData: Boolean
|
|
||||||
) {
|
) {
|
||||||
if (restoreSyncData) {
|
for (restoreSource in restoreSources) {
|
||||||
restoreMap(backupFile.syncMeta._Bool, RestoreSource.SYNC, restoreKeys)
|
val restoreData = RestoreMapData()
|
||||||
restoreMap(backupFile.syncMeta._Int, RestoreSource.SYNC, restoreKeys)
|
|
||||||
restoreMap(backupFile.syncMeta._String, RestoreSource.SYNC, restoreKeys)
|
restoreData.addAll(backupFile.restore(this, restoreSource, restoreKeys))
|
||||||
restoreMap(backupFile.syncMeta._Float, RestoreSource.SYNC, restoreKeys)
|
|
||||||
restoreMap(backupFile.syncMeta._Long, RestoreSource.SYNC, restoreKeys)
|
// we must remove keys that are not present
|
||||||
restoreMap(backupFile.syncMeta._StringSet, RestoreSource.SYNC, restoreKeys)
|
if (!restoreKeys.isNullOrEmpty()) {
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "successfulRestore for src=[${restoreSource.name}]: ${restoreData.successfulRestore}")
|
||||||
|
val removedKeys = restoreData.wantToRestore - restoreData.successfulRestore
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "removed keys for src=[${restoreSource.name}]: $removedKeys")
|
||||||
|
|
||||||
|
removedKeys.forEach { removeKeyRaw(it, restoreSource) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restoreSettings) {
|
Log.d(BackupAPI.LOG_KEY, "restore on ui event fired")
|
||||||
restoreMap(backupFile.settings._Bool, RestoreSource.SETTINGS, restoreKeys)
|
afterBackupRestoreEvent.invoke(Unit)
|
||||||
restoreMap(backupFile.settings._Int, RestoreSource.SETTINGS, restoreKeys)
|
|
||||||
restoreMap(backupFile.settings._String, RestoreSource.SETTINGS, restoreKeys)
|
|
||||||
restoreMap(backupFile.settings._Float, RestoreSource.SETTINGS, restoreKeys)
|
|
||||||
restoreMap(backupFile.settings._Long, RestoreSource.SETTINGS, restoreKeys)
|
|
||||||
restoreMap(backupFile.settings._StringSet, RestoreSource.SETTINGS, restoreKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restoreDataStore) {
|
|
||||||
restoreMap(backupFile.datastore._Bool, RestoreSource.DATA, restoreKeys)
|
|
||||||
restoreMap(backupFile.datastore._Int, RestoreSource.DATA, restoreKeys)
|
|
||||||
restoreMap(backupFile.datastore._String, RestoreSource.DATA, restoreKeys)
|
|
||||||
restoreMap(backupFile.datastore._Float, RestoreSource.DATA, restoreKeys)
|
|
||||||
restoreMap(backupFile.datastore._Long, RestoreSource.DATA, restoreKeys)
|
|
||||||
restoreMap(backupFile.datastore._StringSet, RestoreSource.DATA, restoreKeys)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
|
@ -263,12 +298,7 @@ object BackupUtils {
|
||||||
val input = activity.contentResolver.openInputStream(uri)
|
val input = activity.contentResolver.openInputStream(uri)
|
||||||
?: return@ioSafe
|
?: return@ioSafe
|
||||||
|
|
||||||
activity.restore(
|
activity.restore(mapper.readValue(input))
|
||||||
mapper.readValue(input),
|
|
||||||
restoreSettings = true,
|
|
||||||
restoreDataStore = true,
|
|
||||||
restoreSyncData = true
|
|
||||||
)
|
|
||||||
activity.runOnUiThread { activity.recreate() }
|
activity.runOnUiThread { activity.recreate() }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
|
@ -310,10 +340,10 @@ object BackupUtils {
|
||||||
private fun <T> Context.restoreMap(
|
private fun <T> Context.restoreMap(
|
||||||
map: Map<String, T>?,
|
map: Map<String, T>?,
|
||||||
restoreSource: RestoreSource,
|
restoreSource: RestoreSource,
|
||||||
restoreKeys: List<String>?
|
restoreKeys: Set<String>? = null
|
||||||
) {
|
): RestoreMapData {
|
||||||
val restoreOnlyThese = mutableListOf<String>()
|
val restoreOnlyThese = mutableSetOf<String>()
|
||||||
val successfulRestore = mutableListOf<String>()
|
val successfulRestore = mutableSetOf<String>()
|
||||||
|
|
||||||
if (!restoreKeys.isNullOrEmpty()) {
|
if (!restoreKeys.isNullOrEmpty()) {
|
||||||
val prefixToMatch = "${BackupAPI.SYNC_HISTORY_PREFIX}${restoreSource.prefix}"
|
val prefixToMatch = "${BackupAPI.SYNC_HISTORY_PREFIX}${restoreSource.prefix}"
|
||||||
|
@ -327,26 +357,26 @@ object BackupUtils {
|
||||||
restoreOnlyThese.addAll(restore)
|
restoreOnlyThese.addAll(restore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
map?.filter {
|
map?.filter {
|
||||||
var isTransferable = it.key.isTransferable()
|
var isTransferable = it.key.isTransferable()
|
||||||
var canRestore = isTransferable
|
|
||||||
if (restoreOnlyThese.isNotEmpty()) {
|
if (isTransferable && restoreOnlyThese.isNotEmpty()) {
|
||||||
canRestore = canRestore && restoreOnlyThese.contains(it.key)
|
isTransferable = restoreOnlyThese.contains(it.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTransferable && canRestore) {
|
if (isTransferable) {
|
||||||
successfulRestore.add(it.key)
|
successfulRestore.add(it.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
canRestore
|
isTransferable
|
||||||
}?.forEach {
|
}?.forEach {
|
||||||
setKeyRaw(it.key, it.value, restoreSource)
|
setKeyRaw(it.key, it.value, restoreSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we must remove keys that are not present
|
return RestoreMapData(
|
||||||
if (!restoreKeys.isNullOrEmpty()) {
|
restoreOnlyThese,
|
||||||
var removedKeys = restoreOnlyThese - successfulRestore.toSet()
|
successfulRestore
|
||||||
removedKeys.forEach { removeKeyRaw(it, restoreSource) }
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,7 +27,7 @@ object DataStore {
|
||||||
.configure(DeserializationFeature.USE_LONG_FOR_INTS, true)
|
.configure(DeserializationFeature.USE_LONG_FOR_INTS, true)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val backupScheduler = BackupAPI.createBackupScheduler()
|
private val backupScheduler = Scheduler.createBackupScheduler()
|
||||||
|
|
||||||
private fun getPreferences(context: Context): SharedPreferences {
|
private fun getPreferences(context: Context): SharedPreferences {
|
||||||
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||||
|
@ -110,8 +110,11 @@ object DataStore {
|
||||||
editor.remove(path)
|
editor.remove(path)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
||||||
getSyncPrefs().logHistoryChanged(path, BackupUtils.RestoreSource.DATA)
|
val success =
|
||||||
backupScheduler.work(BackupAPI.PreferencesSchedulerData(path, false))
|
backupScheduler.work(BackupAPI.PreferencesSchedulerData(prefs, path, false))
|
||||||
|
if (success) {
|
||||||
|
getSyncPrefs().logHistoryChanged(path, BackupUtils.RestoreSource.DATA)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
|
@ -128,12 +131,15 @@ object DataStore {
|
||||||
|
|
||||||
fun <T> Context.setKey(path: String, value: T) {
|
fun <T> Context.setKey(path: String, value: T) {
|
||||||
try {
|
try {
|
||||||
val editor: SharedPreferences.Editor = getSharedPrefs().edit()
|
val prefs = getSharedPrefs()
|
||||||
|
val editor: SharedPreferences.Editor = prefs.edit()
|
||||||
editor.putString(path, mapper.writeValueAsString(value))
|
editor.putString(path, mapper.writeValueAsString(value))
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
||||||
getSyncPrefs().logHistoryChanged(path, BackupUtils.RestoreSource.DATA)
|
val success = backupScheduler.work(BackupAPI.PreferencesSchedulerData(prefs,path, false))
|
||||||
backupScheduler.work(BackupAPI.PreferencesSchedulerData(path, false))
|
if (success) {
|
||||||
|
getSyncPrefs().logHistoryChanged(path, BackupUtils.RestoreSource.DATA)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.BackupAPI.Companion.attachBackupListener
|
import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
|
127
app/src/main/java/com/lagradost/cloudstream3/utils/Scheduler.kt
Normal file
127
app/src/main/java/com/lagradost/cloudstream3/utils/Scheduler.kt
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Handler
|
||||||
|
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.BackupAPI.Companion.logHistoryChanged
|
||||||
|
import com.lagradost.cloudstream3.ui.player.PLAYBACK_SPEED_KEY
|
||||||
|
import com.lagradost.cloudstream3.ui.player.RESIZE_MODE_KEY
|
||||||
|
|
||||||
|
class Scheduler<INPUT>(
|
||||||
|
private val throttleTimeMs: Long,
|
||||||
|
private val onWork: (INPUT?) -> Unit,
|
||||||
|
private val canWork: ((INPUT?) -> Boolean)? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
var SCHEDULER_ID = 1
|
||||||
|
|
||||||
|
fun createBackupScheduler() = Scheduler<BackupAPI.PreferencesSchedulerData>(
|
||||||
|
BackupAPI.UPLOAD_THROTTLE.inWholeMilliseconds,
|
||||||
|
onWork = { input ->
|
||||||
|
if (input == null) {
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountManager.BackupApis.forEach {
|
||||||
|
it.addToQueue(
|
||||||
|
input.storeKey,
|
||||||
|
input.isSettings
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canWork = { input ->
|
||||||
|
if (input == null) {
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val invalidKeys = listOf(
|
||||||
|
VideoDownloadManager.KEY_DOWNLOAD_INFO,
|
||||||
|
PLAYBACK_SPEED_KEY,
|
||||||
|
RESIZE_MODE_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
return@Scheduler !invalidKeys.contains(input.storeKey)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common usage is `val settingsManager = PreferenceManager.getDefaultSharedPreferences(this).attachListener().self`
|
||||||
|
// 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,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BackupAPI.SharedPreferencesWithListener(this, scheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SharedPreferences.attachBackupListener(syncPrefs: SharedPreferences): BackupAPI.SharedPreferencesWithListener {
|
||||||
|
return attachBackupListener(true, syncPrefs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val id = SCHEDULER_ID++
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var runnable: Runnable? = null
|
||||||
|
|
||||||
|
fun work(input: INPUT? = null): Boolean {
|
||||||
|
if (canWork?.invoke(input) == false) {
|
||||||
|
// Log.d(LOG_KEY, "[$id] cannot schedule [${input}]")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "[$id] wants to schedule [${input}]")
|
||||||
|
throttle(input)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun workNow(input: INPUT? = null): Boolean {
|
||||||
|
if (canWork?.invoke(input) == false) {
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "[$id] cannot run immediate [${input}]")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "[$id] runs immediate [${input}]")
|
||||||
|
stop()
|
||||||
|
onWork(input)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
runnable?.let {
|
||||||
|
handler.removeCallbacks(it)
|
||||||
|
runnable = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun throttle(input: INPUT?) {
|
||||||
|
stop()
|
||||||
|
|
||||||
|
runnable = Runnable {
|
||||||
|
Log.d(BackupAPI.LOG_KEY, "[$id] schedule success")
|
||||||
|
onWork(input)
|
||||||
|
}
|
||||||
|
handler.postDelayed(runnable!!, throttleTimeMs)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue