diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncRepository.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt similarity index 64% rename from app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncRepository.kt rename to app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt index 8e9303873..114f6d027 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt @@ -1,10 +1,8 @@ package org.koitharu.kotatsu.sync.domain import android.accounts.Account -import android.content.ContentProviderClient -import android.content.ContentProviderOperation -import android.content.Context -import android.content.SyncResult +import android.content.* +import android.database.Cursor import android.net.Uri import androidx.annotation.WorkerThread import okhttp3.OkHttpClient @@ -27,10 +25,14 @@ 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" + +private const val FIELD_TIMESTAMP = "timestamp" + /** * Warning! This class may be used in another process */ -class SyncRepository( +@WorkerThread +class SyncHelper( context: Context, account: Account, private val provider: ContentProviderClient, @@ -41,116 +43,122 @@ class SyncRepository( .build() private val baseUrl = context.getString(R.string.url_sync_server) - @WorkerThread - fun syncFavouriteCategories(syncResult: SyncResult) { - val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES) - val data = JSONObject() - provider.query(uri, null, null, null, null)?.use { cursor -> - val favourites = JSONArray() - if (cursor.moveToFirst()) { - do { - favourites.put(cursor.toJson()) - } while (cursor.moveToNext()) - } - data.put(TABLE_FAVOURITES, favourites) - } - data.put("timestamp", System.currentTimeMillis()) - val request = Request.Builder() - .url("$baseUrl/resource/$TABLE_FAVOURITE_CATEGORIES") - .post(data.toRequestBody()) - .build() - val response = httpClient.newCall(request).execute().parseJson() - val operations = ArrayList() - val timestamp = response.getLong("timestamp") - operations += ContentProviderOperation.newDelete(uri) - .withSelection("created_at < ?", arrayOf(timestamp.toString())) - .build() - val ja = response.getJSONArray(TABLE_FAVOURITE_CATEGORIES) - ja.mapJSONTo(operations) { jo -> - ContentProviderOperation.newInsert(uri) - .withValues(jo.toContentValues()) - .build() - } - - val result = provider.applyBatch(operations) - syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L - syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L } - } - - @WorkerThread fun syncFavourites(syncResult: SyncResult) { - val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES) val data = JSONObject() - provider.query(uri, null, null, null, null)?.use { cursor -> - val jsonArray = JSONArray() - if (cursor.moveToFirst()) { - do { - val jo = cursor.toJson() - jo.put("manga", getManga(AUTHORITY_FAVOURITES, jo.getLong("manga_id"))) - jsonArray.put(jo) - } while (cursor.moveToNext()) - } - data.put(TABLE_FAVOURITES, jsonArray) - } - data.put("timestamp", System.currentTimeMillis()) + data.put(TABLE_FAVOURITE_CATEGORIES, getFavouriteCategories()) + data.put(TABLE_FAVOURITES, getFavourites()) + data.put(FIELD_TIMESTAMP, System.currentTimeMillis()) val request = Request.Builder() .url("$baseUrl/resource/$TABLE_FAVOURITES") .post(data.toRequestBody()) .build() val response = httpClient.newCall(request).execute().parseJson() - val operations = ArrayList() - val timestamp = response.getLong("timestamp") - operations += ContentProviderOperation.newDelete(uri) - .withSelection("created_at < ?", arrayOf(timestamp.toString())) - .build() - val ja = response.getJSONArray(TABLE_FAVOURITES) - ja.mapJSONTo(operations) { jo -> - ContentProviderOperation.newInsert(uri) - .withValues(jo.toContentValues()) - .build() - } - - val result = provider.applyBatch(operations) - syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L - syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L } + val timestamp = response.getLong(FIELD_TIMESTAMP) + val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES), timestamp) + syncResult.stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L + syncResult.stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L } + val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp) + syncResult.stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L + syncResult.stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L } } - @WorkerThread fun syncHistory(syncResult: SyncResult) { - val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY) val data = JSONObject() - provider.query(uri, null, null, null, null)?.use { cursor -> - val jsonArray = JSONArray() - if (cursor.moveToFirst()) { - do { - val jo = cursor.toJson() - jo.put("manga", getManga(AUTHORITY_HISTORY, jo.getLong("manga_id"))) - jsonArray.put(jo) - } while (cursor.moveToNext()) - } - data.put(TABLE_HISTORY, jsonArray) - } - data.put("timestamp", System.currentTimeMillis()) + data.put(TABLE_HISTORY, getHistory()) + data.put(FIELD_TIMESTAMP, System.currentTimeMillis()) val request = Request.Builder() .url("$baseUrl/resource/$TABLE_HISTORY") .post(data.toRequestBody()) .build() val response = httpClient.newCall(request).execute().parseJson() + val result = upsertHistory( + json = response.getJSONArray(TABLE_HISTORY), + timestamp = response.getLong(FIELD_TIMESTAMP), + ) + syncResult.stats.numDeletes += result.first().count?.toLong() ?: 0L + syncResult.stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L } + } + + private fun upsertHistory(json: JSONArray, timestamp: Long): Array { + val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY) val operations = ArrayList() - val timestamp = response.getLong("timestamp") operations += ContentProviderOperation.newDelete(uri) .withSelection("updated_at < ?", arrayOf(timestamp.toString())) .build() - val ja = response.getJSONArray(TABLE_HISTORY) - ja.mapJSONTo(operations) { jo -> + json.mapJSONTo(operations) { jo -> ContentProviderOperation.newInsert(uri) .withValues(jo.toContentValues()) .build() } + return provider.applyBatch(operations) + } - val result = provider.applyBatch(operations) - syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L - syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L } + private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array { + val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES) + val operations = ArrayList() + operations += ContentProviderOperation.newDelete(uri) + .withSelection("created_at < ?", arrayOf(timestamp.toString())) + .build() + json.mapJSONTo(operations) { jo -> + ContentProviderOperation.newInsert(uri) + .withValues(jo.toContentValues()) + .build() + } + return provider.applyBatch(operations) + } + + private fun upsertFavourites(json: JSONArray, timestamp: Long): Array { + val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES) + val operations = ArrayList() + operations += ContentProviderOperation.newDelete(uri) + .withSelection("created_at < ?", arrayOf(timestamp.toString())) + .build() + json.mapJSONTo(operations) { jo -> + ContentProviderOperation.newInsert(uri) + .withValues(jo.toContentValues()) + .build() + } + return provider.applyBatch(operations) + } + + private fun getHistory(): JSONArray { + return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor -> + val json = JSONArray() + if (cursor.moveToFirst()) { + do { + val jo = cursor.toJson() + jo.put("manga", getManga(AUTHORITY_HISTORY, jo.getLong("manga_id"))) + json.put(jo) + } while (cursor.moveToNext()) + } + json + } + } + + private fun getFavourites(): JSONArray { + return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITES).use { cursor -> + val json = JSONArray() + if (cursor.moveToFirst()) { + do { + val jo = cursor.toJson() + jo.put("manga", getManga(AUTHORITY_FAVOURITES, jo.getLong("manga_id"))) + json.put(jo) + } while (cursor.moveToNext()) + } + json + } + } + + private fun getFavouriteCategories(): JSONArray { + return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES).use { cursor -> + val json = JSONArray() + if (cursor.moveToFirst()) { + do { + json.put(cursor.toJson()) + } while (cursor.moveToNext()) + } + json + } } private fun getManga(authority: String, id: Long): JSONObject { @@ -202,5 +210,11 @@ class SyncRepository( return requireNotNull(tag) } + private fun ContentProviderClient.query(authority: String, table: String): Cursor { + val uri = uri(authority, table) + return query(uri, null, null, null, null) + ?: throw OperationApplicationException("Query failed: $uri") + } + private fun uri(authority: String, table: String) = Uri.parse("content://$authority/$table") } \ No newline at end of file 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 b2e9daa9f..2f5b91eb3 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,7 +6,7 @@ import android.content.ContentProviderClient import android.content.Context import android.content.SyncResult import android.os.Bundle -import org.koitharu.kotatsu.sync.domain.SyncRepository +import org.koitharu.kotatsu.sync.domain.SyncHelper import org.koitharu.kotatsu.utils.ext.onError class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) { @@ -18,11 +18,9 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont provider: ContentProviderClient, syncResult: SyncResult, ) { - // Debug.waitForDebugger() - val repository = SyncRepository(context, account, provider) + val syncHelper = SyncHelper(context, account, provider) runCatching { - repository.syncFavouriteCategories(syncResult) - repository.syncFavourites(syncResult) + syncHelper.syncFavourites(syncResult) }.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 f574131b6..e42541509 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,7 +6,7 @@ import android.content.ContentProviderClient import android.content.Context import android.content.SyncResult import android.os.Bundle -import org.koitharu.kotatsu.sync.domain.SyncRepository +import org.koitharu.kotatsu.sync.domain.SyncHelper import org.koitharu.kotatsu.utils.ext.onError class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) { @@ -18,10 +18,9 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context provider: ContentProviderClient, syncResult: SyncResult, ) { - // Debug.waitForDebugger() - val repository = SyncRepository(context, account, provider) + val syncHelper = SyncHelper(context, account, provider) runCatching { - repository.syncHistory(syncResult) + syncHelper.syncHistory(syncResult) }.onFailure(syncResult::onError) } } \ No newline at end of file