Fix synchronization

pull/340/head
Koitharu 3 years ago
parent ffd31dbea9
commit 85710acb3a
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 33
versionCode 534 versionCode 535
versionName '5.0-b1' versionName '5.0-b2'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="account_type_sync" translatable="false">org.kotatsu.debug.sync</string> <string name="account_type_sync" translatable="false">org.kotatsu.debug.sync</string>
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.debug.history</string>
<string name="sync_authority_favourites" translatable="false">org.koitharu.kotatsu.debug.favourites</string>
</resources> </resources>

@ -227,13 +227,13 @@
</provider> </provider>
<provider <provider
android:name="org.koitharu.kotatsu.sync.ui.favourites.FavouritesSyncProvider" android:name="org.koitharu.kotatsu.sync.ui.favourites.FavouritesSyncProvider"
android:authorities="${applicationId}.favourites" android:authorities="@string/sync_authority_favourites"
android:exported="false" android:exported="false"
android:label="@string/favourites" android:label="@string/favourites"
android:syncable="true" /> android:syncable="true" />
<provider <provider
android:name="org.koitharu.kotatsu.sync.ui.history.HistorySyncProvider" android:name="org.koitharu.kotatsu.sync.ui.history.HistorySyncProvider"
android:authorities="${applicationId}.history" android:authorities="@string/sync_authority_history"
android:exported="false" android:exported="false"
android:label="@string/history" android:label="@string/history"
android:syncable="true" /> android:syncable="true" />

