mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Create ComicInfo Metadata files on chapter download (#8033)
* generate ComicInfo files at the chapter root and inside CBZ archives on chapter download. * Update app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt Co-authored-by: Andreas <andreas.everos@gmail.com> * Improvements suggested by @ghostbear * now creates ComicInfo files in normal chapter folders as well use manga directly instead of converting it to SManga truncate old files before overwriting them Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com> * remove empty line after resolving merge conflict * fixes Serializer for class 'ComicInfo' is not found error * some changes to comments and variable names * Revert leftover changes to archiveChapter() function * minor cleanup * Changed Chapter to SChapter Co-authored-by: Andreas <andreas.everos@gmail.com> Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
This commit is contained in:
		| @@ -1,12 +1,14 @@ | ||||
| package eu.kanade.domain.manga.model | ||||
|  | ||||
| import kotlinx.serialization.Serializable | ||||
| import nl.adaptivity.xmlutil.serialization.XmlElement | ||||
| import nl.adaptivity.xmlutil.serialization.XmlSerialName | ||||
| import nl.adaptivity.xmlutil.serialization.XmlValue | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("ComicInfo", "", "") | ||||
| data class ComicInfo( | ||||
|     val title: ComicInfoTitle?, | ||||
|     val series: ComicInfoSeries?, | ||||
|     val summary: ComicInfoSummary?, | ||||
|     val writer: ComicInfoWriter?, | ||||
| @@ -15,9 +17,24 @@ data class ComicInfo( | ||||
|     val colorist: ComicInfoColorist?, | ||||
|     val letterer: ComicInfoLetterer?, | ||||
|     val coverArtist: ComicInfoCoverArtist?, | ||||
|     val translator: ComicInfoTranslator?, | ||||
|     val genre: ComicInfoGenre?, | ||||
|     val tags: ComicInfoTags?, | ||||
| ) | ||||
|     val web: ComicInfoWeb?, | ||||
|     val publishingStatusTachiyomi: ComicInfoPublishingStatusTachiyomi?, | ||||
| ) { | ||||
|     @XmlElement(false) | ||||
|     @XmlSerialName("xmlns:xsd", "", "") | ||||
|     val xmlSchema: String = "http://www.w3.org/2001/XMLSchema" | ||||
|  | ||||
|     @XmlElement(false) | ||||
|     @XmlSerialName("xmlns:xsi", "", "") | ||||
|     val xmlSchemaInstance: String = "http://www.w3.org/2001/XMLSchema-instance" | ||||
| } | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("Title", "", "") | ||||
| data class ComicInfoTitle(@XmlValue(true) val value: String = "") | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("Series", "", "") | ||||
| @@ -51,6 +68,10 @@ data class ComicInfoLetterer(@XmlValue(true) val value: String = "") | ||||
| @XmlSerialName("CoverArtist", "", "") | ||||
| data class ComicInfoCoverArtist(@XmlValue(true) val value: String = "") | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("Translator", "", "") | ||||
| data class ComicInfoTranslator(@XmlValue(true) val value: String = "") | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("Genre", "", "") | ||||
| data class ComicInfoGenre(@XmlValue(true) val value: String = "") | ||||
| @@ -58,3 +79,11 @@ data class ComicInfoGenre(@XmlValue(true) val value: String = "") | ||||
| @Serializable | ||||
| @XmlSerialName("Tags", "", "") | ||||
| data class ComicInfoTags(@XmlValue(true) val value: String = "") | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("Web", "", "") | ||||
| data class ComicInfoWeb(@XmlValue(true) val value: String = "") | ||||
|  | ||||
| @Serializable | ||||
| @XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty") | ||||
| data class ComicInfoPublishingStatusTachiyomi(@XmlValue(true) val value: String = "") | ||||
|   | ||||
| @@ -43,6 +43,8 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences | ||||
| import eu.kanade.tachiyomi.util.system.isDevFlavor | ||||
| import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory | ||||
| import kotlinx.serialization.json.Json | ||||
| import nl.adaptivity.xmlutil.XmlDeclMode | ||||
| import nl.adaptivity.xmlutil.core.XmlVersion | ||||
| import nl.adaptivity.xmlutil.serialization.UnknownChildHandler | ||||
| import nl.adaptivity.xmlutil.serialization.XML | ||||
| import uy.kohesive.injekt.api.InjektModule | ||||
| @@ -106,6 +108,9 @@ class AppModule(val app: Application) : InjektModule { | ||||
|             XML { | ||||
|                 unknownChildHandler = UnknownChildHandler { _, _, _, _, _ -> emptyList() } | ||||
|                 autoPolymorphic = true | ||||
|                 xmlDeclMode = XmlDeclMode.Charset | ||||
|                 indent = 4 | ||||
|                 xmlVersion = XmlVersion.XML10 | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,19 @@ import com.jakewharton.rxrelay.PublishRelay | ||||
| import eu.kanade.domain.chapter.model.Chapter | ||||
| import eu.kanade.domain.chapter.model.toDbChapter | ||||
| import eu.kanade.domain.download.service.DownloadPreferences | ||||
| import eu.kanade.domain.manga.model.ComicInfo | ||||
| import eu.kanade.domain.manga.model.ComicInfoGenre | ||||
| import eu.kanade.domain.manga.model.ComicInfoPenciller | ||||
| import eu.kanade.domain.manga.model.ComicInfoPublishingStatusTachiyomi | ||||
| import eu.kanade.domain.manga.model.ComicInfoSeries | ||||
| import eu.kanade.domain.manga.model.ComicInfoSummary | ||||
| import eu.kanade.domain.manga.model.ComicInfoTitle | ||||
| import eu.kanade.domain.manga.model.ComicInfoTranslator | ||||
| import eu.kanade.domain.manga.model.ComicInfoWeb | ||||
| import eu.kanade.domain.manga.model.ComicInfoWriter | ||||
| import eu.kanade.domain.manga.model.Manga | ||||
| import eu.kanade.domain.track.interactor.GetTracks | ||||
| import eu.kanade.domain.track.model.Track | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.cache.ChapterCache | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
| @@ -16,6 +28,8 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.UnmeteredSource | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList | ||||
| import eu.kanade.tachiyomi.util.lang.RetryWithDelay | ||||
| @@ -28,7 +42,9 @@ import eu.kanade.tachiyomi.util.storage.saveTo | ||||
| import eu.kanade.tachiyomi.util.system.ImageUtil | ||||
| import eu.kanade.tachiyomi.util.system.logcat | ||||
| import kotlinx.coroutines.async | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import logcat.LogPriority | ||||
| import nl.adaptivity.xmlutil.serialization.XML | ||||
| import okhttp3.Response | ||||
| import rx.Observable | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| @@ -36,8 +52,10 @@ import rx.schedulers.Schedulers | ||||
| import rx.subscriptions.CompositeSubscription | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.io.BufferedOutputStream | ||||
| import java.io.File | ||||
| import java.io.FileOutputStream | ||||
| import java.util.zip.CRC32 | ||||
| import java.util.zip.ZipEntry | ||||
| import java.util.zip.ZipOutputStream | ||||
| @@ -63,8 +81,14 @@ class Downloader( | ||||
|     private val sourceManager: SourceManager = Injekt.get(), | ||||
|     private val chapterCache: ChapterCache = Injekt.get(), | ||||
|     private val downloadPreferences: DownloadPreferences = Injekt.get(), | ||||
|     private val getTracks: GetTracks = Injekt.get(), | ||||
| ) { | ||||
|  | ||||
|     /** | ||||
|      * xml format used for ComicInfo files | ||||
|      */ | ||||
|     private val xml: XML by injectLazy() | ||||
|  | ||||
|     /** | ||||
|      * Store for persisting downloads across restarts. | ||||
|      */ | ||||
| @@ -513,6 +537,8 @@ class Downloader( | ||||
|         // Ensure that the chapter folder has all the images. | ||||
|         val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") || (it.name!!.contains("__") && !it.name!!.contains("__001.jpg")) } | ||||
|  | ||||
|         createComicInfoFile(tmpDir, download.manga, download.chapter) | ||||
|  | ||||
|         download.status = if (downloadedImages.size == download.pages!!.size) { | ||||
|             // Only rename the directory if it's downloaded. | ||||
|             if (downloadPreferences.saveChaptersAsCBZ().get()) { | ||||
| @@ -524,6 +550,8 @@ class Downloader( | ||||
|  | ||||
|             DiskUtil.createNoMediaFile(tmpDir, context) | ||||
|  | ||||
|             createComicInfoFile(mangaDir, download.manga, download.chapter) | ||||
|  | ||||
|             Download.State.DOWNLOADED | ||||
|         } else { | ||||
|             Download.State.ERROR | ||||
| @@ -564,6 +592,59 @@ 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 of the chapter to download. | ||||
|      * @param chapter the chapter to download | ||||
|      */ | ||||
|     private fun createComicInfoFile( | ||||
|         dir: UniFile, | ||||
|         manga: Manga, | ||||
|         chapter: SChapter, | ||||
|     ) { | ||||
|         File("${dir.filePath}/ComicInfo.xml").outputStream().also { | ||||
|             // Force overwrite old file | ||||
|             (it as? FileOutputStream)?.channel?.truncate(0) | ||||
|         }.use { it.write(getComicInfo(manga, chapter)) } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns a ByteArray containing the Manga Metadata of the chapter to download in ComicInfo.xml format | ||||
|      * | ||||
|      * @param manga the manga of the chapter to download. | ||||
|      * @param chapter the name of the chapter to download | ||||
|      */ | ||||
|     private fun getComicInfo(manga: Manga, chapter: SChapter): ByteArray { | ||||
|         val track: Track? = runBlocking { getTracks.await(manga.id).firstOrNull() } | ||||
|         val comicInfo = ComicInfo( | ||||
|             title = ComicInfoTitle(chapter.name), | ||||
|             series = ComicInfoSeries(manga.title), | ||||
|             summary = manga.description?.let { ComicInfoSummary(it) }, | ||||
|             writer = manga.author?.let { ComicInfoWriter(it) }, | ||||
|             penciller = manga.artist?.let { ComicInfoPenciller(it) }, | ||||
|             translator = chapter.scanlator?.let { ComicInfoTranslator(it) }, | ||||
|             genre = manga.genre?.let { ComicInfoGenre(it.joinToString()) }, | ||||
|             web = track?.remoteUrl?.let { ComicInfoWeb(it) }, | ||||
|             publishingStatusTachiyomi = when (manga.status) { | ||||
|                 SManga.ONGOING.toLong() -> ComicInfoPublishingStatusTachiyomi("Ongoing") | ||||
|                 SManga.COMPLETED.toLong() -> ComicInfoPublishingStatusTachiyomi("Completed") | ||||
|                 SManga.LICENSED.toLong() -> ComicInfoPublishingStatusTachiyomi("Licensed") | ||||
|                 SManga.PUBLISHING_FINISHED.toLong() -> ComicInfoPublishingStatusTachiyomi("Publishing finished") | ||||
|                 SManga.CANCELLED.toLong() -> ComicInfoPublishingStatusTachiyomi("Cancelled") | ||||
|                 SManga.ON_HIATUS.toLong() -> ComicInfoPublishingStatusTachiyomi("On hiatus") | ||||
|                 else -> ComicInfoPublishingStatusTachiyomi("Unknown") | ||||
|             }, | ||||
|             inker = null, | ||||
|             colorist = null, | ||||
|             letterer = null, | ||||
|             coverArtist = null, | ||||
|             tags = null, | ||||
|         ) | ||||
|         return xml.encodeToString(ComicInfo.serializer(), comicInfo).toByteArray() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Completes a download. This method is called in the main thread. | ||||
|      */ | ||||
|   | ||||
| @@ -269,6 +269,16 @@ class LocalSource( | ||||
|             .joinToString(", ") { it.trim() } | ||||
|             .takeIf { it.isNotEmpty() } | ||||
|             ?.let { manga.artist = it } | ||||
|  | ||||
|         manga.status = when (comicInfo.publishingStatusTachiyomi?.value) { | ||||
|             "Ongoing" -> SManga.ONGOING | ||||
|             "Completed" -> SManga.COMPLETED | ||||
|             "Licensed" -> SManga.LICENSED | ||||
|             "Publishing finished" -> SManga.PUBLISHING_FINISHED | ||||
|             "Cancelled" -> SManga.CANCELLED | ||||
|             "On hiatus" -> SManga.ON_HIATUS | ||||
|             else -> SManga.UNKNOWN | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Serializable | ||||
|   | ||||
		Reference in New Issue
	
	Block a user