Merge branch 'master' of github.com:Suwayomi/Tachidesk

This commit is contained in:
Aria Moradi 2021-05-30 04:56:59 +04:30
commit 10a29cab33
20 changed files with 362 additions and 24 deletions

View File

@ -1,6 +1,7 @@
plugins { plugins {
application application
kotlin("plugin.serialization")
} }
@ -46,6 +47,17 @@ dependencies {
implementation("org.mozilla:rhino-runtime:1.7.13") implementation("org.mozilla:rhino-runtime:1.7.13")
// 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn // 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn
implementation("org.mozilla:rhino-engine:1.7.13") 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<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn")
}
} }
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar') //def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')

View File

@ -38,7 +38,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl; import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.io.AndroidFiles; 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.service.ServiceSupport;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper; import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
@ -165,23 +165,22 @@ public class CustomContext extends Context implements DIAware {
/** Fake shared prefs! **/ /** Fake shared prefs! **/
private Map<String, SharedPreferences> prefs = new HashMap<>(); //Cache private Map<String, SharedPreferences> prefs = new HashMap<>(); //Cache
private File sharedPrefsFileFromString(String s) {
return new File(androidFiles.getPrefsDir(), s + ".json");
}
@Override @Override
public synchronized SharedPreferences getSharedPreferences(String s, int i) { public synchronized SharedPreferences getSharedPreferences(String s, int i) {
SharedPreferences preferences = prefs.get(s); SharedPreferences preferences = prefs.get(s);
//Create new shared preferences if one does not exist //Create new shared preferences if one does not exist
if(preferences == null) { if(preferences == null) {
preferences = getSharedPreferences(sharedPrefsFileFromString(s), i); preferences = new JavaSharedPreferences(s);
prefs.put(s, preferences); prefs.put(s, preferences);
} }
return preferences; return preferences;
} }
public SharedPreferences getSharedPreferences(File file, int mode) { @Override
return new JsonSharedPreferences(file); public SharedPreferences getSharedPreferences(@NotNull File file, int mode) {
String path = file.getAbsolutePath().replace('\\', '/');
int firstSlash = path.indexOf("/");
return new JavaSharedPreferences(path.substring(firstSlash));
} }
@Override @Override
@ -191,8 +190,8 @@ public class CustomContext extends Context implements DIAware {
@Override @Override
public boolean deleteSharedPreferences(String name) { public boolean deleteSharedPreferences(String name) {
prefs.remove(name); JavaSharedPreferences item = (JavaSharedPreferences) prefs.remove(name);
return sharedPrefsFileFromString(name).delete(); return item.deleteAll();
} }
@Override @Override

View File

@ -26,8 +26,6 @@ class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir) val downloadCacheDir: File get() = registerFile(filesConfig.downloadCacheDir)
val databasesDir: File get() = registerFile(filesConfig.databasesDir) val databasesDir: File get() = registerFile(filesConfig.databasesDir)
val prefsDir: File get() = registerFile(filesConfig.prefsDir)
val packagesDir: File get() = registerFile(filesConfig.packageDir) val packagesDir: File get() = registerFile(filesConfig.packageDir)
fun registerFile(file: String): File { fun registerFile(file: String): File {

View File

@ -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<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
override fun getAll(): MutableMap<String, *> {
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<String>?): MutableSet<String>? {
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<String, Any>()
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<String>?
): 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<String>)
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
}
}

View File

@ -23,7 +23,7 @@ Here is a list of current features:
- A library to save your mangas and categories to put them into. - A library to save your mangas and categories to put them into.
- Searching and browsing installed sources. - Searching and browsing installed sources.
- A decent chapter reader. - 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 - 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. **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.

View File

