diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt index 683c1fa63..a4596c599 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncController.kt @@ -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(2) override fun onInvalidated(tables: MutableSet) { 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) + } + } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt index 2f5b91eb3..fa6995793 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt index e42541509..43c8978c9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt index ee6b5b30f..2aeefd08b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -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() } \ No newline at end of file