Update sync helper

This commit is contained in:
Koitharu
2022-04-29 16:12:15 +03:00
parent 837fb91133
commit 6ea98fa056
3 changed files with 110 additions and 99 deletions

View File

@@ -1,10 +1,8 @@
package org.koitharu.kotatsu.sync.domain package org.koitharu.kotatsu.sync.domain
import android.accounts.Account import android.accounts.Account
import android.content.ContentProviderClient import android.content.*
import android.content.ContentProviderOperation import android.database.Cursor
import android.content.Context
import android.content.SyncResult
import android.net.Uri import android.net.Uri
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import okhttp3.OkHttpClient 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_HISTORY = "org.koitharu.kotatsu.history"
private const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites" private const val AUTHORITY_FAVOURITES = "org.koitharu.kotatsu.favourites"
private const val FIELD_TIMESTAMP = "timestamp"
/** /**
* Warning! This class may be used in another process * Warning! This class may be used in another process
*/ */
class SyncRepository( @WorkerThread
class SyncHelper(
context: Context, context: Context,
account: Account, account: Account,
private val provider: ContentProviderClient, private val provider: ContentProviderClient,
@@ -41,116 +43,122 @@ class SyncRepository(
.build() .build()
private val baseUrl = context.getString(R.string.url_sync_server) 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<ContentProviderOperation>()
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) { fun syncFavourites(syncResult: SyncResult) {
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES)
val data = JSONObject() val data = JSONObject()
provider.query(uri, null, null, null, null)?.use { cursor -> data.put(TABLE_FAVOURITE_CATEGORIES, getFavouriteCategories())
val jsonArray = JSONArray() data.put(TABLE_FAVOURITES, getFavourites())
if (cursor.moveToFirst()) { data.put(FIELD_TIMESTAMP, System.currentTimeMillis())
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())
val request = Request.Builder() val request = Request.Builder()
.url("$baseUrl/resource/$TABLE_FAVOURITES") .url("$baseUrl/resource/$TABLE_FAVOURITES")
.post(data.toRequestBody()) .post(data.toRequestBody())
.build() .build()
val response = httpClient.newCall(request).execute().parseJson() val response = httpClient.newCall(request).execute().parseJson()
val operations = ArrayList<ContentProviderOperation>() val timestamp = response.getLong(FIELD_TIMESTAMP)
val timestamp = response.getLong("timestamp") val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES), timestamp)
operations += ContentProviderOperation.newDelete(uri) syncResult.stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
.withSelection("created_at < ?", arrayOf(timestamp.toString())) syncResult.stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
.build() val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp)
val ja = response.getJSONArray(TABLE_FAVOURITES) syncResult.stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
ja.mapJSONTo(operations) { jo -> syncResult.stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
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 syncHistory(syncResult: SyncResult) { fun syncHistory(syncResult: SyncResult) {
val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY)
val data = JSONObject() val data = JSONObject()
provider.query(uri, null, null, null, null)?.use { cursor -> data.put(TABLE_HISTORY, getHistory())
val jsonArray = JSONArray() data.put(FIELD_TIMESTAMP, System.currentTimeMillis())
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())
val request = Request.Builder() val request = Request.Builder()
.url("$baseUrl/resource/$TABLE_HISTORY") .url("$baseUrl/resource/$TABLE_HISTORY")
.post(data.toRequestBody()) .post(data.toRequestBody())
.build() .build()
val response = httpClient.newCall(request).execute().parseJson() 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<ContentProviderResult> {
val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY)
val operations = ArrayList<ContentProviderOperation>() val operations = ArrayList<ContentProviderOperation>()
val timestamp = response.getLong("timestamp")
operations += ContentProviderOperation.newDelete(uri) operations += ContentProviderOperation.newDelete(uri)
.withSelection("updated_at < ?", arrayOf(timestamp.toString())) .withSelection("updated_at < ?", arrayOf(timestamp.toString()))
.build() .build()
val ja = response.getJSONArray(TABLE_HISTORY) json.mapJSONTo(operations) { jo ->
ja.mapJSONTo(operations) { jo ->
ContentProviderOperation.newInsert(uri) ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues()) .withValues(jo.toContentValues())
.build() .build()
} }
return provider.applyBatch(operations)
}
val result = provider.applyBatch(operations) private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)
syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L } val operations = ArrayList<ContentProviderOperation>()
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<ContentProviderResult> {
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES)
val operations = ArrayList<ContentProviderOperation>()
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 { private fun getManga(authority: String, id: Long): JSONObject {
@@ -202,5 +210,11 @@ class SyncRepository(
return requireNotNull(tag) 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") private fun uri(authority: String, table: String) = Uri.parse("content://$authority/$table")
} }

View File

@@ -6,7 +6,7 @@ import android.content.ContentProviderClient
import android.content.Context import android.content.Context
import android.content.SyncResult import android.content.SyncResult
import android.os.Bundle 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 import org.koitharu.kotatsu.utils.ext.onError
class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) { class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {
@@ -18,11 +18,9 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
provider: ContentProviderClient, provider: ContentProviderClient,
syncResult: SyncResult, syncResult: SyncResult,
) { ) {
// Debug.waitForDebugger() val syncHelper = SyncHelper(context, account, provider)
val repository = SyncRepository(context, account, provider)
runCatching { runCatching {
repository.syncFavouriteCategories(syncResult) syncHelper.syncFavourites(syncResult)
repository.syncFavourites(syncResult)
}.onFailure(syncResult::onError) }.onFailure(syncResult::onError)
} }
} }

View File

@@ -6,7 +6,7 @@ import android.content.ContentProviderClient
import android.content.Context import android.content.Context
import android.content.SyncResult import android.content.SyncResult
import android.os.Bundle 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 import org.koitharu.kotatsu.utils.ext.onError
class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) { class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, true) {
@@ -18,10 +18,9 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
provider: ContentProviderClient, provider: ContentProviderClient,
syncResult: SyncResult, syncResult: SyncResult,
) { ) {
// Debug.waitForDebugger() val syncHelper = SyncHelper(context, account, provider)
val repository = SyncRepository(context, account, provider)
runCatching { runCatching {
repository.syncHistory(syncResult) syncHelper.syncHistory(syncResult)
}.onFailure(syncResult::onError) }.onFailure(syncResult::onError)
} }
} }