@ -8,7 +8,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.github.AppUpdateRepository import org.koitharu.kotatsu.core.github.AppUpdateRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -16,7 +15,6 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.prefs.observeAsLiveData import org.koitharu.kotatsu.core.prefs.observeAsLiveData
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData import org.koitharu.kotatsu.utils.asFlowLiveData
@ -27,8 +25,6 @@ class MainViewModel @Inject constructor(
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
private val appUpdateRepository: AppUpdateRepository, private val appUpdateRepository: AppUpdateRepository,
private val trackingRepository: TrackingRepository, private val trackingRepository: TrackingRepository,
syncController: SyncController,
database: MangaDatabase,
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
@ -61,9 +57,6 @@ class MainViewModel @Inject constructor(
launchJob { launchJob {
appUpdateRepository.fetchUpdate() appUpdateRepository.fetchUpdate()
} }
launchJob {
syncController.requestFullSyncAndGc(database)
}
} }
fun openLastReader() { fun openLastReader() {

@ -34,6 +34,7 @@ import org.koitharu.kotatsu.shelf.domain.ShelfContent
import org.koitharu.kotatsu.shelf.domain.ShelfRepository import org.koitharu.kotatsu.shelf.domain.ShelfRepository
import org.koitharu.kotatsu.shelf.domain.ShelfSection import org.koitharu.kotatsu.shelf.domain.ShelfSection
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData import org.koitharu.kotatsu.utils.asFlowLiveData
@ -46,6 +47,7 @@ class ShelfViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
private val trackingRepository: TrackingRepository, private val trackingRepository: TrackingRepository,
private val settings: AppSettings, private val settings: AppSettings,
syncController: SyncController,
networkState: NetworkState, networkState: NetworkState,
) : BaseViewModel(), ListExtraProvider { ) : BaseViewModel(), ListExtraProvider {
@ -63,6 +65,12 @@ class ShelfViewModel @Inject constructor(
emit(listOf(e.toErrorState(canRetry = false))) emit(listOf(e.toErrorState(canRetry = false)))
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
init {
launchJob(Dispatchers.Default) {
syncController.requestFullSync()
}
}
override suspend fun getCounter(mangaId: Long): Int { override suspend fun getCounter(mangaId: Long): Int {
return if (settings.isTrackerEnabled) { return if (settings.isTrackerEnabled) {
trackingRepository.getNewChaptersCount(mangaId) trackingRepository.getNewChaptersCount(mangaId)

@ -3,20 +3,21 @@ package org.koitharu.kotatsu.sync.domain
import android.accounts.Account import android.accounts.Account
import android.accounts.AccountManager import android.accounts.AccountManager
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.collection.ArrayMap
import androidx.room.InvalidationTracker import androidx.room.InvalidationTracker
import androidx.room.withTransaction import androidx.room.withTransaction
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
@ -25,24 +26,21 @@ import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.utils.ext.processLifecycleScope import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class SyncController @Inject constructor( class SyncController @Inject constructor(
@ApplicationContext context: Context, @ApplicationContext context: Context,
private val dbProvider: Provider<MangaDatabase>,
) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) { ) : InvalidationTracker.Observer(arrayOf(TABLE_HISTORY, TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)) {
private val authorityHistory = context.getString(R.string.sync_authority_history)
private val authorityFavourites = context.getString(R.string.sync_authority_favourites)
private val am = AccountManager.get(context) private val am = AccountManager.get(context)
private val accountType = context.getString(R.string.account_type_sync) 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 mutex = Mutex() private val mutex = Mutex()
private val jobs = ArrayMap<String, Job>(2) private val defaultGcPeriod = TimeUnit.DAYS.toMillis(2) // gc period if sync disabled
private val defaultGcPeriod: Long // gc period if sync disabled
get() = TimeUnit.HOURS.toMillis(2)
override fun onInvalidated(tables: Set<String>) { override fun onInvalidated(tables: Set<String>) {
requestSync( requestSync(
@ -57,79 +55,52 @@ class SyncController @Inject constructor(
return rawValue.toLongOrNull() ?: 0L return rawValue.toLongOrNull() ?: 0L
} }
fun setLastSync(account: Account, authority: String, time: Long) { fun observeSyncStatus(): Flow<Boolean> = callbackFlow {
val key = "last_sync_" + authority.substringAfterLast('.') val handle = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE) { which ->
am.setUserData(account, key, time.toString()) trySendBlocking(which and SYNC_OBSERVER_TYPE_ACTIVE != 0)
} }
awaitClose { ContentResolver.removeStatusChangeListener(handle) }
suspend fun requestFullSync() = withContext(Dispatchers.Default) {
requestSyncImpl(favourites = true, history = true, db = null)
} }
suspend fun requestFullSyncAndGc(database: MangaDatabase) = withContext(Dispatchers.Default) { suspend fun requestFullSync() = withContext(Dispatchers.Default) {
requestSyncImpl(favourites = true, history = true, db = database) requestSyncImpl(favourites = true, history = true)
} }
private fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) { private fun requestSync(favourites: Boolean, history: Boolean) = processLifecycleScope.launch(Dispatchers.Default) {
requestSyncImpl(favourites = favourites, history = history, db = null) requestSyncImpl(favourites = favourites, history = history)
} }
private suspend fun requestSyncImpl(favourites: Boolean, history: Boolean, db: MangaDatabase?) = mutex.withLock { private suspend fun requestSyncImpl(favourites: Boolean, history: Boolean) = mutex.withLock {
if (!favourites && !history) { if (!favourites && !history) {
return return
} }
val db = dbProvider.get()
val account = peekAccount() val account = peekAccount()
if (account == null || !ContentResolver.getMasterSyncAutomatically()) { if (account == null || !ContentResolver.getMasterSyncAutomatically()) {
db?.gc(favourites, history) db.gc(favourites, history)
return return
} }
var gcHistory = false var gcHistory = false
var gcFavourites = false var gcFavourites = false
if (favourites) { if (favourites) {
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_FAVOURITES)) { if (ContentResolver.getSyncAutomatically(account, authorityFavourites)) {
scheduleSync(account, AUTHORITY_FAVOURITES) ContentResolver.requestSync(account, authorityFavourites, Bundle.EMPTY)
} else { } else {
gcFavourites = true gcFavourites = true
} }
} }
if (history) { if (history) {
if (ContentResolver.getSyncAutomatically(account, AUTHORITY_HISTORY)) { if (ContentResolver.getSyncAutomatically(account, authorityHistory)) {
scheduleSync(account, AUTHORITY_HISTORY) ContentResolver.requestSync(account, authorityHistory, Bundle.EMPTY)
} else { } else {
gcHistory = true gcHistory = true
} }
} }
if (db != null && (gcHistory || gcFavourites)) { if (gcHistory || gcFavourites) {
db.gc(gcFavourites, gcHistory) db.gc(gcFavourites, gcHistory)
} }
} }
private fun scheduleSync(account: Account, authority: String) {
if (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)
}
}
}
}
private fun peekAccount(): Account? { private fun peekAccount(): Account? {
return am.getAccountsByType(accountType).firstOrNull() return am.getAccountsByType(accountType).firstOrNull()
} }
@ -144,4 +115,14 @@ class SyncController @Inject constructor(
favouriteCategoriesDao.gc(deletedAt) favouriteCategoriesDao.gc(deletedAt)
} }
} }
companion object {
@JvmStatic
fun setLastSync(context: Context, account: Account, authority: String, time: Long) {
val key = "last_sync_" + authority.substringAfterLast('.')
val am = AccountManager.get(context)
am.setUserData(account, key, time.toString())
}
}
} }

