This commit is contained in:
Koitharu
2022-05-14 13:43:59 +03:00
parent 32836d05d8
commit 1be8760c00
7 changed files with 79 additions and 14 deletions

View File

@@ -312,6 +312,7 @@ class AppSettings(context: Context) {
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
const val KEY_DOH = "doh"
const val KEY_SYNC = "sync"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -1,7 +1,9 @@
package org.koitharu.kotatsu.settings
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.provider.Settings
import android.view.View
import androidx.preference.ListPreference
import androidx.preference.Preference
@@ -76,6 +78,10 @@ class ContentSettingsFragment :
AppSettings.KEY_SOURCES_HIDDEN -> {
bindRemoteSourcesSummary()
}
AppSettings.KEY_SYNC -> {
val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
startActivity(intent)
}
}
}

View File

@@ -5,6 +5,7 @@ import android.content.*
import android.database.Cursor
import android.net.Uri
import androidx.annotation.WorkerThread
import androidx.core.content.contentValuesOf
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
@@ -13,9 +14,9 @@ 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.sync.data.SyncAuthApi
import org.koitharu.kotatsu.utils.GZipInterceptor
import org.koitharu.kotatsu.utils.ext.toContentValues
import org.koitharu.kotatsu.utils.ext.toJson
@@ -86,6 +87,7 @@ class SyncHelper(
.withSelection("updated_at < ?", arrayOf(timestamp.toString()))
.build()
json.mapJSONTo(operations) { jo ->
operations.addAll(upsertManga(jo.removeJSONObject("manga"), AUTHORITY_HISTORY))
ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues())
.build()
@@ -114,6 +116,7 @@ class SyncHelper(
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
.build()
json.mapJSONTo(operations) { jo ->
operations.addAll(upsertManga(jo.removeJSONObject("manga"), AUTHORITY_FAVOURITES))
ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues())
.build()
@@ -121,6 +124,31 @@ class SyncHelper(
return provider.applyBatch(operations)
}
private fun upsertManga(json: JSONObject, authority: String): List<ContentProviderOperation> {
val tags = json.removeJSONArray(TABLE_TAGS)
val result = ArrayList<ContentProviderOperation>(tags.length() * 2 + 1)
for (i in 0 until tags.length()) {
val tag = tags.getJSONObject(i)
result += ContentProviderOperation.newInsert(uri(authority, TABLE_TAGS))
.withValues(tag.toContentValues())
.build()
result += ContentProviderOperation.newInsert(uri(authority, TABLE_MANGA_TAGS))
.withValues(
contentValuesOf(
"manga_id" to json.getLong("manga_id"),
"tag_id" to tag.getLong("tag_id"),
)
).build()
}
result.add(
0,
ContentProviderOperation.newInsert(uri(authority, TABLE_MANGA))
.withValues(json.toContentValues())
.build()
)
return result
}
private fun getHistory(): JSONArray {
return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor ->
val json = JSONArray()
@@ -217,4 +245,8 @@ class SyncHelper(
}
private fun uri(authority: String, table: String) = Uri.parse("content://$authority/$table")
private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject
private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray
}

View File

@@ -7,10 +7,11 @@ import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import java.util.concurrent.Callable
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.core.db.*
import java.util.concurrent.Callable
abstract class SyncProvider : ContentProvider() {
@@ -55,15 +56,14 @@ abstract class SyncProvider : ContentProvider() {
return null
}
val db = database.openHelper.writableDatabase
db.insert(table, SQLiteDatabase.CONFLICT_REPLACE, values)
return null
if (db.insert(table, SQLiteDatabase.CONFLICT_IGNORE, values) < 0) {
db.update(table, values)
}
return uri
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
val table = getTableName(uri)
if (table == null) {
return 0
}
val table = getTableName(uri) ?: return 0
return database.openHelper.writableDatabase.delete(table, selection, selectionArgs)
}
@@ -77,14 +77,35 @@ abstract class SyncProvider : ContentProvider() {
}
override fun applyBatch(operations: ArrayList<ContentProviderOperation>): Array<ContentProviderResult> {
return database.runInTransaction(Callable { super.applyBatch(operations) })
return runAtomicTransaction { super.applyBatch(operations) }
}
override fun bulkInsert(uri: Uri, values: Array<out ContentValues>): Int {
return database.runInTransaction(Callable { super.bulkInsert(uri, values) })
return runAtomicTransaction { super.bulkInsert(uri, values) }
}
private fun getTableName(uri: Uri): String? {
return uri.pathSegments.singleOrNull()?.takeIf { it in supportedTables }
}
private fun <R> runAtomicTransaction(callable: Callable<R>): R {
return synchronized(database) {
database.runInTransaction(callable)
}
}
private fun SupportSQLiteDatabase.update(table: String, values: ContentValues) {
val keys = when (table) {
TABLE_TAGS -> listOf("tag_id")
TABLE_MANGA_TAGS -> listOf("tag_id", "manga_id")
TABLE_MANGA -> listOf("manga_id")
TABLE_FAVOURITES -> listOf("manga_id", "category_id")
TABLE_FAVOURITE_CATEGORIES -> listOf("category_id")
TABLE_HISTORY -> listOf("manga_id")
else -> throw IllegalArgumentException("Update for $table is not supported")
}
val whereClause = keys.joinToString(" AND ") { "`$it` = ?" }
val whereArgs = Array<Any>(keys.size) { i -> values.get("`${keys[i]}`") ?: values.get(keys[i]) }
this.update(table, SQLiteDatabase.CONFLICT_IGNORE, values, whereClause, whereArgs)
}
}

View File

@@ -24,7 +24,7 @@ fun JSONObject.toContentValues(): ContentValues {
for (key in keys()) {
val name = key.escapeName()
when (val value = get(key)) {
null -> cv.putNull(name)
JSONObject.NULL, "null", null -> cv.putNull(name)
is String -> cv.put(name, value)
is Float -> cv.put(name, value)
is Double -> cv.put(name, value)

View File

@@ -5,7 +5,7 @@
<string name="url_forpda" translatable="false">https://4pda.to/forum/index.php?showtopic=697669</string>
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
<string name="url_sync_server" translatable="false">http://192.168.0.113:8080</string>
<string name="url_sync_server" translatable="false">http://95.216.215.49:8080</string>
<string-array name="values_theme" translatable="false">
<item>-1</item>
<item>1</item>

View File

@@ -40,9 +40,14 @@
android:valueTo="5"
app:defaultValue="2" />
<Preference
android:key="sync"
android:persistent="false"
android:summary="@string/sync_title"
android:title="@string/sync" />
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment"
android:title="@string/backup_restore"
app:allowDividerAbove="true" />
android:title="@string/backup_restore" />
</PreferenceScreen>