From e69dbbf418b24fe7caac95dc5c9a1596b9efdcaf Mon Sep 17 00:00:00 2001 From: Syer10 Date: Sat, 29 May 2021 19:35:56 -0400 Subject: [PATCH 1/3] Working shared preferences (#112) * Working shared preferences * Remove unneeded prefs dir * Todo --- AndroidCompat/build.gradle.kts | 12 ++ .../androidimpl/CustomContext.java | 19 +- .../nulldev/androidcompat/io/AndroidFiles.kt | 2 - .../io/sharedprefs/JavaSharedPreferences.kt | 168 ++++++++++++++++++ build.gradle.kts | 5 + 5 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt diff --git a/AndroidCompat/build.gradle.kts b/AndroidCompat/build.gradle.kts index 20acf2f..fd782bc 100644 --- a/AndroidCompat/build.gradle.kts +++ b/AndroidCompat/build.gradle.kts @@ -1,6 +1,7 @@ plugins { application + kotlin("plugin.serialization") } @@ -46,6 +47,17 @@ dependencies { implementation("org.mozilla:rhino-runtime:1.7.13") // 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn implementation("org.mozilla:rhino-engine:1.7.13") + + // Kotlin wrapper around Java Preferences, makes certain things easier + val multiplatformSettingsVersion = "0.7.7" + implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion") + implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion") +} + +tasks { + withType { + kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") + } } //def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar') diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java index cdbb3ef..617a6f5 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/androidimpl/CustomContext.java @@ -38,7 +38,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; import xyz.nulldev.androidcompat.io.AndroidFiles; -import xyz.nulldev.androidcompat.io.sharedprefs.JsonSharedPreferences; +import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences; import xyz.nulldev.androidcompat.service.ServiceSupport; import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; @@ -165,23 +165,22 @@ public class CustomContext extends Context implements DIAware { /** Fake shared prefs! **/ private Map prefs = new HashMap<>(); //Cache - private File sharedPrefsFileFromString(String s) { - return new File(androidFiles.getPrefsDir(), s + ".json"); - } - @Override public synchronized SharedPreferences getSharedPreferences(String s, int i) { SharedPreferences preferences = prefs.get(s); //Create new shared preferences if one does not exist if(preferences == null) { - preferences = getSharedPreferences(sharedPrefsFileFromString(s), i); + preferences = new JavaSharedPreferences(s); prefs.put(s, preferences); } return preferences; } - public SharedPreferences getSharedPreferences(File file, int mode) { - return new JsonSharedPreferences(file); + @Override + public SharedPreferences getSharedPreferences(@NotNull File file, int mode) { + String path = file.getAbsolutePath().replace('\\', '/'); + int firstSlash = path.indexOf("/"); + return new JavaSharedPreferences(path.substring(firstSlash)); } @Override @@ -191,8 +190,8 @@ public class CustomContext extends Context implements DIAware { @Override public boolean deleteSharedPreferences(String name) { - prefs.remove(name); - return sharedPrefsFileFromString(name).delete(); + JavaSharedPreferences item = (JavaSharedPreferences) prefs.remove(name); + return item.deleteAll(); } @Override diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt index ea55b49..dafa70e 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/AndroidFiles.kt @@ -26,8 +26,6 @@ class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) { val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir) val databasesDir: File get() = registerFile(filesConfig.databasesDir) - val prefsDir: File get() = registerFile(filesConfig.prefsDir) - val packagesDir: File get() = registerFile(filesConfig.packageDir) fun registerFile(file: String): File { diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt new file mode 100644 index 0000000..1c7f3ea --- /dev/null +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt @@ -0,0 +1,168 @@ +package xyz.nulldev.androidcompat.io.sharedprefs + +import android.content.SharedPreferences +import com.russhwolf.settings.ExperimentalSettingsApi +import com.russhwolf.settings.ExperimentalSettingsImplementation +import com.russhwolf.settings.JvmPreferencesSettings +import com.russhwolf.settings.serialization.decodeValue +import com.russhwolf.settings.serialization.encodeValue +import com.russhwolf.settings.set +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.SetSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import java.util.prefs.PreferenceChangeListener +import java.util.prefs.Preferences + +@OptIn(ExperimentalSettingsImplementation::class, ExperimentalSerializationApi::class, ExperimentalSettingsApi::class) +class JavaSharedPreferences(key: String) : SharedPreferences { + private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key") + private val preferences = JvmPreferencesSettings(javaPreferences) + private val listeners = mutableMapOf() + + // TODO: 2021-05-29 Need to find a way to get this working with all pref types + override fun getAll(): MutableMap { + return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap() + } + + override fun getString(key: String, defValue: String?): String? { + return if (defValue != null) { + preferences.getString(key, defValue) + } else { + preferences.getStringOrNull(key) + } + } + + override fun getStringSet(key: String, defValues: MutableSet?): MutableSet? { + try { + return if (defValues != null) { + preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, defValues) + } else { + preferences.decodeValue(SetSerializer(String.serializer()).nullable, key, null) + }?.toMutableSet() + } catch (e: SerializationException) { + throw ClassCastException("$key was not a StringSet") + } + } + + override fun getInt(key: String, defValue: Int): Int { + return preferences.getInt(key, defValue) + } + + override fun getLong(key: String, defValue: Long): Long { + return preferences.getLong(key, defValue) + } + + override fun getFloat(key: String, defValue: Float): Float { + return preferences.getFloat(key, defValue) + } + + override fun getBoolean(key: String, defValue: Boolean): Boolean { + return preferences.getBoolean(key, defValue) + } + + override fun contains(key: String): Boolean { + return key in preferences.keys + } + + override fun edit(): SharedPreferences.Editor { + return Editor(preferences) + } + + class Editor(private val preferences: JvmPreferencesSettings) : SharedPreferences.Editor { + val itemsToAdd = mutableMapOf() + + override fun putString(key: String, value: String?): SharedPreferences.Editor { + if (value != null) { + itemsToAdd[key] = value + } else { + remove(key) + } + return this + } + + override fun putStringSet( + key: String, + values: MutableSet? + ): SharedPreferences.Editor { + if (values != null) { + itemsToAdd[key] = values + } else { + remove(key) + } + return this + } + + override fun putInt(key: String, value: Int): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun putLong(key: String, value: Long): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun putFloat(key: String, value: Float): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor { + itemsToAdd[key] = value + return this + } + + override fun remove(key: String): SharedPreferences.Editor { + itemsToAdd.remove(key) + return this + } + + override fun clear(): SharedPreferences.Editor { + itemsToAdd.clear() + return this + } + + override fun commit(): Boolean { + addToPreferences() + return true + } + + override fun apply() { + addToPreferences() + } + + private fun addToPreferences() { + itemsToAdd.forEach { (key, value) -> + @Suppress("UNCHECKED_CAST") + when (value) { + is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), key, value as Set) + else -> { + preferences[key] = value + } + } + } + } + } + + override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { + val javaListener = PreferenceChangeListener { + listener.onSharedPreferenceChanged(this, it.key) + } + listeners[listener] = javaListener + javaPreferences.addPreferenceChangeListener(javaListener) + } + + override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { + val registeredListener = listeners.remove(listener) + if (registeredListener != null) { + javaPreferences.removePreferenceChangeListener(registeredListener) + } + } + + fun deleteAll(): Boolean { + javaPreferences.removeNode() + return true + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index f8c4a24..532c923 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.4.32" + kotlin("plugin.serialization") version "1.4.32" apply false } allprojects { @@ -50,6 +51,10 @@ configure(projects) { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") + val kotlinSerializationVersion = "1.2.1" + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion") + // Dependency Injection implementation("org.kodein.di:kodein-di-conf-jvm:7.5.0") From 6c22fe193aee17a05ff0853e7e538365b1c16b66 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Sat, 29 May 2021 19:48:08 -0400 Subject: [PATCH 2/3] Add meta info for clients to store custom data in (#113) * Add meta info for clients to store custom data in * PR comments * Really update migration --- .../migration/M0004_AnimeTablesBatch1.kt | 8 ++-- .../migration/M0005_AnimeTablesBatch2.kt | 4 +- .../migration/M0006_AnimeTablesBatch3.kt | 4 +- .../migration/M0010_MangaAndChapterMeta.kt | 38 +++++++++++++++++++ .../kotlin/suwayomi/tachidesk/TachideskAPI.kt | 27 +++++++++++++ .../kotlin/suwayomi/tachidesk/impl/Chapter.kt | 32 +++++++++++++++- .../kotlin/suwayomi/tachidesk/impl/Manga.kt | 32 ++++++++++++++++ .../suwayomi/tachidesk/impl/MangaList.kt | 4 +- .../model/dataclass/ChapterDataClass.kt | 2 + .../model/dataclass/MangaDataClass.kt | 1 + .../tachidesk/model/table/ChapterMetaTable.kt | 10 +++++ .../tachidesk/model/table/ChapterTable.kt | 2 + .../tachidesk/model/table/MangaMetaTable.kt | 10 +++++ .../tachidesk/model/table/MangaTable.kt | 4 +- 14 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt index 5a6e8b1..3bca772 100644 --- a/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt @@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ class M0004_AnimeTablesBatch1 : Migration() { - private object AnimeExtensionTable : IntIdTable() { + private class AnimeExtensionTable : IntIdTable() { val apkName = varchar("apk_name", 1024) // default is the local source icon from tachiyomi @@ -35,7 +35,7 @@ class M0004_AnimeTablesBatch1 : Migration() { val classFQName = varchar("class_name", 1024).default("") // fully qualified name } - private object AnimeSourceTable : IdTable() { + private class AnimeSourceTable : IdTable() { override val id = long("id").entityId() val name = varchar("name", 128) val lang = varchar("lang", 10) @@ -46,8 +46,8 @@ class M0004_AnimeTablesBatch1 : Migration() { override fun run() { transaction { SchemaUtils.create( - AnimeExtensionTable, - AnimeSourceTable + AnimeExtensionTable(), + AnimeSourceTable() ) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt index 9169cf3..35bfb21 100644 --- a/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt @@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ class M0005_AnimeTablesBatch2 : Migration() { - private object AnimeTable : IntIdTable() { + private class AnimeTable : IntIdTable() { val url = varchar("url", 2048) val title = varchar("title", 512) val initialized = bool("initialized").default(false) @@ -38,7 +38,7 @@ class M0005_AnimeTablesBatch2 : Migration() { override fun run() { transaction { SchemaUtils.create( - AnimeTable + AnimeTable() ) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt index 891a708..5a83f08 100644 --- a/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt @@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ class M0006_AnimeTablesBatch3 : Migration() { - private object EpisodeTable : IntIdTable() { + private class EpisodeTable : IntIdTable() { val url = varchar("url", 2048) val name = varchar("name", 512) val date_upload = long("date_upload").default(0) @@ -34,7 +34,7 @@ class M0006_AnimeTablesBatch3 : Migration() { override fun run() { transaction { SchemaUtils.create( - EpisodeTable + EpisodeTable() ) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt new file mode 100644 index 0000000..8a8e019 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt @@ -0,0 +1,38 @@ +package suwayomi.server.database.migration + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.server.database.migration.lib.Migration +import suwayomi.tachidesk.model.table.ChapterTable +import suwayomi.tachidesk.model.table.MangaTable + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +class M0010_MangaAndChapterMeta : Migration() { + private class ChapterMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE) + } + private class MangaMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE) + } + + override fun run() { + transaction { + SchemaUtils.create( + ChapterMetaTable(), + MangaMetaTable() + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt index 5c7df9c..b7d32b5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt @@ -18,11 +18,13 @@ import suwayomi.tachidesk.impl.CategoryManga.removeMangaFromCategory import suwayomi.tachidesk.impl.Chapter.getChapter import suwayomi.tachidesk.impl.Chapter.getChapterList import suwayomi.tachidesk.impl.Chapter.modifyChapter +import suwayomi.tachidesk.impl.Chapter.modifyChapterMeta import suwayomi.tachidesk.impl.Library.addMangaToLibrary import suwayomi.tachidesk.impl.Library.getLibraryMangas import suwayomi.tachidesk.impl.Library.removeMangaFromLibrary import suwayomi.tachidesk.impl.Manga.getManga import suwayomi.tachidesk.impl.Manga.getMangaThumbnail +import suwayomi.tachidesk.impl.Manga.modifyMangaMeta import suwayomi.tachidesk.impl.MangaList.getMangaList import suwayomi.tachidesk.impl.Page.getPageImage import suwayomi.tachidesk.impl.Search.sourceFilters @@ -185,6 +187,18 @@ object TachideskAPI { ctx.json(future { getChapterList(mangaId, onlineFetch) }) } + // used to modify a manga's meta paramaters + app.patch("/api/v1/manga/:mangaId/meta") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + + val key = ctx.formParam("key")!! + val value = ctx.formParam("value")!! + + modifyMangaMeta(mangaId, key, value) + + ctx.status(200) + } + // used to display a chapter, get a chapter in order to show it's pages app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx -> val chapterIndex = ctx.pathParam("chapterIndex").toInt() @@ -207,6 +221,19 @@ object TachideskAPI { ctx.status(200) } + // used to modify a chapter's meta paramaters + app.patch("/api/v1/manga/:mangaId/chapter/:chapterIndex/meta") { ctx -> + val chapterIndex = ctx.pathParam("chapterIndex").toInt() + val mangaId = ctx.pathParam("mangaId").toInt() + + val key = ctx.formParam("key")!! + val value = ctx.formParam("value")!! + + modifyChapterMeta(mangaId, chapterIndex, key, value) + + ctx.status(200) + } + // get page at index "index" app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx -> val mangaId = ctx.pathParam("mangaId").toInt() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt index d062377..ea31205 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt @@ -9,6 +9,7 @@ package suwayomi.tachidesk.impl import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SortOrder.DESC import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere @@ -20,6 +21,7 @@ import suwayomi.tachidesk.impl.Manga.getManga import suwayomi.tachidesk.impl.util.GetHttpSource.getHttpSource import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.model.dataclass.ChapterDataClass +import suwayomi.tachidesk.model.table.ChapterMetaTable import suwayomi.tachidesk.model.table.ChapterTable import suwayomi.tachidesk.model.table.MangaTable import suwayomi.tachidesk.model.table.PageTable @@ -131,6 +133,7 @@ object Chapter { dbChapter[ChapterTable.pageCount], chapterList.size, + meta = getChapterMetaMap(dbChapter[ChapterTable.id]) ) } } @@ -199,7 +202,8 @@ object Chapter { chapterEntry[ChapterTable.chapterIndex], chapterEntry[ChapterTable.isDownloaded], pageCount, - chapterCount.toInt() + chapterCount.toInt(), + getChapterMetaMap(chapterEntry[ChapterTable.id]) ) } else { ChapterTable.toDataClass(chapterEntry) @@ -230,4 +234,30 @@ object Chapter { } } } + + fun getChapterMetaMap(chapter: EntityID): Map { + return transaction { + ChapterMetaTable.select { ChapterMetaTable.ref eq chapter } + .associate { it[ChapterMetaTable.key] to it[ChapterMetaTable.value] } + } + } + + fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) { + transaction { + val chapter = ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) } + .first()[ChapterTable.id] + val meta = transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapter) and (ChapterMetaTable.key eq key) } }.firstOrNull() + if (meta == null) { + ChapterMetaTable.insert { + it[ChapterMetaTable.key] = key + it[ChapterMetaTable.value] = value + it[ChapterMetaTable.ref] = chapter + } + } else { + ChapterMetaTable.update { + it[ChapterMetaTable.value] = value + } + } + } + } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt index 490d386..9e7c953 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt @@ -9,6 +9,9 @@ package suwayomi.tachidesk.impl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.SManga +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update @@ -24,6 +27,7 @@ import suwayomi.tachidesk.impl.util.network.await import suwayomi.tachidesk.impl.util.storage.CachedImageResponse.clearCachedImage import suwayomi.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse import suwayomi.tachidesk.model.dataclass.MangaDataClass +import suwayomi.tachidesk.model.table.MangaMetaTable import suwayomi.tachidesk.model.table.MangaStatus import suwayomi.tachidesk.model.table.MangaTable import java.io.InputStream @@ -57,6 +61,7 @@ object Manga { MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, mangaEntry[MangaTable.inLibrary], getSource(mangaEntry[MangaTable.sourceReference]), + getMangaMetaMap(mangaEntry[MangaTable.id]), false ) } else { // initialize manga @@ -104,11 +109,38 @@ object Manga { MangaStatus.valueOf(fetchedManga.status).name, mangaEntry[MangaTable.inLibrary], getSource(mangaEntry[MangaTable.sourceReference]), + getMangaMetaMap(mangaEntry[MangaTable.id]), true ) } } + fun getMangaMetaMap(manga: EntityID): Map { + return transaction { + MangaMetaTable.select { MangaMetaTable.ref eq manga } + .associate { it[MangaMetaTable.key] to it[MangaMetaTable.value] } + } + } + + fun modifyMangaMeta(mangaId: Int, key: String, value: String) { + transaction { + val manga = MangaMetaTable.select { (MangaTable.id eq mangaId) } + .first()[MangaTable.id] + val meta = transaction { MangaMetaTable.select { (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) } }.firstOrNull() + if (meta == null) { + MangaMetaTable.insert { + it[MangaMetaTable.key] = key + it[MangaMetaTable.value] = value + it[MangaMetaTable.ref] = manga + } + } else { + MangaMetaTable.update { + it[MangaMetaTable.value] = value + } + } + } + } + private val applicationDirs by DI.global.instance() suspend fun getMangaThumbnail(mangaId: Int): Pair { val saveDir = applicationDirs.mangaThumbnailsRoot diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt index b3359c0..cb8e28d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.MangasPage import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.impl.Manga.getMangaMetaMap import suwayomi.tachidesk.impl.util.GetHttpSource.getHttpSource import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.model.dataclass.MangaDataClass @@ -89,7 +90,8 @@ object MangaList { mangaEntry[MangaTable.description], mangaEntry[MangaTable.genre], MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary] + mangaEntry[MangaTable.inLibrary], + meta = getMangaMetaMap(mangaEntry[MangaTable.id]) ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt index 9a956f5..528effe 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt @@ -39,4 +39,6 @@ data class ChapterDataClass( /** total chapter count, used to calculate if there's a next and prev chapter */ val chapterCount: Int? = null, + /** used to store client specific values */ + val meta: Map = emptyMap(), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt index 7ed07ff..1570a52 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt @@ -26,6 +26,7 @@ data class MangaDataClass( val status: String = MangaStatus.UNKNOWN.name, val inLibrary: Boolean = false, val source: SourceDataClass? = null, + val meta: Map = emptyMap(), val freshData: Boolean = false ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt new file mode 100644 index 0000000..c3ca6c8 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt @@ -0,0 +1,10 @@ +package suwayomi.tachidesk.model.table + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +object ChapterMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt index c39ecd3..ad187d4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt @@ -11,6 +11,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.impl.Chapter.getChapterMetaMap import suwayomi.tachidesk.model.dataclass.ChapterDataClass object ChapterTable : IntIdTable() { @@ -51,4 +52,5 @@ fun ChapterTable.toDataClass(chapterEntry: ResultRow) = chapterEntry[isDownloaded], chapterEntry[pageCount], transaction { ChapterTable.select { ChapterTable.manga eq chapterEntry[manga].value }.count().toInt() }, + getChapterMetaMap(chapterEntry[id]), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt new file mode 100644 index 0000000..f726997 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt @@ -0,0 +1,10 @@ +package suwayomi.tachidesk.model.table + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +object MangaMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt index 9792ef7..d1d9039 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt @@ -10,6 +10,7 @@ package suwayomi.tachidesk.model.table import eu.kanade.tachiyomi.source.model.SManga import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ResultRow +import suwayomi.tachidesk.impl.Manga.getMangaMetaMap import suwayomi.tachidesk.impl.MangaList.proxyThumbnailUrl import suwayomi.tachidesk.model.dataclass.MangaDataClass import suwayomi.tachidesk.model.table.MangaStatus.Companion @@ -50,7 +51,8 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) = mangaEntry[description], mangaEntry[genre], Companion.valueOf(mangaEntry[status]).name, - mangaEntry[inLibrary] + mangaEntry[inLibrary], + meta = getMangaMetaMap(mangaEntry[id]) ) enum class MangaStatus(val status: Int) { From 849e2f103aa085cad3e9e97b88afeded1d7a874e Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sun, 30 May 2021 04:24:21 +0430 Subject: [PATCH 3/3] [SKIP CI] download chapters for real now --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63258d1..ce3c765 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Here is a list of current features: - A library to save your mangas and categories to put them into. - Searching and browsing installed sources. - A decent chapter reader. -- Ability to download Mangas for offline read(This partially works) +- Ability to download Mangas for offline read - Backup and restore support powered by Tachiyomi Legacy Backups **Note:** Keep in mind that Tachidesk is alpha software and can break rarely and/or with each update. See [Troubleshooting](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting) if it happens.