@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.4.32" kotlin("jvm") version "1.4.32"
kotlin("plugin.serialization") version "1.4.32" apply false
} }
allprojects { allprojects {
@ -50,6 +51,10 @@ configure(projects) {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$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 // Dependency Injection
implementation("org.kodein.di:kodein-di-conf-jvm:7.5.0") implementation("org.kodein.di:kodein-di-conf-jvm:7.5.0")

View File

@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
class M0004_AnimeTablesBatch1 : Migration() { class M0004_AnimeTablesBatch1 : Migration() {
private object AnimeExtensionTable : IntIdTable() { private class AnimeExtensionTable : IntIdTable() {
val apkName = varchar("apk_name", 1024) val apkName = varchar("apk_name", 1024)
// default is the local source icon from tachiyomi // 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 val classFQName = varchar("class_name", 1024).default("") // fully qualified name
} }
private object AnimeSourceTable : IdTable<Long>() { private class AnimeSourceTable : IdTable<Long>() {
override val id = long("id").entityId() override val id = long("id").entityId()
val name = varchar("name", 128) val name = varchar("name", 128)
val lang = varchar("lang", 10) val lang = varchar("lang", 10)
@ -46,8 +46,8 @@ class M0004_AnimeTablesBatch1 : Migration() {
override fun run() { override fun run() {
transaction { transaction {
SchemaUtils.create( SchemaUtils.create(
AnimeExtensionTable, AnimeExtensionTable(),
AnimeSourceTable AnimeSourceTable()
) )
} }
} }

View File

@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
class M0005_AnimeTablesBatch2 : Migration() { class M0005_AnimeTablesBatch2 : Migration() {
private object AnimeTable : IntIdTable() { private class AnimeTable : IntIdTable() {
val url = varchar("url", 2048) val url = varchar("url", 2048)
val title = varchar("title", 512) val title = varchar("title", 512)
val initialized = bool("initialized").default(false) val initialized = bool("initialized").default(false)
@ -38,7 +38,7 @@ class M0005_AnimeTablesBatch2 : Migration() {
override fun run() { override fun run() {
transaction { transaction {
SchemaUtils.create( SchemaUtils.create(
AnimeTable AnimeTable()
) )
} }
} }

View File

@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
class M0006_AnimeTablesBatch3 : Migration() { class M0006_AnimeTablesBatch3 : Migration() {
private object EpisodeTable : IntIdTable() { private class EpisodeTable : IntIdTable() {
val url = varchar("url", 2048) val url = varchar("url", 2048)
val name = varchar("name", 512) val name = varchar("name", 512)
val date_upload = long("date_upload").default(0) val date_upload = long("date_upload").default(0)
@ -34,7 +34,7 @@ class M0006_AnimeTablesBatch3 : Migration() {
override fun run() { override fun run() {
transaction { transaction {
SchemaUtils.create( SchemaUtils.create(
EpisodeTable EpisodeTable()
) )
} }
} }

View File

@ -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()
)
}
}
}

View File

@ -18,11 +18,13 @@ import suwayomi.tachidesk.impl.CategoryManga.removeMangaFromCategory
import suwayomi.tachidesk.impl.Chapter.getChapter import suwayomi.tachidesk.impl.Chapter.getChapter
import suwayomi.tachidesk.impl.Chapter.getChapterList import suwayomi.tachidesk.impl.Chapter.getChapterList
import suwayomi.tachidesk.impl.Chapter.modifyChapter import suwayomi.tachidesk.impl.Chapter.modifyChapter
import suwayomi.tachidesk.impl.Chapter.modifyChapterMeta
import suwayomi.tachidesk.impl.Library.addMangaToLibrary import suwayomi.tachidesk.impl.Library.addMangaToLibrary
import suwayomi.tachidesk.impl.Library.getLibraryMangas import suwayomi.tachidesk.impl.Library.getLibraryMangas
import suwayomi.tachidesk.impl.Library.removeMangaFromLibrary import suwayomi.tachidesk.impl.Library.removeMangaFromLibrary
import suwayomi.tachidesk.impl.Manga.getManga import suwayomi.tachidesk.impl.Manga.getManga
import suwayomi.tachidesk.impl.Manga.getMangaThumbnail import suwayomi.tachidesk.impl.Manga.getMangaThumbnail
import suwayomi.tachidesk.impl.Manga.modifyMangaMeta
import suwayomi.tachidesk.impl.MangaList.getMangaList import suwayomi.tachidesk.impl.MangaList.getMangaList
import suwayomi.tachidesk.impl.Page.getPageImage import suwayomi.tachidesk.impl.Page.getPageImage
import suwayomi.tachidesk.impl.Search.sourceFilters import suwayomi.tachidesk.impl.Search.sourceFilters
@ -185,6 +187,18 @@ object TachideskAPI {
ctx.json(future { getChapterList(mangaId, onlineFetch) }) 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 // used to display a chapter, get a chapter in order to show it's pages
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx -> app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
val chapterIndex = ctx.pathParam("chapterIndex").toInt() val chapterIndex = ctx.pathParam("chapterIndex").toInt()
@ -207,6 +221,19 @@ object TachideskAPI {
ctx.status(200) 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" // get page at index "index"
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx -> app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx ->
val mangaId = ctx.pathParam("mangaId").toInt() val mangaId = ctx.pathParam("mangaId").toInt()

View File

@ -9,6 +9,7 @@ package suwayomi.tachidesk.impl
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga 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.SortOrder.DESC
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere 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.GetHttpSource.getHttpSource
import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.impl.util.lang.awaitSingle
import suwayomi.tachidesk.model.dataclass.ChapterDataClass import suwayomi.tachidesk.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.model.table.ChapterMetaTable
import suwayomi.tachidesk.model.table.ChapterTable import suwayomi.tachidesk.model.table.ChapterTable
import suwayomi.tachidesk.model.table.MangaTable import suwayomi.tachidesk.model.table.MangaTable
import suwayomi.tachidesk.model.table.PageTable import suwayomi.tachidesk.model.table.PageTable
@ -131,6 +133,7 @@ object Chapter {
dbChapter[ChapterTable.pageCount], dbChapter[ChapterTable.pageCount],
chapterList.size, chapterList.size,
meta = getChapterMetaMap(dbChapter[ChapterTable.id])
) )
} }
} }
@ -199,7 +202,8 @@ object Chapter {
chapterEntry[ChapterTable.chapterIndex], chapterEntry[ChapterTable.chapterIndex],
chapterEntry[ChapterTable.isDownloaded], chapterEntry[ChapterTable.isDownloaded],
pageCount, pageCount,
chapterCount.toInt() chapterCount.toInt(),
getChapterMetaMap(chapterEntry[ChapterTable.id])
) )
} else { } else {
ChapterTable.toDataClass(chapterEntry) ChapterTable.toDataClass(chapterEntry)
@ -230,4 +234,30 @@ object Chapter {
} }
} }
} }
fun getChapterMetaMap(chapter: EntityID<Int>): Map<String, String> {
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
}
}
}
}
} }

