|
|
|
@ -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 syncFavourites(syncResult: SyncResult) {
|
|
|
|
fun syncFavouriteCategories(syncResult: SyncResult) {
|
|
|
|
|
|
|
|
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)
|
|
|
|
|
|
|
|
val data = JSONObject()
|
|
|
|
val data = JSONObject()
|
|
|
|
provider.query(uri, null, null, null, null)?.use { cursor ->
|
|
|
|
data.put(TABLE_FAVOURITE_CATEGORIES, getFavouriteCategories())
|
|
|
|
val favourites = JSONArray()
|
|
|
|
data.put(TABLE_FAVOURITES, getFavourites())
|
|
|
|
if (cursor.moveToFirst()) {
|
|
|
|
data.put(FIELD_TIMESTAMP, System.currentTimeMillis())
|
|
|
|
do {
|
|
|
|
val request = Request.Builder()
|
|
|
|
favourites.put(cursor.toJson())
|
|
|
|
.url("$baseUrl/resource/$TABLE_FAVOURITES")
|
|
|
|
} while (cursor.moveToNext())
|
|
|
|
.post(data.toRequestBody())
|
|
|
|
}
|
|
|
|
.build()
|
|
|
|
data.put(TABLE_FAVOURITES, favourites)
|
|
|
|
val response = httpClient.newCall(request).execute().parseJson()
|
|
|
|
|
|
|
|
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 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.put("timestamp", System.currentTimeMillis())
|
|
|
|
|
|
|
|
|
|
|
|
fun syncHistory(syncResult: SyncResult) {
|
|
|
|
|
|
|
|
val data = JSONObject()
|
|
|
|
|
|
|
|
data.put(TABLE_HISTORY, getHistory())
|
|
|
|
|
|
|
|
data.put(FIELD_TIMESTAMP, System.currentTimeMillis())
|
|
|
|
val request = Request.Builder()
|
|
|
|
val request = Request.Builder()
|
|
|
|
.url("$baseUrl/resource/$TABLE_FAVOURITE_CATEGORIES")
|
|
|
|
.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("created_at < ?", arrayOf(timestamp.toString()))
|
|
|
|
.withSelection("updated_at < ?", arrayOf(timestamp.toString()))
|
|
|
|
.build()
|
|
|
|
.build()
|
|
|
|
val ja = response.getJSONArray(TABLE_FAVOURITE_CATEGORIES)
|
|
|
|
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)
|
|
|
|
|
|
|
|
syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L
|
|
|
|
|
|
|
|
syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread
|
|
|
|
private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
|
|
|
fun syncFavourites(syncResult: SyncResult) {
|
|
|
|
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITE_CATEGORIES)
|
|
|
|
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES)
|
|
|
|
val operations = ArrayList<ContentProviderOperation>()
|
|
|
|
val data = JSONObject()
|
|
|
|
operations += ContentProviderOperation.newDelete(uri)
|
|
|
|
provider.query(uri, null, null, null, null)?.use { cursor ->
|
|
|
|
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
|
|
|
|
val jsonArray = JSONArray()
|
|
|
|
.build()
|
|
|
|
if (cursor.moveToFirst()) {
|
|
|
|
json.mapJSONTo(operations) { jo ->
|
|
|
|
do {
|
|
|
|
ContentProviderOperation.newInsert(uri)
|
|
|
|
val jo = cursor.toJson()
|
|
|
|
.withValues(jo.toContentValues())
|
|
|
|
jo.put("manga", getManga(AUTHORITY_FAVOURITES, jo.getLong("manga_id")))
|
|
|
|
.build()
|
|
|
|
jsonArray.put(jo)
|
|
|
|
|
|
|
|
} while (cursor.moveToNext())
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.put(TABLE_FAVOURITES, jsonArray)
|
|
|
|
return provider.applyBatch(operations)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.put("timestamp", System.currentTimeMillis())
|
|
|
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
private fun upsertFavourites(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
|
|
|
|
.url("$baseUrl/resource/$TABLE_FAVOURITES")
|
|
|
|
val uri = uri(AUTHORITY_FAVOURITES, TABLE_FAVOURITES)
|
|
|
|
.post(data.toRequestBody())
|
|
|
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
val response = httpClient.newCall(request).execute().parseJson()
|
|
|
|
|
|
|
|
val operations = ArrayList<ContentProviderOperation>()
|
|
|
|
val operations = ArrayList<ContentProviderOperation>()
|
|
|
|
val timestamp = response.getLong("timestamp")
|
|
|
|
|
|
|
|
operations += ContentProviderOperation.newDelete(uri)
|
|
|
|
operations += ContentProviderOperation.newDelete(uri)
|
|
|
|
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
|
|
|
|
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
|
|
|
|
.build()
|
|
|
|
.build()
|
|
|
|
val ja = response.getJSONArray(TABLE_FAVOURITES)
|
|
|
|
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)
|
|
|
|
|
|
|
|
syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L
|
|
|
|
|
|
|
|
syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread
|
|
|
|
private fun getHistory(): JSONArray {
|
|
|
|
fun syncHistory(syncResult: SyncResult) {
|
|
|
|
return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor ->
|
|
|
|
val uri = uri(AUTHORITY_HISTORY, TABLE_HISTORY)
|
|
|
|
val json = JSONArray()
|
|
|
|
val data = JSONObject()
|
|
|
|
|
|
|
|
provider.query(uri, null, null, null, null)?.use { cursor ->
|
|
|
|
|
|
|
|
val jsonArray = 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(AUTHORITY_HISTORY, jo.getLong("manga_id")))
|
|
|
|
jsonArray.put(jo)
|
|
|
|
json.put(jo)
|
|
|
|
} while (cursor.moveToNext())
|
|
|
|
} while (cursor.moveToNext())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.put(TABLE_HISTORY, jsonArray)
|
|
|
|
json
|
|
|
|
}
|
|
|
|
}
|
|
|
|
data.put("timestamp", System.currentTimeMillis())
|
|
|
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
|
|
|
|
.url("$baseUrl/resource/$TABLE_HISTORY")
|
|
|
|
|
|
|
|
.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("updated_at < ?", arrayOf(timestamp.toString()))
|
|
|
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
val ja = response.getJSONArray(TABLE_HISTORY)
|
|
|
|
|
|
|
|
ja.mapJSONTo(operations) { jo ->
|
|
|
|
|
|
|
|
ContentProviderOperation.newInsert(uri)
|
|
|
|
|
|
|
|
.withValues(jo.toContentValues())
|
|
|
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
val result = provider.applyBatch(operations)
|
|
|
|
private fun getFavourites(): JSONArray {
|
|
|
|
syncResult.stats.numDeletes = result.first().count?.toLong() ?: 0L
|
|
|
|
return provider.query(AUTHORITY_FAVOURITES, TABLE_FAVOURITES).use { cursor ->
|
|
|
|
syncResult.stats.numInserts = result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|