pull/163/head
Koitharu 4 years ago
parent 32836d05d8
commit 1be8760c00
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

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

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

@ -5,6 +5,7 @@ import android.content.*
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.content.contentValuesOf
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONArray import org.json.JSONArray
@ -13,9 +14,9 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.* import org.koitharu.kotatsu.core.db.*
import org.koitharu.kotatsu.parsers.util.json.mapJSONTo import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
import org.koitharu.kotatsu.parsers.util.parseJson 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.SyncAuthenticator
import org.koitharu.kotatsu.sync.data.SyncInterceptor 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.GZipInterceptor
import org.koitharu.kotatsu.utils.ext.toContentValues import org.koitharu.kotatsu.utils.ext.toContentValues
import org.koitharu.kotatsu.utils.ext.toJson import org.koitharu.kotatsu.utils.ext.toJson
@ -86,6 +87,7 @@ class SyncHelper(
.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))
ContentProviderOperation.newInsert(uri) ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues()) .withValues(jo.toContentValues())
.build() .build()
@ -114,6 +116,7 @@ class SyncHelper(
.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))
ContentProviderOperation.newInsert(uri) ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues()) .withValues(jo.toContentValues())
.build() .build()
@ -121,6 +124,31 @@ class SyncHelper(
return provider.applyBatch(operations) 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 { private fun getHistory(): JSONArray {
return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor -> return provider.query(AUTHORITY_HISTORY, TABLE_HISTORY).use { cursor ->
val json = JSONArray() val json = JSONArray()
@ -217,4 +245,8 @@ class SyncHelper(
} }
private fun uri(authority: String, table: String) = Uri.parse("content://$authority/$table") 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
} }

@ -7,10 +7,11 @@ import android.content.ContentValues
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.net.Uri import android.net.Uri
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteQueryBuilder import androidx.sqlite.db.SupportSQLiteQueryBuilder
import java.util.concurrent.Callable
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.core.db.* import org.koitharu.kotatsu.core.db.*
import java.util.concurrent.Callable
abstract class SyncProvider : ContentProvider() { abstract class SyncProvider : ContentProvider() {
@ -55,15 +56,14 @@ abstract class SyncProvider : ContentProvider() {
return null return null
} }
val db = database.openHelper.writableDatabase val db = database.openHelper.writableDatabase
db.insert(table, SQLiteDatabase.CONFLICT_REPLACE, values) if (db.insert(table, SQLiteDatabase.CONFLICT_IGNORE, values) < 0) {
return null db.update(table, values)
}
return uri
} }
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
val table = getTableName(uri) val table = getTableName(uri) ?: return 0
if (table == null) {
return 0
}
return database.openHelper.writableDatabase.delete(table, selection, selectionArgs) return database.openHelper.writableDatabase.delete(table, selection, selectionArgs)
} }
@ -77,14 +77,35 @@ abstract class SyncProvider : ContentProvider() {
} }
override fun applyBatch(operations: ArrayList<ContentProviderOperation>): Array<ContentProviderResult> { 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 { 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? { private fun getTableName(uri: Uri): String? {
return uri.pathSegments.singleOrNull()?.takeIf { it in supportedTables } 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)
}
} }

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

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

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