View File

@ -9,6 +9,9 @@ package suwayomi.tachidesk.impl
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SManga 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.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update 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.clearCachedImage
import suwayomi.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse import suwayomi.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
import suwayomi.tachidesk.model.dataclass.MangaDataClass import suwayomi.tachidesk.model.dataclass.MangaDataClass
import suwayomi.tachidesk.model.table.MangaMetaTable
import suwayomi.tachidesk.model.table.MangaStatus import suwayomi.tachidesk.model.table.MangaStatus
import suwayomi.tachidesk.model.table.MangaTable import suwayomi.tachidesk.model.table.MangaTable
import java.io.InputStream import java.io.InputStream
@ -57,6 +61,7 @@ object Manga {
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary], mangaEntry[MangaTable.inLibrary],
getSource(mangaEntry[MangaTable.sourceReference]), getSource(mangaEntry[MangaTable.sourceReference]),
getMangaMetaMap(mangaEntry[MangaTable.id]),
false false
) )
} else { // initialize manga } else { // initialize manga
@ -104,11 +109,38 @@ object Manga {
MangaStatus.valueOf(fetchedManga.status).name, MangaStatus.valueOf(fetchedManga.status).name,
mangaEntry[MangaTable.inLibrary], mangaEntry[MangaTable.inLibrary],
getSource(mangaEntry[MangaTable.sourceReference]), getSource(mangaEntry[MangaTable.sourceReference]),
getMangaMetaMap(mangaEntry[MangaTable.id]),
true true
) )
} }
} }
fun getMangaMetaMap(manga: EntityID<Int>): Map<String, String> {
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<ApplicationDirs>() private val applicationDirs by DI.global.instance<ApplicationDirs>()
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> { suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
val saveDir = applicationDirs.mangaThumbnailsRoot val saveDir = applicationDirs.mangaThumbnailsRoot

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction 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.GetHttpSource.getHttpSource
import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.impl.util.lang.awaitSingle
import suwayomi.tachidesk.model.dataclass.MangaDataClass import suwayomi.tachidesk.model.dataclass.MangaDataClass
@ -89,7 +90,8 @@ object MangaList {
mangaEntry[MangaTable.description], mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre], mangaEntry[MangaTable.genre],
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary] mangaEntry[MangaTable.inLibrary],
meta = getMangaMetaMap(mangaEntry[MangaTable.id])
) )
} }
} }

