Schedule sync with rate limit
This commit is contained in:
@@ -5,15 +5,16 @@ import android.accounts.AccountManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.ArrayMap
|
||||
import androidx.room.InvalidationTracker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
|
||||
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
|
||||
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SyncController(
|
||||
context: Context,
|
||||
@@ -21,6 +22,12 @@ class SyncController(
|
||||
|
||||
private val am = AccountManager.get(context)
|
||||
private val accountType = context.getString(R.string.account_type_sync)
|
||||
private val minSyncInterval = if (BuildConfig.DEBUG) {
|
||||
TimeUnit.SECONDS.toMillis(5)
|
||||
} else {
|
||||
TimeUnit.MINUTES.toMillis(4)
|
||||
}
|
||||
private val jobs = ArrayMap<String, Job>(2)
|
||||
|
||||
override fun onInvalidated(tables: MutableSet<String>) {
|
||||
requestSync(
|
||||
@@ -29,6 +36,17 @@ class SyncController(
|
||||
)
|
||||
}
|
||||
|
||||
fun getLastSync(account: Account, authority: String): Long {
|
||||
val key = "last_sync_" + authority.substringAfterLast('.')
|
||||
val rawValue = am.getUserData(account, key) ?: return 0L
|
||||
return rawValue.toLongOrNull() ?: 0L
|
||||
}
|
||||
|
||||
fun setLastSync(account: Account, authority: String, time: Long) {
|
||||
val key = "last_sync_" + authority.substringAfterLast('.')
|
||||
am.setUserData(account, key, time.toString())
|
||||
}
|
||||
|
||||
suspend fun requestFullSync() = withContext(Dispatchers.Default) {
|
||||
requestSyncImpl(favourites = true, history = true)
|
||||
}
|
||||
@@ -46,22 +64,41 @@ class SyncController(
|
||||
if (!ContentResolver.getMasterSyncAutomatically()) {
|
||||
return
|
||||
}
|
||||
// TODO limit frequency
|
||||
if (favourites) {
|
||||
requestSyncForAuthority(account, AUTHORITY_FAVOURITES)
|
||||
scheduleSync(account, AUTHORITY_FAVOURITES)
|
||||
}
|
||||
if (history) {
|
||||
requestSyncForAuthority(account, AUTHORITY_HISTORY)
|
||||
scheduleSync(account, AUTHORITY_HISTORY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestSyncForAuthority(account: Account, authority: String) {
|
||||
private fun scheduleSync(account: Account, authority: String) {
|
||||
if (
|
||||
ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES) &&
|
||||
!ContentResolver.isSyncActive(account, authority) &&
|
||||
!ContentResolver.isSyncPending(account, authority)
|
||||
!ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES) ||
|
||||
ContentResolver.isSyncActive(account, authority) ||
|
||||
ContentResolver.isSyncPending(account, authority)
|
||||
) {
|
||||
return
|
||||
}
|
||||
val job = jobs[authority]
|
||||
if (job?.isActive == true) {
|
||||
// already scheduled
|
||||
return
|
||||
}
|
||||
val lastSyncTime = getLastSync(account, authority)
|
||||
val timeLeft = System.currentTimeMillis() - lastSyncTime + minSyncInterval
|
||||
if (timeLeft <= 0) {
|
||||
jobs.remove(authority)
|
||||
ContentResolver.requestSync(account, authority, Bundle.EMPTY)
|
||||
} else {
|
||||
jobs[authority] = processLifecycleScope.launch(Dispatchers.Default) {
|
||||
try {
|
||||
delay(timeLeft)
|
||||
} finally {
|
||||
// run even if scope cancelled
|
||||
ContentResolver.requestSync(account, authority, Bundle.EMPTY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.ContentProviderClient
|
||||
import android.content.Context
|
||||
import android.content.SyncResult
|
||||
import android.os.Bundle
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.sync.domain.SyncHelper
|
||||
import org.koitharu.kotatsu.utils.ext.onError
|
||||
|
||||
@@ -21,6 +22,7 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
|
||||
val syncHelper = SyncHelper(context, account, provider)
|
||||
runCatching {
|
||||
syncHelper.syncFavourites(syncResult)
|
||||
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
|
||||
}.onFailure(syncResult::onError)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.content.ContentProviderClient
|
||||
import android.content.Context
|
||||
import android.content.SyncResult
|
||||
import android.os.Bundle
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.sync.domain.SyncHelper
|
||||
import org.koitharu.kotatsu.utils.ext.onError
|
||||
|
||||
@@ -21,6 +22,7 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
|
||||
val syncHelper = SyncHelper(context, account, provider)
|
||||
runCatching {
|
||||
syncHelper.syncHistory(syncResult)
|
||||
SyncController(context).setLastSync(account, authority, System.currentTimeMillis())
|
||||
}.onFailure(syncResult::onError)
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,13 @@ import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import androidx.work.CoroutineWorker
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okio.IOException
|
||||
import org.json.JSONException
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
val Context.connectivityManager: ConnectivityManager
|
||||
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
@@ -82,4 +82,5 @@ fun SyncResult.onError(error: Throwable) {
|
||||
is JSONException -> stats.numParseExceptions++
|
||||
else -> if (BuildConfig.DEBUG) throw error
|
||||
}
|
||||
error.printStackTraceDebug()
|
||||
}
|
||||
Reference in New Issue
Block a user