Sync on demand

This commit is contained in:
Koitharu
2022-05-14 18:29:38 +03:00
parent 1be8760c00
commit d31d302896
18 changed files with 178 additions and 55 deletions

View File

@@ -1,14 +1,19 @@
package org.koitharu.kotatsu.sync
import androidx.room.InvalidationTracker
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.sync.ui.SyncAuthViewModel
val syncModule
get() = module {
single { SyncController(androidContext()) } bind InvalidationTracker.Observer::class
factory { SyncAuthApi(androidContext(), get()) }
viewModel { SyncAuthViewModel(get()) }

View File

@@ -0,0 +1,71 @@
package org.koitharu.kotatsu.sync.domain
import android.accounts.Account
import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.Context
import android.os.Bundle
import androidx.room.InvalidationTracker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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
class SyncController(
context: Context,
) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) {
private val am = AccountManager.get(context)
private val accountType = context.getString(R.string.account_type_sync)
override fun onInvalidated(tables: MutableSet<String>) {
requestSync(
favourites = TABLE_FAVOURITES in tables || TABLE_FAVOURITE_CATEGORIES in tables,
history = TABLE_HISTORY in tables,
)
}
suspend fun requestFullSync() = withContext(Dispatchers.Default) {
requestSyncImpl(favourites = true, history = true)
}
private fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) {
requestSyncImpl(favourites, history)
}
@Synchronized
private fun requestSyncImpl(favourites: Boolean, history: Boolean) {
if (!favourites && !history) {
return
}
val account = peekAccount() ?: return
if (!ContentResolver.getMasterSyncAutomatically()) {
return
}
// TODO limit frequency
if (favourites) {
requestSyncForAuthority(account, AUTHORITY_FAVOURITES)
}
if (history) {
requestSyncForAuthority(account, AUTHORITY_HISTORY)
}
}
private fun requestSyncForAuthority(account: Account, authority: String) {
if (
ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES) &&
!ContentResolver.isSyncActive(account, authority) &&
!ContentResolver.isSyncPending(account, authority)
) {
ContentResolver.requestSync(account, authority, Bundle.EMPTY)
}
}
private fun peekAccount(): Account? {
return am.getAccountsByType(accountType).firstOrNull()
}
}

View File

@@ -13,17 +13,17 @@ import org.json.JSONObject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.*
import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.data.SyncAuthenticator
import org.koitharu.kotatsu.sync.data.SyncInterceptor
import org.koitharu.kotatsu.utils.GZipInterceptor
import org.koitharu.kotatsu.utils.ext.parseJsonOrNull
import org.koitharu.kotatsu.utils.ext.toContentValues
import org.koitharu.kotatsu.utils.ext.toJson
import org.koitharu.kotatsu.utils.ext.toRequestBody
private const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
private const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
private const val FIELD_TIMESTAMP = "timestamp"
@@ -53,7 +53,7 @@ class SyncHelper(
.url("$baseUrl/resource/$TABLE_FAVOURITES")
.post(data.toRequestBody())
.build()
val response = httpClient.newCall(request).execute().parseJson()
val response = httpClient.newCall(request).execute().parseJsonOrNull() ?: return
val timestamp = response.getLong(FIELD_TIMESTAMP)
val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES), timestamp)
syncResult.stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
@@ -71,7 +71,7 @@ class SyncHelper(
.url("$baseUrl/resource/$TABLE_HISTORY")
.post(data.toRequestBody())
.build()
val response = httpClient.newCall(request).execute().parseJson()
val response = httpClient.newCall(request).execute().parseJsonOrNull() ?: return
val result = upsertHistory(
json = response.getJSONArray(TABLE_HISTORY),
timestamp = response.getLong(FIELD_TIMESTAMP),

View File

@@ -9,7 +9,7 @@ import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
class SyncAuthenticator(private val context: Context) : AbstractAccountAuthenticator(context) {
class SyncAccountAuthenticator(private val context: Context) : AbstractAccountAuthenticator(context) {
override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?): Bundle? = null
@@ -52,7 +52,6 @@ class SyncAuthenticator(private val context: Context) : AbstractAccountAuthentic
} else {
val intent = Intent(context, SyncAuthActivity::class.java)
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
// intent.putExtra(SyncAuthActivity.EXTRA_TOKEN_TYPE, authTokenType)
val bundle = Bundle()
if (options != null) {
bundle.putAll(options)

View File

@@ -6,11 +6,11 @@ import android.os.IBinder
class SyncAuthenticatorService : Service() {
private lateinit var authenticator: SyncAuthenticator
private lateinit var authenticator: SyncAccountAuthenticator
override fun onCreate() {
super.onCreate()
authenticator = SyncAuthenticator(this)
authenticator = SyncAccountAuthenticator(this)
}
override fun onBind(intent: Intent?): IBinder? {