View File

@ -39,4 +39,6 @@ data class ChapterDataClass(
/** total chapter count, used to calculate if there's a next and prev chapter */ /** total chapter count, used to calculate if there's a next and prev chapter */
val chapterCount: Int? = null, val chapterCount: Int? = null,
/** used to store client specific values */
val meta: Map<String, String> = emptyMap(),
) )

View File

@ -26,6 +26,7 @@ data class MangaDataClass(
val status: String = MangaStatus.UNKNOWN.name, val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false, val inLibrary: Boolean = false,
val source: SourceDataClass? = null, val source: SourceDataClass? = null,
val meta: Map<String, String> = emptyMap(),
val freshData: Boolean = false val freshData: Boolean = false
) )

View File

@ -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)
}

View File

@ -11,6 +11,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.impl.Chapter.getChapterMetaMap
import suwayomi.tachidesk.model.dataclass.ChapterDataClass import suwayomi.tachidesk.model.dataclass.ChapterDataClass
object ChapterTable : IntIdTable() { object ChapterTable : IntIdTable() {
@ -51,4 +52,5 @@ fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
chapterEntry[isDownloaded], chapterEntry[isDownloaded],
chapterEntry[pageCount], chapterEntry[pageCount],
transaction { ChapterTable.select { ChapterTable.manga eq chapterEntry[manga].value }.count().toInt() }, transaction { ChapterTable.select { ChapterTable.manga eq chapterEntry[manga].value }.count().toInt() },
getChapterMetaMap(chapterEntry[id]),
) )

View File

@ -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)
}

View File

@ -10,6 +10,7 @@ package suwayomi.tachidesk.model.table
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import suwayomi.tachidesk.impl.Manga.getMangaMetaMap
import suwayomi.tachidesk.impl.MangaList.proxyThumbnailUrl import suwayomi.tachidesk.impl.MangaList.proxyThumbnailUrl
import suwayomi.tachidesk.model.dataclass.MangaDataClass import suwayomi.tachidesk.model.dataclass.MangaDataClass
import suwayomi.tachidesk.model.table.MangaStatus.Companion import suwayomi.tachidesk.model.table.MangaStatus.Companion
@ -50,7 +51,8 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) =
mangaEntry[description], mangaEntry[description],
mangaEntry[genre], mangaEntry[genre],
Companion.valueOf(mangaEntry[status]).name, Companion.valueOf(mangaEntry[status]).name,
mangaEntry[inLibrary] mangaEntry[inLibrary],
meta = getMangaMetaMap(mangaEntry[id])
) )
enum class MangaStatus(val status: Int) { enum class MangaStatus(val status: Int) {