diff --git a/app/src/main/java/dev/yokai/core/metadata/ComicInfo.kt b/app/src/main/java/dev/yokai/core/metadata/ComicInfo.kt index 45470a04fb..a4be9ec371 100644 --- a/app/src/main/java/dev/yokai/core/metadata/ComicInfo.kt +++ b/app/src/main/java/dev/yokai/core/metadata/ComicInfo.kt @@ -1,5 +1,7 @@ package dev.yokai.core.metadata +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.source.model.SManga import kotlinx.serialization.Serializable import nl.adaptivity.xmlutil.serialization.XmlElement @@ -9,6 +11,42 @@ import nl.adaptivity.xmlutil.serialization.XmlValue const val COMIC_INFO_EDITS_FILE = "ComicInfoEdits.xml" const val COMIC_INFO_FILE = "ComicInfo.xml" +fun getComicInfo( + manga: Manga, + chapter: Chapter, + urls: List, + categories: List?, + sourceName: String, + lang: String?, +) = ComicInfo( + title = ComicInfo.Title(chapter.name), + series = ComicInfo.Series(manga.title), + number = chapter.chapter_number.takeIf { it >= 0 }?.let { + if (it.rem(1) == 0.0f) { + ComicInfo.Number(it.toInt().toString()) + } else { + ComicInfo.Number(it.toString()) + } + }, + summary = manga.description?.let { ComicInfo.Summary(it) }, + writer = manga.author?.let { ComicInfo.Writer(it) }, + penciller = manga.artist?.let { ComicInfo.Penciller(it) }, + inker = null, + colorist = null, + letterer = null, + coverArtist = null, + translator = chapter.scanlator?.let { ComicInfo.Translator(it) }, + genre = manga.genre?.let { ComicInfo.Genre(it) }, + tags = null, + web = ComicInfo.Web(urls.joinToString(" ")), + publishingStatus = ComicInfo.PublishingStatusTachiyomi( + ComicInfoPublishingStatus.toComicInfoValue(manga.status.toLong()) + ), + categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) }, + source = ComicInfo.SourceMihon(sourceName), + language = lang?.let { ComicInfo.LanguageJ2K(it) }, +) + fun SManga.toComicInfo(lang: String? = null) = ComicInfo( title = null, series = ComicInfo.Series(title), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 12d267a300..2b155b38e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -5,9 +5,13 @@ import android.os.Handler import android.os.Looper import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.PublishRelay +import dev.yokai.core.metadata.COMIC_INFO_FILE +import dev.yokai.core.metadata.ComicInfo +import dev.yokai.core.metadata.getComicInfo import dev.yokai.domain.download.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download @@ -27,6 +31,7 @@ import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchNow import eu.kanade.tachiyomi.util.system.withIOContext import eu.kanade.tachiyomi.util.system.withUIContext +import eu.kanade.tachiyomi.util.system.writeText import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -38,6 +43,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.runBlocking +import nl.adaptivity.xmlutil.serialization.XML import okhttp3.Response import rx.Observable import rx.Subscription @@ -74,6 +80,8 @@ class Downloader( private val preferences: PreferencesHelper by injectLazy() private val downloadPreferences: DownloadPreferences by injectLazy() private val chapterCache: ChapterCache by injectLazy() + private val xml: XML by injectLazy() + private val db: DatabaseHelper by injectLazy() /** * Store for persisting downloads across restarts. @@ -443,16 +451,8 @@ class Downloader( } // When the page is ready, set page path, progress (just in case) and status - val success = splitTallImageIfNeeded(page, tmpDir) - /* - if (!success) { - notifier.onError( - context.getString(R.string.download_notifier_split_failed), - chapName, - download.manga.title, - ) - } - */ + splitTallImageIfNeeded(page, tmpDir) + page.uri = file.uri page.progress = 100 page.status = Page.State.READY @@ -596,14 +596,12 @@ class Downloader( } download.status = if (downloadedImagesCount == downloadPageCount) { - // TODO: Uncomment when #8537 is resolved -// val chapterUrl = download.source.getChapterUrl(download.chapter) -// createComicInfoFile( -// tmpDir, -// download.manga, -// download.chapter.toDomainChapter()!!, -// chapterUrl, -// ) + createComicInfoFile( + tmpDir, + download.manga, + download.chapter, + download.source, + ) // Only rename the directory if it's downloaded if (preferences.saveChaptersAsCBZ().get()) { @@ -655,28 +653,37 @@ class Downloader( tmpDir.delete() } -// /** -// * Creates a ComicInfo.xml file inside the given directory. -// * -// * @param dir the directory in which the ComicInfo file will be generated. -// * @param manga the manga. -// * @param chapter the chapter. -// * @param chapterUrl the resolved URL for the chapter. -// */ -// private fun createComicInfoFile( -// dir: UniFile, -// manga: Manga, -// chapter: Chapter, -// chapterUrl: String, -// ) { -// val comicInfo = getComicInfo(manga, chapter, chapterUrl) -// val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo) -// // Remove the old file -// dir.findFile(COMIC_INFO_FILE)?.delete() -// dir.createFile(COMIC_INFO_FILE).openOutputStream().use { -// it.write(comicInfoString.toByteArray()) -// } -// } + /** + * Creates a ComicInfo.xml file inside the given directory. + * + * @param dir the directory in which the ComicInfo file will be generated. + * @param manga the manga. + * @param chapter the chapter. + * @param chapterUrl the resolved URL for the chapter. + */ + private fun createComicInfoFile( + dir: UniFile, + manga: Manga, + chapter: Chapter, + source: HttpSource, + ) { + val categories = + db.getCategoriesForManga(manga).executeAsBlocking().map { it.name.trim() }.takeUnless { it.isEmpty() } + val urls = source.getChapterUrl(manga, chapter)?.let { listOf(it) } ?: listOf() + + val comicInfo = getComicInfo( + manga, + chapter, + urls, + categories, + source.name, + source.lang, + ) + + // Remove the old file + dir.findFile(COMIC_INFO_FILE)?.delete() + dir.createFile(COMIC_INFO_FILE)?.writeText(xml.encodeToString(ComicInfo.serializer(), comicInfo)) + } /** * Completes a download. This method is called in the main thread. diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt index f22d2c151d..4f39c1d778 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt @@ -33,6 +33,8 @@ import kotlinx.serialization.json.decodeFromStream import nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.serialization.XML import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.FileInputStream import java.io.InputStream @@ -48,6 +50,12 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS) private val langMap = hashMapOf() + fun decodeComicInfo(stream: InputStream, xml: XML = Injekt.get()): ComicInfo { + return AndroidXmlReader(stream, StandardCharsets.UTF_8.name()).use { reader -> + xml.decodeFromReader(reader) + } + } + fun getMangaLang(manga: SManga): String { return langMap.getOrPut(manga.url) { val localDetails = getBaseDirectory().findFile(manga.url)?.listFiles().orEmpty() @@ -55,10 +63,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour .firstOrNull { it.name == COMIC_INFO_FILE } return if (localDetails != null) { - val obj = AndroidXmlReader(localDetails.openInputStream(), StandardCharsets.UTF_8.name()).use { - XML.decodeFromReader(it) - } - obj.language?.value ?: "other" + decodeComicInfo(localDetails.openInputStream()).language?.value ?: "other" } else { "other" } @@ -218,9 +223,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour } private fun setMangaDetailsFromComicInfoFile(stream: InputStream, manga: SManga) { - val comicInfo = AndroidXmlReader(stream, StandardCharsets.UTF_8.name()).use { - xml.decodeFromReader(it) - } + val comicInfo = decodeComicInfo(stream, xml) comicInfo.language?.let { langMap[manga.url] = it.value } manga.copyFromComicInfo(comicInfo) @@ -279,10 +282,14 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour val chapters = getBaseDirectory().findFile(manga.url)?.listFiles().orEmpty() .filter { it.isDirectory || isSupportedFile(it.extension.orEmpty()) } .map { chapterFile -> + val chapterComicInfo = chapterFile.findFile(COMIC_INFO_FILE)?.let { + decodeComicInfo(it.openInputStream(), xml) + } + SChapter.create().apply { url = "${manga.url}/${chapterFile.name}" name = if (chapterFile.isDirectory) { - chapterFile.name.orEmpty() + chapterComicInfo?.title?.value ?: chapterFile.name.orEmpty() } else { chapterFile.nameWithoutExtension.orEmpty() }