@ -1,7 +1,12 @@
package org.koitharu.kotatsu.sync.domain package org.koitharu.kotatsu.sync.domain
import android.accounts.Account import android.accounts.Account
import android.content.* import android.content.ContentProviderClient
import android.content.ContentProviderOperation
import android.content.ContentProviderResult
import android.content.Context
import android.content.OperationApplicationException
import android.content.SyncResult
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
@ -11,7 +16,12 @@ import okhttp3.Request
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.* 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.core.db.TABLE_MANGA
import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS
import org.koitharu.kotatsu.core.db.TABLE_TAGS
import org.koitharu.kotatsu.parsers.util.json.mapJSONTo import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
import org.koitharu.kotatsu.sync.data.SyncAuthApi import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.data.SyncAuthenticator import org.koitharu.kotatsu.sync.data.SyncAuthenticator
@ -23,9 +33,6 @@ import org.koitharu.kotatsu.utils.ext.toJson
import org.koitharu.kotatsu.utils.ext.toRequestBody import org.koitharu.kotatsu.utils.ext.toRequestBody
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
const val AUTHORITY_HISTORY = "org.koitharu.kotatsu.history"
const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
private const val FIELD_TIMESTAMP = "timestamp" private const val FIELD_TIMESTAMP = "timestamp"
/** /**
@ -38,6 +45,8 @@ class SyncHelper(
private val provider: ContentProviderClient, private val provider: ContentProviderClient,
) { ) {
private val authorityHistory = context.getString(R.string.sync_authority_history)
private val authorityFavourites = context.getString(R.string.sync_authority_favourites)
private val httpClient = OkHttpClient.Builder() private val httpClient = OkHttpClient.Builder()
.authenticator(SyncAuthenticator(context, account, SyncAuthApi(context, OkHttpClient()))) .authenticator(SyncAuthenticator(context, account, SyncAuthApi(context, OkHttpClient())))
.addInterceptor(SyncInterceptor(context, account)) .addInterceptor(SyncInterceptor(context, account))
@ -86,13 +95,13 @@ class SyncHelper(
} }
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> { private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY) val uri = uri(authorityHistory, TABLE_HISTORY)
val operations = ArrayList<ContentProviderOperation>() val operations = ArrayList<ContentProviderOperation>()
operations += ContentProviderOperation.newDelete(uri) operations += ContentProviderOperation.newDelete(uri)
.withSelection("updated_at < ?", arrayOf(timestamp.toString())) .withSelection("updated_at < ?", arrayOf(timestamp.toString()))
.build() .build()
json.mapJSONTo(operations) { jo -> json.mapJSONTo(operations) { jo ->
operations.addAll(upsertManga(jo.removeJSONObject("manga"), AUTHORITY_HISTORY)) operations.addAll(upsertManga(jo.removeJSONObject("manga"), authorityHistory))
ContentProviderOperation.newInsert(uri) ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues()) .withValues(jo.toContentValues())
.build() .build()
@ -101,7 +110,7 @@ class SyncHelper(
} }
private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> { private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES) val uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES)
val operations = ArrayList<ContentProviderOperation>() val operations = ArrayList<ContentProviderOperation>()
operations += ContentProviderOperation.newDelete(uri) operations += ContentProviderOperation.newDelete(uri)
.withSelection("created_at < ?", arrayOf(timestamp.toString())) .withSelection("created_at < ?", arrayOf(timestamp.toString()))
@ -115,13 +124,13 @@ class SyncHelper(
} }
private fun upsertFavourites(json: JSONArray, timestamp: Long): Array<ContentProviderResult> { private fun upsertFavourites(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES) val uri = uri(authorityFavourites, TABLE_FAVOURITES)
val operations = ArrayList<ContentProviderOperation>() val operations = ArrayList<ContentProviderOperation>()
operations += ContentProviderOperation.newDelete(uri) operations += ContentProviderOperation.newDelete(uri)
.withSelection("created_at < ?", arrayOf(timestamp.toString())) .withSelection("created_at < ?", arrayOf(timestamp.toString()))
.build() .build()
json.mapJSONTo(operations) { jo -> json.mapJSONTo(operations) { jo ->
operations.addAll(upsertManga(jo.removeJSONObject("manga"), AUTHORITY_FAVOURITES)) operations.addAll(upsertManga(jo.removeJSONObject("manga"), authorityFavourites))
ContentProviderOperation.newInsert(uri) ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues()) .withValues(jo.toContentValues())
.build() .build()
@ -142,25 +151,25 @@ class SyncHelper(
contentValuesOf( contentValuesOf(
"manga_id" to json.getLong("manga_id"), "manga_id" to json.getLong("manga_id"),
"tag_id" to tag.getLong("tag_id"), "tag_id" to tag.getLong("tag_id"),
) ),
).build() ).build()
} }
result.add( result.add(
0, 0,
ContentProviderOperation.newInsert(uri(authority, TABLE_MANGA)) ContentProviderOperation.newInsert(uri(authority, TABLE_MANGA))
.withValues(json.toContentValues()) .withValues(json.toContentValues())
.build() .build(),
) )
return result return result
} }
private fun getHistory(): JSONArray { private fun getHistory(): JSONArray {
return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor -> return provider.query(authorityHistory, TABLE_HISTORY).use { cursor ->
val json = JSONArray() val json = JSONArray()
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
val jo = cursor.toJson() val jo = cursor.toJson()
jo.put("manga", getManga(AUTHORITY_HISTORY, jo.getLong("manga_id"))) jo.put("manga", getManga(authorityHistory, jo.getLong("manga_id")))
json.put(jo) json.put(jo)
} while (cursor.moveToNext()) } while (cursor.moveToNext())
} }
@ -169,12 +178,12 @@ class SyncHelper(
} }
private fun getFavourites(): JSONArray { private fun getFavourites(): JSONArray {
return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITES).use { cursor -> return provider.query(authorityFavourites, TABLE_FAVOURITES).use { cursor ->
val json = JSONArray() val json = JSONArray()
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
val jo = cursor.toJson() val jo = cursor.toJson()
jo.put("manga", getManga(AUTHORITY_FAVOURITES, jo.getLong("manga_id"))) jo.put("manga", getManga(authorityFavourites, jo.getLong("manga_id")))
json.put(jo) json.put(jo)
} while (cursor.moveToNext()) } while (cursor.moveToNext())
} }
@ -183,7 +192,7 @@ class SyncHelper(
} }
private fun getFavouriteCategories(): JSONArray { private fun getFavouriteCategories(): JSONArray {
return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES).use { cursor -> return provider.query(authorityFavourites, TABLE_FAVOURITE_CATEGORIES).use { cursor ->
val json = JSONArray() val json = JSONArray()
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
@ -247,15 +256,15 @@ class SyncHelper(
val deletedAt = System.currentTimeMillis() - defaultGcPeriod val deletedAt = System.currentTimeMillis() - defaultGcPeriod
val selection = "deleted_at != 0 AND deleted_at < ?" val selection = "deleted_at != 0 AND deleted_at < ?"
val args = arrayOf(deletedAt.toString()) val args = arrayOf(deletedAt.toString())
provider.delete(uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES), selection, args) provider.delete(uri(authorityFavourites, TABLE_FAVOURITES), selection, args)
provider.delete(uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES), selection, args) provider.delete(uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES), selection, args)
} }
private fun gcHistory() { private fun gcHistory() {
val deletedAt = System.currentTimeMillis() - defaultGcPeriod val deletedAt = System.currentTimeMillis() - defaultGcPeriod
val selection = "deleted_at != 0 AND deleted_at < ?" val selection = "deleted_at != 0 AND deleted_at < ?"
val args = arrayOf(deletedAt.toString()) val args = arrayOf(deletedAt.toString())
provider.delete(uri(AUTHORITY_HISTORY, TABLE_HISTORY), selection, args) provider.delete(uri(authorityHistory, TABLE_HISTORY), selection, args)
} }
private fun ContentProviderClient.query(authority: String, table: String): Cursor { private fun ContentProviderClient.query(authority: String, table: String): Cursor {

@ -27,7 +27,7 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
val syncHelper = SyncHelper(context, account, provider) val syncHelper = SyncHelper(context, account, provider)
runCatchingCancellable { runCatchingCancellable {
syncHelper.syncFavourites(syncResult) syncHelper.syncFavourites(syncResult)
SyncController(context).setLastSync(account, authority, System.currentTimeMillis()) SyncController.setLastSync(context, account, authority, System.currentTimeMillis())
}.onFailure(syncResult::onError) }.onFailure(syncResult::onError)
} }
} }

@ -27,7 +27,7 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
val syncHelper = SyncHelper(context, account, provider) val syncHelper = SyncHelper(context, account, provider)
runCatchingCancellable { runCatchingCancellable {
syncHelper.syncHistory(syncResult) syncHelper.syncHistory(syncResult)
SyncController(context).setLastSync(account, authority, System.currentTimeMillis()) SyncController.setLastSync(context, account, authority, System.currentTimeMillis())
}.onFailure(syncResult::onError) }.onFailure(syncResult::onError)
} }
} }

@ -15,6 +15,8 @@
<string name="mal_clientId" translatable="false">6cd8e6349e9a36bc1fc1ab97703c9fd1</string> <string name="mal_clientId" translatable="false">6cd8e6349e9a36bc1fc1ab97703c9fd1</string>
<string name="acra_login" translatable="false">SxhkCVnqVLbGogvi</string> <string name="acra_login" translatable="false">SxhkCVnqVLbGogvi</string>
<string name="acra_password" translatable="false">xPDACTLHnHU9Nfjv</string> <string name="acra_password" translatable="false">xPDACTLHnHU9Nfjv</string>
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.history</string>
<string name="sync_authority_favourites" translatable="false">org.koitharu.kotatsu.favourites</string>
<string-array name="values_theme" translatable="false"> <string-array name="values_theme" translatable="false">
<item>-1</item> <item>-1</item>
<item>1</item> <item>1</item>

@ -3,5 +3,5 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:accountPreferences="@xml/pref_sync" android:accountPreferences="@xml/pref_sync"
android:accountType="@string/account_type_sync" android:accountType="@string/account_type_sync"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name" /> android:label="@string/app_name" />

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type_sync" android:accountType="@string/account_type_sync"
android:allowParallelSyncs="false" android:allowParallelSyncs="false"
android:contentAuthority="${applicationId}.favourites" android:contentAuthority="@string/sync_authority_favourites"
android:isAlwaysSyncable="true" android:isAlwaysSyncable="true"
android:supportsUploading="true" android:supportsUploading="true"
android:userVisible="true" /> android:userVisible="true" />

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type_sync" android:accountType="@string/account_type_sync"
android:allowParallelSyncs="false" android:allowParallelSyncs="false"
android:contentAuthority="${applicationId}.history" android:contentAuthority="@string/sync_authority_history"
android:isAlwaysSyncable="true" android:isAlwaysSyncable="true"
android:supportsUploading="true" android:supportsUploading="true"
android:userVisible="true" /> android:userVisible="true" />

Loading…
Cancel
Save