Split the universal update queries into case-specific ones

Source-owned fields move out of MangaUpdate/ChapterUpdate into MangaRemoteUpdate/ChapterRemoteUpdate, which only UpdateMangaFromRemote and SyncChaptersWithSource write. Backup restore gets its own updateFromBackup queries for entries and chapters, writing only what a backup carries.
This commit is contained in:
AntsyLich
2026-09-27 21:50:38 +06:00
parent c67a33f3d4
commit a4abb6292b
14 changed files with 158 additions and 111 deletions
@@ -19,8 +19,8 @@ import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
import tachiyomi.domain.chapter.interactor.UpdateChapter import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.ChapterRemoteUpdate
import tachiyomi.domain.chapter.model.NoChaptersException import tachiyomi.domain.chapter.model.NoChaptersException
import tachiyomi.domain.chapter.model.toChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.chapter.service.ChapterRecognition import tachiyomi.domain.chapter.service.ChapterRecognition
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
@@ -78,7 +78,7 @@ class SyncChaptersWithSource(
val dbChapters = getChaptersByMangaId.await(manga.id) val dbChapters = getChaptersByMangaId.await(manga.id)
val newChapters = mutableListOf<Chapter>() val newChapters = mutableListOf<Chapter>()
val updatedChapters = mutableListOf<Chapter>() val updatedChapters = mutableListOf<ChapterRemoteUpdate>()
val removedChapters = dbChapters.filterNot { dbChapter -> val removedChapters = dbChapters.filterNot { dbChapter ->
sourceChapters.any { sourceChapter -> sourceChapters.any { sourceChapter ->
dbChapter.url == sourceChapter.url dbChapter.url == sourceChapter.url
@@ -130,18 +130,17 @@ class SyncChaptersWithSource(
downloadManager.renameChapter(source, manga, dbChapter, chapter) downloadManager.renameChapter(source, manga, dbChapter, chapter)
} }
var toChangeChapter = dbChapter.copy( updatedChapters.add(
name = chapter.name, ChapterRemoteUpdate(
chapterNumber = chapter.chapterNumber, id = dbChapter.id,
scanlator = chapter.scanlator, name = chapter.name,
sourceOrder = chapter.sourceOrder, chapterNumber = chapter.chapterNumber,
memo = chapter.memo, scanlator = chapter.scanlator,
sourceOrder = chapter.sourceOrder,
dateUpload = chapter.dateUpload.takeIf { it != 0L },
memo = chapter.memo,
),
) )
if (chapter.dateUpload != 0L) {
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
}
updatedChapters.add(toChangeChapter)
} }
} }
} }
@@ -221,8 +220,7 @@ class SyncChaptersWithSource(
} }
if (updatedChapters.isNotEmpty()) { if (updatedChapters.isNotEmpty()) {
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() } updateChapter.awaitAllRemote(updatedChapters)
updateChapter.awaitAll(chapterUpdates)
} }
updateManga.awaitUpdateFetchInterval(manga, timeZone, now, fetchWindow) updateManga.awaitUpdateFetchInterval(manga, timeZone, now, fetchWindow)
@@ -107,9 +107,7 @@ class MangaRestorer(
} }
private suspend fun updateManga(manga: Manga): Manga { private suspend fun updateManga(manga: Manga): Manga {
database.mangasQueries.update( database.mangasQueries.updateFromBackup(
source = manga.source,
url = manga.url,
artist = manga.artist, artist = manga.artist,
author = manga.author, author = manga.author,
description = manga.description, description = manga.description,
@@ -119,8 +117,6 @@ class MangaRestorer(
thumbnailUrl = manga.thumbnailUrl, thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite, favorite = manga.favorite,
lastUpdate = manga.lastUpdate, lastUpdate = manga.lastUpdate,
nextUpdate = null,
calculateInterval = null,
initialized = manga.initialized, initialized = manga.initialized,
viewer = manga.viewerFlags, viewer = manga.viewerFlags,
chapterFlags = manga.chapterFlags, chapterFlags = manga.chapterFlags,
@@ -205,18 +201,10 @@ class MangaRestorer(
private suspend fun updateExistingChapters(chapters: List<Chapter>) { private suspend fun updateExistingChapters(chapters: List<Chapter>) {
database.transaction { database.transaction {
chapters.forEach { chapter -> chapters.forEach { chapter ->
database.chaptersQueries.update( database.chaptersQueries.updateFromBackup(
mangaId = null,
url = null,
name = null,
scanlator = null,
read = chapter.read, read = chapter.read,
bookmark = chapter.bookmark, bookmark = chapter.bookmark,
lastPageRead = chapter.lastPageRead, lastPageRead = chapter.lastPageRead,
chapterNumber = null,
sourceOrder = null,
dateFetch = null,
dateUpload = null,
chapterId = chapter.id, chapterId = chapter.id,
memo = chapter.memo, memo = chapter.memo,
) )
@@ -17,7 +17,7 @@ import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.manga.model.MangaRemoteUpdate
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
@@ -122,8 +122,8 @@ class UpdateMangaFromRemote(
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() } val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
val success = mangaRepository.update( val success = mangaRepository.updateRemote(
MangaUpdate( MangaRemoteUpdate(
id = localManga.id, id = localManga.id,
title = title, title = title,
coverLastModified = coverLastModified, coverLastModified = coverLastModified,
@@ -15,6 +15,7 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.data.Database import tachiyomi.data.Database
import tachiyomi.data.subscribeToList import tachiyomi.data.subscribeToList
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.ChapterRemoteUpdate
import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
@@ -61,23 +62,32 @@ class ChapterRepositoryImpl(
partialUpdate(*chapterUpdates.toTypedArray()) partialUpdate(*chapterUpdates.toTypedArray())
} }
override suspend fun updateAllRemote(chapterUpdates: List<ChapterRemoteUpdate>) {
database.transaction {
chapterUpdates.forEach { chapterUpdate ->
database.chaptersQueries.updateRemote(
name = chapterUpdate.name,
scanlator = chapterUpdate.scanlator,
chapterNumber = chapterUpdate.chapterNumber,
sourceOrder = chapterUpdate.sourceOrder,
dateUpload = chapterUpdate.dateUpload,
chapterId = chapterUpdate.id,
memo = chapterUpdate.memo,
)
}
}
}
private suspend fun partialUpdate(vararg chapterUpdates: ChapterUpdate) { private suspend fun partialUpdate(vararg chapterUpdates: ChapterUpdate) {
database.transaction { database.transaction {
chapterUpdates.forEach { chapterUpdate -> chapterUpdates.forEach { chapterUpdate ->
database.chaptersQueries.update( database.chaptersQueries.update(
mangaId = chapterUpdate.mangaId, mangaId = chapterUpdate.mangaId,
url = chapterUpdate.url,
name = chapterUpdate.name,
scanlator = chapterUpdate.scanlator,
read = chapterUpdate.read, read = chapterUpdate.read,
bookmark = chapterUpdate.bookmark, bookmark = chapterUpdate.bookmark,
lastPageRead = chapterUpdate.lastPageRead, lastPageRead = chapterUpdate.lastPageRead,
chapterNumber = chapterUpdate.chapterNumber,
sourceOrder = chapterUpdate.sourceOrder,
dateFetch = chapterUpdate.dateFetch, dateFetch = chapterUpdate.dateFetch,
dateUpload = chapterUpdate.dateUpload,
chapterId = chapterUpdate.id, chapterId = chapterUpdate.id,
memo = chapterUpdate.memo,
) )
} }
} }
@@ -19,6 +19,7 @@ import tachiyomi.data.subscribeToOne
import tachiyomi.data.subscribeToOneOrNull import tachiyomi.data.subscribeToOneOrNull
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaRemoteUpdate
import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.MangaWithChapterCount
import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.manga.repository.MangaRepository
@@ -185,32 +186,43 @@ class MangaRepositoryImpl(
} }
} }
override suspend fun updateRemote(update: MangaRemoteUpdate): Boolean {
return try {
database.mangasQueries.updateRemote(
artist = update.artist,
author = update.author,
description = update.description,
genre = update.genre,
title = update.title,
status = update.status,
thumbnailUrl = update.thumbnailUrl,
initialized = update.initialized,
coverLastModified = update.coverLastModified,
updateStrategy = update.updateStrategy,
memo = update.memo,
mangaId = update.id,
)
true
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
false
}
}
private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) { private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) {
database.transaction { database.transaction {
mangaUpdates.forEach { value -> mangaUpdates.forEach { value ->
database.mangasQueries.update( database.mangasQueries.update(
source = value.source,
url = value.url,
artist = value.artist,
author = value.author,
description = value.description,
genre = value.genre,
title = value.title,
status = value.status,
thumbnailUrl = value.thumbnailUrl,
favorite = value.favorite, favorite = value.favorite,
lastUpdate = value.lastUpdate, lastUpdate = value.lastUpdate,
nextUpdate = value.nextUpdate, nextUpdate = value.nextUpdate,
calculateInterval = value.fetchInterval?.toLong(), calculateInterval = value.fetchInterval?.toLong(),
initialized = value.initialized,
viewer = value.viewerFlags, viewer = value.viewerFlags,
chapterFlags = value.chapterFlags, chapterFlags = value.chapterFlags,
coverLastModified = value.coverLastModified, coverLastModified = value.coverLastModified,
dateAdded = value.dateAdded, dateAdded = value.dateAdded,
mangaId = value.id, mangaId = value.id,
updateStrategy = value.updateStrategy,
notes = value.notes, notes = value.notes,
memo = value.memo,
) )
} }
} }
@@ -80,15 +80,26 @@ RETURNING _id;
update: update:
UPDATE chapters UPDATE chapters
SET manga_id = coalesce(:mangaId, manga_id), SET manga_id = coalesce(:mangaId, manga_id),
url = coalesce(:url, url),
name = coalesce(:name, name),
scanlator = coalesce(:scanlator, scanlator),
read = coalesce(:read, read), read = coalesce(:read, read),
bookmark = coalesce(:bookmark, bookmark), bookmark = coalesce(:bookmark, bookmark),
last_page_read = coalesce(:lastPageRead, last_page_read), last_page_read = coalesce(:lastPageRead, last_page_read),
chapter_number = coalesce(:chapterNumber, chapter_number), date_fetch = coalesce(:dateFetch, date_fetch)
source_order = coalesce(:sourceOrder, source_order), WHERE _id = :chapterId;
date_fetch = coalesce(:dateFetch, date_fetch),
date_upload = coalesce(:dateUpload, date_upload), updateFromBackup:
UPDATE chapters
SET read = coalesce(:read, read),
bookmark = coalesce(:bookmark, bookmark),
last_page_read = coalesce(:lastPageRead, last_page_read),
memo = coalesce(:memo, memo) memo = coalesce(:memo, memo)
WHERE _id = :chapterId; WHERE _id = :chapterId;
updateRemote:
UPDATE chapters
SET name = :name,
scanlator = coalesce(:scanlator, scanlator),
chapter_number = :chapterNumber,
source_order = :sourceOrder,
date_upload = coalesce(:dateUpload, date_upload),
memo = :memo
WHERE _id = :chapterId;
@@ -203,8 +203,19 @@ RETURNING _id;
update: update:
UPDATE mangas SET UPDATE mangas SET
source = coalesce(:source, source), favorite = coalesce(:favorite, favorite),
url = coalesce(:url, url), last_update = coalesce(:lastUpdate, last_update),
next_update = coalesce(:nextUpdate, next_update),
viewer = coalesce(:viewer, viewer),
chapter_flags = coalesce(:chapterFlags, chapter_flags),
cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
date_added = coalesce(:dateAdded, date_added),
calculate_interval = coalesce(:calculateInterval, calculate_interval),
notes = coalesce(:notes, notes)
WHERE _id = :mangaId;
updateFromBackup:
UPDATE mangas SET
artist = coalesce(:artist, artist), artist = coalesce(:artist, artist),
author = coalesce(:author, author), author = coalesce(:author, author),
description = coalesce(:description, description), description = coalesce(:description, description),
@@ -214,18 +225,31 @@ UPDATE mangas SET
thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url), thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url),
favorite = coalesce(:favorite, favorite), favorite = coalesce(:favorite, favorite),
last_update = coalesce(:lastUpdate, last_update), last_update = coalesce(:lastUpdate, last_update),
next_update = coalesce(:nextUpdate, next_update),
initialized = coalesce(:initialized, initialized), initialized = coalesce(:initialized, initialized),
viewer = coalesce(:viewer, viewer), viewer = coalesce(:viewer, viewer),
chapter_flags = coalesce(:chapterFlags, chapter_flags), chapter_flags = coalesce(:chapterFlags, chapter_flags),
cover_last_modified = coalesce(:coverLastModified, cover_last_modified), cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
date_added = coalesce(:dateAdded, date_added), date_added = coalesce(:dateAdded, date_added),
update_strategy = coalesce(:updateStrategy, update_strategy), update_strategy = coalesce(:updateStrategy, update_strategy),
calculate_interval = coalesce(:calculateInterval, calculate_interval),
notes = coalesce(:notes, notes), notes = coalesce(:notes, notes),
memo = coalesce(:memo, memo) memo = coalesce(:memo, memo)
WHERE _id = :mangaId; WHERE _id = :mangaId;
updateRemote:
UPDATE mangas SET
artist = coalesce(:artist, artist),
author = coalesce(:author, author),
description = coalesce(:description, description),
genre = coalesce(:genre, genre),
title = coalesce(:title, title),
status = :status,
thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url),
initialized = :initialized,
cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
update_strategy = :updateStrategy,
memo = :memo
WHERE _id = :mangaId;
insertNetworkManga { insertNetworkManga {
-- Insert the manga if it doesn't exist already -- Insert the manga if it doesn't exist already
INSERT INTO mangas( INSERT INTO mangas(
@@ -3,6 +3,7 @@ package tachiyomi.domain.chapter.interactor
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.chapter.model.ChapterRemoteUpdate
import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository import tachiyomi.domain.chapter.repository.ChapterRepository
@@ -26,4 +27,12 @@ class UpdateChapter(
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
} }
} }
suspend fun awaitAllRemote(chapterUpdates: List<ChapterRemoteUpdate>) {
try {
chapterRepository.updateAllRemote(chapterUpdates)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
} }
@@ -0,0 +1,13 @@
package tachiyomi.domain.chapter.model
import kotlinx.serialization.json.JsonObject
data class ChapterRemoteUpdate(
val id: Long,
val name: String,
val scanlator: String?,
val chapterNumber: Double,
val dateUpload: Long?,
val sourceOrder: Long,
val memo: JsonObject,
)
@@ -1,7 +1,5 @@
package tachiyomi.domain.chapter.model package tachiyomi.domain.chapter.model
import kotlinx.serialization.json.JsonObject
data class ChapterUpdate( data class ChapterUpdate(
val id: Long, val id: Long,
val mangaId: Long? = null, val mangaId: Long? = null,
@@ -9,29 +7,15 @@ data class ChapterUpdate(
val bookmark: Boolean? = null, val bookmark: Boolean? = null,
val lastPageRead: Long? = null, val lastPageRead: Long? = null,
val dateFetch: Long? = null, val dateFetch: Long? = null,
val sourceOrder: Long? = null,
val url: String? = null,
val name: String? = null,
val dateUpload: Long? = null,
val chapterNumber: Double? = null,
val scanlator: String? = null,
val memo: JsonObject? = null,
) )
fun Chapter.toChapterUpdate(): ChapterUpdate { fun Chapter.toChapterUpdate(): ChapterUpdate {
return ChapterUpdate( return ChapterUpdate(
id, id = id,
mangaId, mangaId = mangaId,
read, read = read,
bookmark, bookmark = bookmark,
lastPageRead, lastPageRead = lastPageRead,
dateFetch, dateFetch = dateFetch,
sourceOrder,
url,
name,
dateUpload,
chapterNumber,
scanlator,
memo,
) )
} }
@@ -2,6 +2,7 @@ package tachiyomi.domain.chapter.repository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.ChapterRemoteUpdate
import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.model.ChapterUpdate
interface ChapterRepository { interface ChapterRepository {
@@ -12,6 +13,8 @@ interface ChapterRepository {
suspend fun updateAll(chapterUpdates: List<ChapterUpdate>) suspend fun updateAll(chapterUpdates: List<ChapterUpdate>)
suspend fun updateAllRemote(chapterUpdates: List<ChapterRemoteUpdate>)
suspend fun removeChaptersWithIds(chapterIds: List<Long>) suspend fun removeChaptersWithIds(chapterIds: List<Long>)
suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter> suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter>
@@ -0,0 +1,19 @@
package tachiyomi.domain.manga.model
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.json.JsonObject
data class MangaRemoteUpdate(
val id: Long,
val title: String?,
val author: String?,
val artist: String?,
val description: String?,
val genre: List<String>?,
val status: Long,
val thumbnailUrl: String?,
val updateStrategy: UpdateStrategy,
val memo: JsonObject,
val initialized: Boolean,
val coverLastModified: Long?,
)
@@ -1,11 +1,7 @@
package tachiyomi.domain.manga.model package tachiyomi.domain.manga.model
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.json.JsonObject
data class MangaUpdate( data class MangaUpdate(
val id: Long, val id: Long,
val source: Long? = null,
val favorite: Boolean? = null, val favorite: Boolean? = null,
val lastUpdate: Long? = null, val lastUpdate: Long? = null,
val nextUpdate: Long? = null, val nextUpdate: Long? = null,
@@ -14,24 +10,12 @@ data class MangaUpdate(
val viewerFlags: Long? = null, val viewerFlags: Long? = null,
val chapterFlags: Long? = null, val chapterFlags: Long? = null,
val coverLastModified: Long? = null, val coverLastModified: Long? = null,
val url: String? = null,
val title: String? = null,
val artist: String? = null,
val author: String? = null,
val description: String? = null,
val genre: List<String>? = null,
val status: Long? = null,
val thumbnailUrl: String? = null,
val updateStrategy: UpdateStrategy? = null,
val initialized: Boolean? = null,
val notes: String? = null, val notes: String? = null,
val memo: JsonObject? = null,
) )
fun Manga.toMangaUpdate(): MangaUpdate { fun Manga.toMangaUpdate(): MangaUpdate {
return MangaUpdate( return MangaUpdate(
id = id, id = id,
source = source,
favorite = favorite, favorite = favorite,
lastUpdate = lastUpdate, lastUpdate = lastUpdate,
nextUpdate = nextUpdate, nextUpdate = nextUpdate,
@@ -40,17 +24,6 @@ fun Manga.toMangaUpdate(): MangaUpdate {
viewerFlags = viewerFlags, viewerFlags = viewerFlags,
chapterFlags = chapterFlags, chapterFlags = chapterFlags,
coverLastModified = coverLastModified, coverLastModified = coverLastModified,
url = url,
title = title,
artist = artist,
author = author,
description = description,
genre = genre,
status = status,
thumbnailUrl = thumbnailUrl,
updateStrategy = updateStrategy,
initialized = initialized,
notes = notes, notes = notes,
memo = memo,
) )
} }
@@ -3,6 +3,7 @@ package tachiyomi.domain.manga.repository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaRemoteUpdate
import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.MangaWithChapterCount
@@ -40,6 +41,8 @@ interface MangaRepository {
suspend fun update(update: MangaUpdate): Boolean suspend fun update(update: MangaUpdate): Boolean
suspend fun updateRemote(update: MangaRemoteUpdate): Boolean
suspend fun updateAll(mangaUpdates: List<MangaUpdate>): Boolean suspend fun updateAll(mangaUpdates: List<MangaUpdate>): Boolean
suspend fun insertNetworkManga(manga: List<Manga>): List<Manga> suspend fun insertNetworkManga(manga: List<Manga>): List<Manga>