diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt index 4c9670c7..8fe30c1f 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt @@ -19,6 +19,11 @@ import android.app.Application import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf +import nl.adaptivity.xmlutil.serialization.XML +import org.kodein.di.DI +import org.kodein.di.conf.global +import org.kodein.di.instance import rx.Observable import rx.schedulers.Schedulers import uy.kohesive.injekt.api.InjektModule @@ -53,7 +58,20 @@ class AppModule(val app: Application) : InjektModule { // // addSingletonFactory { LibrarySyncManager(app) } - addSingletonFactory { Json { ignoreUnknownKeys = true } } + addSingletonFactory { + val json by DI.global.instance() + json + } + + addSingletonFactory { + val xml by DI.global.instance() + xml + } + + addSingletonFactory { + val protobuf by DI.global.instance() + protobuf + } // Asynchronously init expensive components for a faster cold start diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt index 03682eb6..4b971240 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt @@ -7,9 +7,14 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.transactions.transaction import suwayomi.tachidesk.manga.impl.Page import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter +import suwayomi.tachidesk.manga.impl.util.createComicInfoFile import suwayomi.tachidesk.manga.impl.util.getChapterCachePath +import suwayomi.tachidesk.manga.model.table.ChapterTable +import suwayomi.tachidesk.manga.model.table.MangaTable import java.io.File import java.io.InputStream @@ -62,6 +67,17 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow download.progress = ((pageNum + 1).toFloat()) / pageCount step(download, false) } + + createComicInfoFile( + folder.toPath(), + transaction { + MangaTable.select { MangaTable.id eq mangaId }.first() + }, + transaction { + ChapterTable.select { ChapterTable.id eq chapterId }.first() + }, + ) + return true } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetComicInfo.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetComicInfo.kt new file mode 100644 index 00000000..8b10eeb0 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetComicInfo.kt @@ -0,0 +1,83 @@ +package suwayomi.tachidesk.manga.impl.util + +import eu.kanade.tachiyomi.source.local.metadata.COMIC_INFO_FILE +import eu.kanade.tachiyomi.source.local.metadata.ComicInfo +import eu.kanade.tachiyomi.source.local.metadata.ComicInfoPublishingStatus +import nl.adaptivity.xmlutil.serialization.XML +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.manga.model.table.CategoryMangaTable +import suwayomi.tachidesk.manga.model.table.CategoryTable +import suwayomi.tachidesk.manga.model.table.ChapterTable +import suwayomi.tachidesk.manga.model.table.MangaTable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.nio.file.Path +import kotlin.io.path.deleteIfExists +import kotlin.io.path.div +import kotlin.io.path.outputStream + +/** + * Creates a ComicInfo instance based on the manga and chapter metadata. + */ +fun getComicInfo( + manga: ResultRow, + chapter: ResultRow, + chapterUrl: String, + categories: List?, +) = ComicInfo( + title = ComicInfo.Title(chapter[ChapterTable.name]), + series = ComicInfo.Series(manga[MangaTable.title]), + number = + chapter[ChapterTable.chapter_number].takeIf { it >= 0 }?.let { + if ((it.rem(1) == 0.0f)) { + ComicInfo.Number(it.toInt().toString()) + } else { + ComicInfo.Number(it.toString()) + } + }, + web = ComicInfo.Web(chapterUrl), + summary = manga[MangaTable.description]?.let { ComicInfo.Summary(it) }, + writer = manga[MangaTable.author]?.let { ComicInfo.Writer(it) }, + penciller = manga[MangaTable.artist]?.let { ComicInfo.Penciller(it) }, + translator = chapter[ChapterTable.scanlator]?.let { ComicInfo.Translator(it) }, + genre = manga[MangaTable.genre]?.let { ComicInfo.Genre(it) }, + publishingStatus = + ComicInfo.PublishingStatusTachiyomi( + ComicInfoPublishingStatus.toComicInfoValue(manga[MangaTable.status].toLong()), + ), + categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) }, + inker = null, + colorist = null, + letterer = null, + coverArtist = null, + tags = null, +) + +/** + * Creates a ComicInfo.xml file inside the given directory. + */ +fun createComicInfoFile( + dir: Path, + manga: ResultRow, + chapter: ResultRow, +) { + val chapterUrl = chapter[ChapterTable.realUrl].orEmpty() + val categories = + transaction { + CategoryMangaTable.innerJoin(CategoryTable).select { + CategoryMangaTable.manga eq manga[MangaTable.id] + }.orderBy(CategoryTable.order to SortOrder.ASC).map { + it[CategoryTable.name] + } + }.takeUnless { it.isEmpty() } + val comicInfo = getComicInfo(manga, chapter, chapterUrl, categories) + // Remove the old file + (dir / COMIC_INFO_FILE).deleteIfExists() + (dir / COMIC_INFO_FILE).outputStream().use { + val comicInfoString = Injekt.get().encodeToString(ComicInfo.serializer(), comicInfo) + it.write(comicInfoString.toByteArray()) + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt index 61ef2f06..6c1d9982 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt @@ -17,7 +17,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf import mu.KotlinLogging +import nl.adaptivity.xmlutil.XmlDeclMode +import nl.adaptivity.xmlutil.core.XmlVersion +import nl.adaptivity.xmlutil.serialization.XML import org.bouncycastle.jce.provider.BouncyCastleProvider import org.kodein.di.DI import org.kodein.di.bind @@ -33,7 +37,6 @@ import suwayomi.tachidesk.server.database.databaseUp import suwayomi.tachidesk.server.generated.BuildConfig import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex import suwayomi.tachidesk.server.util.SystemTray -import uy.kohesive.injekt.api.get import xyz.nulldev.androidcompat.AndroidCompat import xyz.nulldev.androidcompat.AndroidCompatInitializer import xyz.nulldev.ts.config.ApplicationRootDir @@ -112,7 +115,29 @@ fun applicationSetup() { bind() with singleton { applicationDirs } bind() with singleton { Updater() } bind() with singleton { JavalinJackson() } - bind() with singleton { Json { ignoreUnknownKeys = true } } + bind() with + singleton { + Json { + ignoreUnknownKeys = true + explicitNulls = false + } + } + bind() with + singleton { + XML { + defaultPolicy { + ignoreUnknownChildren() + } + autoPolymorphic = true + xmlDeclMode = XmlDeclMode.Charset + indent = 2 + xmlVersion = XmlVersion.XML10 + } + } + bind() with + singleton { + ProtoBuf + } }, )