Clean up manga restoring logic

Some behavior changes:
- It prioritizes new entries, then anything more recently updated
- It copies the more recently updated entry's metadata (description, thumbnail, etc.)
This commit is contained in:
arkon 2023-12-14 23:26:02 -05:00
parent d20a8fcf13
commit 58daedc89e
6 changed files with 150 additions and 203 deletions

View File

@ -68,18 +68,18 @@ class BackupNotifier(private val context: Context) {
} }
} }
fun showBackupComplete(unifile: UniFile) { fun showBackupComplete(file: UniFile) {
context.cancelNotification(Notifications.ID_BACKUP_PROGRESS) context.cancelNotification(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) { with(completeNotificationBuilder) {
setContentTitle(context.stringResource(MR.strings.backup_created)) setContentTitle(context.stringResource(MR.strings.backup_created))
setContentText(unifile.filePath ?: unifile.name) setContentText(file.filePath ?: file.name)
clearActions() clearActions()
addAction( addAction(
R.drawable.ic_share_24dp, R.drawable.ic_share_24dp,
context.stringResource(MR.strings.action_share), context.stringResource(MR.strings.action_share),
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri), NotificationReceiver.shareBackupPendingBroadcast(context, file.uri),
) )
show(Notifications.ID_BACKUP_COMPLETE) show(Notifications.ID_BACKUP_COMPLETE)
@ -88,13 +88,16 @@ class BackupNotifier(private val context: Context) {
fun showRestoreProgress( fun showRestoreProgress(
content: String = "", content: String = "",
contentTitle: String = context.stringResource(
MR.strings.restoring_backup,
),
progress: Int = 0, progress: Int = 0,
maxAmount: Int = 100, maxAmount: Int = 100,
sync: Boolean = false,
): NotificationCompat.Builder { ): NotificationCompat.Builder {
val builder = with(progressNotificationBuilder) { val builder = with(progressNotificationBuilder) {
val contentTitle = if (sync) {
context.stringResource(MR.strings.syncing_library)
} else {
context.stringResource(MR.strings.restoring_backup)
}
setContentTitle(contentTitle) setContentTitle(contentTitle)
if (!preferences.hideNotificationContent().get()) { if (!preferences.hideNotificationContent().get()) {
@ -133,10 +136,14 @@ class BackupNotifier(private val context: Context) {
errorCount: Int, errorCount: Int,
path: String?, path: String?,
file: String?, file: String?,
contentTitle: String = context.stringResource( sync: Boolean,
MR.strings.restore_completed,
),
) { ) {
val contentTitle = if (sync) {
context.stringResource(MR.strings.library_sync_complete)
} else {
context.stringResource(MR.strings.restore_completed)
}
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
val timeString = context.stringResource( val timeString = context.stringResource(

View File

@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.source.model.copyFrom
import eu.kanade.tachiyomi.source.sourcePreferences import eu.kanade.tachiyomi.source.sourcePreferences
import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.BackupUtil
import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.createFileInCacheDir
@ -27,7 +26,6 @@ import tachiyomi.core.preference.AndroidPreferenceStore
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Manga_sync import tachiyomi.data.Manga_sync
import tachiyomi.data.Mangas
import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@ -35,6 +33,8 @@ import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.history.model.HistoryUpdate import tachiyomi.domain.history.model.HistoryUpdate
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.FetchInterval import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@ -50,23 +50,25 @@ import kotlin.math.max
class BackupRestorer( class BackupRestorer(
private val context: Context, private val context: Context,
private val notifier: BackupNotifier, private val notifier: BackupNotifier,
private val handler: DatabaseHandler = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val fetchInterval: FetchInterval = Injekt.get(),
private val preferenceStore: PreferenceStore = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
) { ) {
private val handler: DatabaseHandler = Injekt.get()
private val updateManga: UpdateManga = Injekt.get()
private val getCategories: GetCategories = Injekt.get()
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get()
private val fetchInterval: FetchInterval = Injekt.get()
private val preferenceStore: PreferenceStore = Injekt.get()
private val libraryPreferences: LibraryPreferences = Injekt.get()
private var now = ZonedDateTime.now()
private var currentFetchWindow = fetchInterval.getWindow(now)
private var restoreAmount = 0 private var restoreAmount = 0
private var restoreProgress = 0 private var restoreProgress = 0
private var now = ZonedDateTime.now()
private var currentFetchWindow = fetchInterval.getWindow(now)
/** /**
* Mapping of source ID to source name from backup data * Mapping of source ID to source name from backup data
*/ */
@ -76,27 +78,22 @@ class BackupRestorer(
suspend fun syncFromBackup(uri: Uri, sync: Boolean) { suspend fun syncFromBackup(uri: Uri, sync: Boolean) {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
restoreProgress = 0
errors.clear()
performRestore(uri, sync) prepareState()
restoreFromFile(uri, sync)
val endTime = System.currentTimeMillis() val endTime = System.currentTimeMillis()
val time = endTime - startTime val time = endTime - startTime
val logFile = writeErrorLog() val logFile = writeErrorLog()
if (sync) {
notifier.showRestoreComplete( notifier.showRestoreComplete(
time, time,
errors.size, errors.size,
logFile.parent, logFile.parent,
logFile.name, logFile.name,
contentTitle = context.stringResource(MR.strings.library_sync_complete), sync,
) )
} else {
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
}
} }
private fun writeErrorLog(): File { private fun writeErrorLog(): File {
@ -118,7 +115,12 @@ class BackupRestorer(
return File("") return File("")
} }
private suspend fun performRestore(uri: Uri, sync: Boolean) { private fun prepareState() {
now = ZonedDateTime.now()
currentFetchWindow = fetchInterval.getWindow(now)
}
private suspend fun restoreFromFile(uri: Uri, sync: Boolean) {
val backup = BackupUtil.decodeBackup(context, uri) val backup = BackupUtil.decodeBackup(context, uri)
restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs
@ -126,8 +128,6 @@ class BackupRestorer(
// Store source mapping for error messages // Store source mapping for error messages
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
now = ZonedDateTime.now()
currentFetchWindow = fetchInterval.getWindow(now)
coroutineScope { coroutineScope {
ensureActive() ensureActive()
@ -139,8 +139,8 @@ class BackupRestorer(
ensureActive() ensureActive()
restoreSourcePreferences(backup.backupSourcePreferences) restoreSourcePreferences(backup.backupSourcePreferences)
// Restore individual manga backup.backupManga.sortByNew()
backup.backupManga.forEach { .forEach {
ensureActive() ensureActive()
restoreManga(it, backup.backupCategories, sync) restoreManga(it, backup.backupCategories, sync)
} }
@ -149,6 +149,17 @@ class BackupRestorer(
} }
} }
private suspend fun List<BackupManga>.sortByNew(): List<BackupManga> {
val urlsBySource = handler.awaitList { mangasQueries.getAllMangaSourceAndUrl() }
.groupBy({ it.source }, { it.url })
return this
.sortedWith(
compareBy<BackupManga> { it.url in urlsBySource[it.source].orEmpty() }
.then(compareByDescending { it.lastModifiedAt }),
)
}
private suspend fun restoreCategories(backupCategories: List<BackupCategory>) { private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
if (backupCategories.isNotEmpty()) { if (backupCategories.isNotEmpty()) {
val dbCategories = getCategories.await() val dbCategories = getCategories.await()
@ -170,75 +181,72 @@ class BackupRestorer(
} }
restoreProgress += 1 restoreProgress += 1
showRestoreProgress( notifier.showRestoreProgress(
context.stringResource(MR.strings.categories),
restoreProgress, restoreProgress,
restoreAmount, restoreAmount,
context.stringResource(MR.strings.categories), false,
context.stringResource(MR.strings.restoring_backup),
) )
} }
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, sync: Boolean) { private suspend fun restoreManga(
val manga = backupManga.getMangaImpl() backupManga: BackupManga,
val chapters = backupManga.getChaptersImpl() backupCategories: List<BackupCategory>,
val categories = backupManga.categories.map { it.toInt() } sync: Boolean,
val history = ) {
backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } + backupManga.history
val tracks = backupManga.getTrackingImpl()
try { try {
val dbManga = getMangaFromDatabase(manga.url, manga.source) val dbManga = findExistingManga(backupManga)
val manga = backupManga.getMangaImpl()
val restoredManga = if (dbManga == null) { val restoredManga = if (dbManga == null) {
// Manga not in database restoreNewManga(manga)
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
} else { } else {
// Manga in database restoreExistingManga(manga, dbManga)
// Copy information from manga already in database
val updatedManga = restoreExistingManga(manga, dbManga)
// Fetch rest of manga information
restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
} }
updateManga.awaitUpdateFetchInterval(restoredManga, now, currentFetchWindow)
restoreMangaDetails(
manga = restoredManga,
chapters = backupManga.getChaptersImpl(),
categories = backupManga.categories,
backupCategories = backupCategories,
history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } +
backupManga.history,
tracks = backupManga.getTrackingImpl(),
)
} catch (e: Exception) { } catch (e: Exception) {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString() val sourceName = sourceMapping[backupManga.source] ?: backupManga.source.toString()
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") errors.add(Date() to "${backupManga.title} [$sourceName]: ${e.message}")
} }
restoreProgress += 1 restoreProgress += 1
if (sync) { notifier.showRestoreProgress(backupManga.title, restoreProgress, restoreAmount, sync)
showRestoreProgress( }
restoreProgress,
restoreAmount, private suspend fun findExistingManga(backupManga: BackupManga): Manga? {
manga.title, return getMangaByUrlAndSourceId.await(backupManga.url, backupManga.source)
context.stringResource(MR.strings.syncing_library), }
)
private suspend fun restoreExistingManga(manga: Manga, dbManga: Manga): Manga {
return if (manga.lastModifiedAt > dbManga.lastModifiedAt) {
updateManga(dbManga.copyFrom(manga).copy(id = dbManga.id))
} else { } else {
showRestoreProgress( updateManga(manga.copyFrom(dbManga).copy(id = dbManga.id))
restoreProgress, }
restoreAmount, }
manga.title,
context.stringResource(MR.strings.restoring_backup), private fun Manga.copyFrom(newer: Manga): Manga {
return this.copy(
favorite = this.favorite || newer.favorite,
author = newer.author,
artist = newer.artist,
description = newer.description,
genre = newer.genre,
thumbnailUrl = newer.thumbnailUrl,
status = newer.status,
initialized = this.initialized || newer.initialized,
) )
} }
}
/** private suspend fun updateManga(manga: Manga): Manga {
* Returns manga
*
* @return [Manga], null if not found
*/
private suspend fun getMangaFromDatabase(url: String, source: Long): Mangas? {
return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, source) }
}
private suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas): Manga {
var updatedManga = manga.copy(id = dbManga._id)
updatedManga = updatedManga.copyFrom(dbManga)
updateManga(updatedManga)
return updatedManga
}
private suspend fun updateManga(manga: Manga): Long {
handler.await(true) { handler.await(true) {
mangasQueries.update( mangasQueries.update(
source = manga.source, source = manga.source,
@ -263,28 +271,16 @@ class BackupRestorer(
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode), updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
) )
} }
return manga.id return manga
} }
/** private suspend fun restoreNewManga(
* Fetches manga information
*
* @param manga manga that needs updating
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private suspend fun restoreExistingManga(
manga: Manga, manga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
): Manga { ): Manga {
val fetchedManga = restoreNewManga(manga) return manga.copy(
restoreChapters(fetchedManga, chapters) initialized = manga.description != null,
restoreExtras(fetchedManga, categories, history, tracks, backupCategories) id = insertManga(manga),
return fetchedManga )
} }
private suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) { private suspend fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
@ -318,13 +314,10 @@ class BackupRestorer(
} }
val (existingChapters, newChapters) = processed.partition { it.id > 0 } val (existingChapters, newChapters) = processed.partition { it.id > 0 }
updateKnownChapters(existingChapters)
insertChapters(newChapters) insertChapters(newChapters)
updateKnownChapters(existingChapters)
} }
/**
* Inserts list of chapters
*/
private suspend fun insertChapters(chapters: List<Chapter>) { private suspend fun insertChapters(chapters: List<Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
@ -345,9 +338,6 @@ class BackupRestorer(
} }
} }
/**
* Updates a list of chapters with known database ids
*/
private suspend fun updateKnownChapters(chapters: List<Chapter>) { private suspend fun updateKnownChapters(chapters: List<Chapter>) {
handler.await(true) { handler.await(true) {
chapters.forEach { chapter -> chapters.forEach { chapter ->
@ -369,19 +359,6 @@ class BackupRestorer(
} }
} }
/**
* Fetches manga information
*
* @param manga manga that needs updating
* @return Updated manga info.
*/
private suspend fun restoreNewManga(manga: Manga): Manga {
return manga.copy(
initialized = manga.description != null,
id = insertManga(manga),
)
}
/** /**
* Inserts manga and returns id * Inserts manga and returns id
* *
@ -414,29 +391,20 @@ class BackupRestorer(
} }
} }
private suspend fun restoreNewManga( private suspend fun restoreMangaDetails(
backupManga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
): Manga {
restoreChapters(backupManga, chapters)
restoreExtras(backupManga, categories, history, tracks, backupCategories)
return backupManga
}
private suspend fun restoreExtras(
manga: Manga, manga: Manga,
categories: List<Int>, chapters: List<Chapter>,
categories: List<Long>,
backupCategories: List<BackupCategory>,
history: List<BackupHistory>, history: List<BackupHistory>,
tracks: List<Track>, tracks: List<Track>,
backupCategories: List<BackupCategory>, ): Manga {
) { restoreChapters(manga, chapters)
restoreCategories(manga, categories, backupCategories) restoreCategories(manga, categories, backupCategories)
restoreHistory(history) restoreHistory(history)
restoreTracking(manga, tracks) restoreTracking(manga, tracks)
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
return manga
} }
/** /**
@ -445,23 +413,24 @@ class BackupRestorer(
* @param manga the manga whose categories have to be restored. * @param manga the manga whose categories have to be restored.
* @param categories the categories to restore. * @param categories the categories to restore.
*/ */
private suspend fun restoreCategories(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) { private suspend fun restoreCategories(
manga: Manga,
categories: List<Long>,
backupCategories: List<BackupCategory>,
) {
val dbCategories = getCategories.await() val dbCategories = getCategories.await()
val mangaCategoriesToUpdate = mutableListOf<Pair<Long, Long>>() val dbCategoriesByName = dbCategories.associateBy { it.name }
categories.forEach { backupCategoryOrder -> val backupCategoriesByOrder = backupCategories.associateBy { it.order }
backupCategories.firstOrNull {
it.order == backupCategoryOrder.toLong() val mangaCategoriesToUpdate = categories.mapNotNull { backupCategoryOrder ->
}?.let { backupCategory -> backupCategoriesByOrder[backupCategoryOrder]?.let { backupCategory ->
dbCategories.firstOrNull { dbCategory -> dbCategoriesByName[backupCategory.name]?.let { dbCategory ->
dbCategory.name == backupCategory.name Pair(manga.id, dbCategory.id)
}?.let { dbCategory ->
mangaCategoriesToUpdate.add(Pair(manga.id, dbCategory.id))
} }
} }
} }
// Update database
if (mangaCategoriesToUpdate.isNotEmpty()) { if (mangaCategoriesToUpdate.isNotEmpty()) {
handler.await(true) { handler.await(true) {
mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id) mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id)
@ -472,11 +441,6 @@ class BackupRestorer(
} }
} }
/**
* Restore history from Json
*
* @param history list containing history to be restored
*/
private suspend fun restoreHistory(history: List<BackupHistory>) { private suspend fun restoreHistory(history: List<BackupHistory>) {
// List containing history to be updated // List containing history to be updated
val toUpdate = mutableListOf<HistoryUpdate>() val toUpdate = mutableListOf<HistoryUpdate>()
@ -496,7 +460,7 @@ class BackupRestorer(
), ),
) )
} else { } else {
// If not in database create // If not in database, create
handler handler
.awaitOneOrNull { chaptersQueries.getChapterByUrl(url) } .awaitOneOrNull { chaptersQueries.getChapterByUrl(url) }
?.let { ?.let {
@ -521,12 +485,6 @@ class BackupRestorer(
} }
} }
/**
* Restores the sync of a manga.
*
* @param manga the manga whose sync have to be restored.
* @param tracks the track list to restore.
*/
private suspend fun restoreTracking(manga: Manga, tracks: List<Track>) { private suspend fun restoreTracking(manga: Manga, tracks: List<Track>) {
// Get tracks from database // Get tracks from database
val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id) } val dbTracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id) }
@ -611,11 +569,11 @@ class BackupRestorer(
BackupCreateJob.setupTask(context) BackupCreateJob.setupTask(context)
restoreProgress += 1 restoreProgress += 1
showRestoreProgress( notifier.showRestoreProgress(
context.stringResource(MR.strings.app_settings),
restoreProgress, restoreProgress,
restoreAmount, restoreAmount,
context.stringResource(MR.strings.app_settings), false,
context.stringResource(MR.strings.restoring_backup),
) )
} }
@ -626,11 +584,11 @@ class BackupRestorer(
} }
restoreProgress += 1 restoreProgress += 1
showRestoreProgress( notifier.showRestoreProgress(
context.stringResource(MR.strings.source_settings),
restoreProgress, restoreProgress,
restoreAmount, restoreAmount,
context.stringResource(MR.strings.source_settings), false,
context.stringResource(MR.strings.restoring_backup),
) )
} }
@ -674,8 +632,4 @@ class BackupRestorer(
} }
} }
} }
private fun showRestoreProgress(progress: Int, amount: Int, title: String, contentTitle: String) {
notifier.showRestoreProgress(title, contentTitle, progress, amount)
}
} }

View File

@ -1,18 +0,0 @@
package eu.kanade.tachiyomi.source.model
import tachiyomi.data.Mangas
import tachiyomi.domain.manga.model.Manga
fun Manga.copyFrom(other: Mangas): Manga {
var manga = this
other.author?.let { manga = manga.copy(author = it) }
other.artist?.let { manga = manga.copy(artist = it) }
other.description?.let { manga = manga.copy(description = it) }
other.genre?.let { manga = manga.copy(genre = it) }
other.thumbnail_url?.let { manga = manga.copy(thumbnailUrl = it) }
manga = manga.copy(status = other.status)
if (!initialized) {
manga = manga.copy(initialized = other.initialized)
}
return manga
}

View File

@ -74,7 +74,7 @@ class DeepLinkScreenModel(
} }
private suspend fun getMangaFromSManga(sManga: SManga, sourceId: Long): Manga { private suspend fun getMangaFromSManga(sManga: SManga, sourceId: Long): Manga {
return getMangaByUrlAndSourceId.awaitManga(sManga.url, sourceId) return getMangaByUrlAndSourceId.await(sManga.url, sourceId)
?: networkToLocalManga.await(sManga.toDomainManga(sourceId)) ?: networkToLocalManga.await(sManga.toDomainManga(sourceId))
} }

View File

@ -70,6 +70,10 @@ getAllManga:
SELECT * SELECT *
FROM mangas; FROM mangas;
getAllMangaSourceAndUrl:
SELECT source, url
FROM mangas;
getMangasWithFavoriteTimestamp: getMangasWithFavoriteTimestamp:
SELECT * SELECT *
FROM mangas FROM mangas

View File

@ -6,7 +6,7 @@ import tachiyomi.domain.manga.repository.MangaRepository
class GetMangaByUrlAndSourceId( class GetMangaByUrlAndSourceId(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
) { ) {
suspend fun awaitManga(url: String, sourceId: Long): Manga? { suspend fun await(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId) return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
} }
} }