refactor(backup/restore/manga): Use SQLDelight for manga backup restorer

This commit is contained in:
Ahmad Ansori Palembani 2024-06-29 13:54:20 +07:00
parent 76414e60c1
commit 5ddc44dcd7
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
19 changed files with 276 additions and 133 deletions

View file

@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.ChapterImpl import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.chapter.ChapterUtil
@ -11,6 +9,7 @@ import kotlinx.serialization.protobuf.ProtoNumber
import yokai.data.manga.models.readingModeType import yokai.data.manga.models.readingModeType
import yokai.domain.library.custom.model.CustomMangaInfo import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.manga.models.Manga import yokai.domain.manga.models.Manga
import yokai.domain.track.models.Track
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@Serializable @Serializable
@ -56,27 +55,28 @@ data class BackupManga(
@ProtoNumber(804) var customDescription: String? = null, @ProtoNumber(804) var customDescription: String? = null,
@ProtoNumber(805) var customGenre: List<String>? = null, @ProtoNumber(805) var customGenre: List<String>? = null,
) { ) {
fun getMangaImpl(): MangaImpl { fun getMangaImpl(): Manga {
return MangaImpl().apply { return Manga(
url = this@BackupManga.url id = null,
title = this@BackupManga.title url = this@BackupManga.url,
artist = this@BackupManga.artist ogTitle = this@BackupManga.title,
author = this@BackupManga.author ogArtist = this@BackupManga.artist,
description = this@BackupManga.description ogAuthor = this@BackupManga.author,
genre = this@BackupManga.genre.joinToString() ogDescription = this@BackupManga.description,
status = this@BackupManga.status ogGenres = this@BackupManga.genre,
thumbnail_url = this@BackupManga.thumbnailUrl ogStatus = this@BackupManga.status,
favorite = this@BackupManga.favorite thumbnailUrl = this@BackupManga.thumbnailUrl,
source = this@BackupManga.source favorite = this@BackupManga.favorite,
date_added = this@BackupManga.dateAdded source = this@BackupManga.source,
viewer_flags = ( dateAdded = this@BackupManga.dateAdded,
viewerFlags = (
this@BackupManga.viewer_flags this@BackupManga.viewer_flags
?: this@BackupManga.viewer ?: this@BackupManga.viewer
).takeIf { it != 0 } ).takeIf { it != 0 }
?: -1 ?: -1,
chapter_flags = this@BackupManga.chapterFlags chapterFlags = this@BackupManga.chapterFlags,
update_strategy = this@BackupManga.updateStrategy updateStrategy = this@BackupManga.updateStrategy,
} )
} }
fun getChaptersImpl(): List<ChapterImpl> { fun getChaptersImpl(): List<ChapterImpl> {
@ -106,7 +106,7 @@ data class BackupManga(
return null return null
} }
fun getTrackingImpl(): List<TrackImpl> { fun getTrackingImpl(): List<Track> {
return tracking.map { return tracking.map {
it.getTrackingImpl() it.getTrackingImpl()
} }

View file

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import yokai.domain.track.models.Track import yokai.domain.track.models.Track
@ -30,24 +29,25 @@ data class BackupTracking(
@ProtoNumber(100) var mediaId: Long = 0, @ProtoNumber(100) var mediaId: Long = 0,
) { ) {
fun getTrackingImpl(): TrackImpl { fun getTrackingImpl(): Track {
return TrackImpl().apply { return Track(
sync_id = this@BackupTracking.syncId id = -1L,
media_id = if (this@BackupTracking.mediaIdInt != 0) { syncId = this@BackupTracking.syncId,
mediaId = if (this@BackupTracking.mediaIdInt != 0) {
this@BackupTracking.mediaIdInt.toLong() this@BackupTracking.mediaIdInt.toLong()
} else { } else {
this@BackupTracking.mediaId this@BackupTracking.mediaId
} },
library_id = this@BackupTracking.libraryId libraryId = this@BackupTracking.libraryId,
title = this@BackupTracking.title title = this@BackupTracking.title,
last_chapter_read = this@BackupTracking.lastChapterRead lastChapterRead = this@BackupTracking.lastChapterRead,
total_chapters = this@BackupTracking.totalChapters totalChapters = this@BackupTracking.totalChapters,
score = this@BackupTracking.score score = this@BackupTracking.score,
status = this@BackupTracking.status status = this@BackupTracking.status,
started_reading_date = this@BackupTracking.startedReadingDate startedReadingDate = this@BackupTracking.startedReadingDate,
finished_reading_date = this@BackupTracking.finishedReadingDate finishedReadingDate = this@BackupTracking.finishedReadingDate,
tracking_url = this@BackupTracking.trackingUrl trackingUrl = this@BackupTracking.trackingUrl,
} )
} }
companion object { companion object {

View file

@ -3,11 +3,7 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers
import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
@ -16,23 +12,39 @@ import eu.kanade.tachiyomi.util.manga.MangaUtil
import eu.kanade.tachiyomi.util.system.launchNow import eu.kanade.tachiyomi.util.system.launchNow
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import yokai.data.DatabaseHandler
import yokai.data.manga.models.copyFrom
import yokai.domain.category.interactor.GetCategories import yokai.domain.category.interactor.GetCategories
import yokai.domain.chapter.interactor.GetChapter import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.chapter.interactor.UpdateChapter
import yokai.domain.history.interactor.GetHistory
import yokai.domain.history.interactor.UpsertHistory
import yokai.domain.history.models.HistoryUpdate
import yokai.domain.library.custom.model.CustomMangaInfo import yokai.domain.library.custom.model.CustomMangaInfo
import yokai.domain.manga.category.interactor.DeleteMangaCategory
import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.GetManga
import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.InsertManga
import yokai.domain.manga.interactor.UpdateManga import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.Manga
import yokai.domain.manga.models.MangaCategory import yokai.domain.manga.models.MangaCategory
import yokai.domain.track.interactor.GetTrack
import yokai.domain.track.models.Track
import yokai.domain.track.models.TrackUpdate
import kotlin.math.max import kotlin.math.max
class MangaBackupRestorer( class MangaBackupRestorer(
private val db: DatabaseHelper = Injekt.get(), private val handler: DatabaseHandler = Injekt.get(),
private val customMangaManager: CustomMangaManager = Injekt.get(), private val customMangaManager: CustomMangaManager = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapter: GetChapter = Injekt.get(), private val getChapter: GetChapter = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val getHistory: GetHistory = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(), private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val deleteMangaCategory: DeleteMangaCategory = Injekt.get(),
private val getTrack: GetTrack = Injekt.get(),
) { ) {
suspend fun restoreManga( suspend fun restoreManga(
backupManga: BackupManga, backupManga: BackupManga,
@ -57,12 +69,13 @@ class MangaBackupRestorer(
} else { } else {
// Manga in database // Manga in database
// Copy information from manga already in database // Copy information from manga already in database
manga.id = dbManga.id val copy = manga.copy(
manga.filtered_scanlators = dbManga.filtered_scanlators id = dbManga.id,
manga.copyFrom(dbManga) filteredScanlators = dbManga.filteredScanlators
updateManga.await(manga.toMangaUpdate()) ).copyFrom(dbManga)
updateManga.await(copy.toMangaUpdate())
// Fetch rest of manga information // Fetch rest of manga information
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga) restoreExistingManga(copy, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga)
} }
} catch (e: Exception) { } catch (e: Exception) {
onError(manga, e) onError(manga, e)
@ -89,10 +102,10 @@ class MangaBackupRestorer(
filteredScanlators: List<String>, filteredScanlators: List<String>,
customManga: CustomMangaInfo?, customManga: CustomMangaInfo?,
) { ) {
val fetchedManga = manga.also { val fetchedManga = manga.copy(
it.initialized = it.description != null initialized = manga.ogDescription != null,
it.id = insertManga.await(it) id = insertManga.await(manga),
} )
fetchedManga.id ?: return fetchedManga.id ?: return
restoreChapters(fetchedManga, chapters) restoreChapters(fetchedManga, chapters)
@ -136,10 +149,30 @@ class MangaBackupRestorer(
} }
val newChapters = chapters.groupBy { it.id != null } val newChapters = chapters.groupBy { it.id != null }
newChapters[true]?.let { db.updateKnownChaptersBackup(it).executeAsBlocking() } newChapters[true]?.let { updateChapter.awaitAll(it.map{ c -> c.toChapterUpdate() }) }
newChapters[false]?.let { db.insertChapters(it).executeAsBlocking() } newChapters[false]?.let { insertChapters(it) }
} }
private suspend fun insertChapters(chapters: List<Chapter>) =
handler.await(true) {
chapters.forEach { chapter ->
chaptersQueries.insert(
mangaId = chapter.manga_id!!,
url = chapter.url,
name = chapter.name,
scanlator = chapter.scanlator,
read = chapter.read,
bookmark = chapter.bookmark,
lastPageRead = chapter.last_page_read.toLong(),
pagesLeft = chapter.pages_left.toLong(),
chapterNumber = chapter.chapter_number.toDouble(),
sourceOrder = chapter.source_order.toLong(),
dateFetch = chapter.date_fetch,
dateUpload = chapter.date_upload,
)
}
}
private suspend fun restoreExtras( private suspend fun restoreExtras(
manga: Manga, manga: Manga,
categories: List<Int>, categories: List<Int>,
@ -177,15 +210,19 @@ class MangaBackupRestorer(
dbCategories.firstOrNull { dbCategory -> dbCategories.firstOrNull { dbCategory ->
dbCategory.name == backupCategory.name dbCategory.name == backupCategory.name
}?.let { dbCategory -> }?.let { dbCategory ->
mangaCategoriesToUpdate += MangaCategory.create(manga, dbCategory) mangaCategoriesToUpdate += MangaCategory(manga.id!!, dbCategory.id?.toLong()!!)
} }
} }
} }
// Update database // Update database
if (mangaCategoriesToUpdate.isNotEmpty()) { if (mangaCategoriesToUpdate.isNotEmpty()) {
db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking() deleteMangaCategory.awaitByMangaId(manga.id!!)
db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking() handler.await(true) {
mangaCategoriesToUpdate.forEach { update ->
mangas_categoriesQueries.insert(update.mangaId, update.categoryId.toLong())
}
}
} }
} }
@ -196,28 +233,32 @@ class MangaBackupRestorer(
*/ */
internal suspend fun restoreHistoryForManga(history: List<BackupHistory>) { internal suspend fun restoreHistoryForManga(history: List<BackupHistory>) {
// List containing history to be updated // List containing history to be updated
val historyToBeUpdated = ArrayList<History>(history.size) val historyToBeUpdated = ArrayList<HistoryUpdate>(history.size)
for ((url, lastRead, readDuration) in history) { for ((url, lastRead, readDuration) in history) {
val dbHistory = db.getHistoryByChapterUrl(url).executeAsBlocking() val dbHistory = getHistory.awaitByChapterUrl(url)
// Check if history already in database and update // Check if history already in database and update
if (dbHistory != null) { if (dbHistory != null) {
dbHistory.apply { historyToBeUpdated.add(
last_read = max(lastRead, dbHistory.last_read) HistoryUpdate(
time_read = max(readDuration, dbHistory.time_read) chapterId = dbHistory.chapterId,
} readAt = max(lastRead, dbHistory.timeRead),
historyToBeUpdated.add(dbHistory) sessionReadDuration = max(readDuration, dbHistory.timeRead),
)
)
} else { } else {
// If not in database create // If not in database create
db.getChapter(url).executeAsBlocking()?.let { getChapter.await(url)?.let {
val historyToAdd = History.create(it).apply { historyToBeUpdated.add(
last_read = lastRead HistoryUpdate(
time_read = readDuration chapterId = it.id!!,
} readAt = lastRead,
historyToBeUpdated.add(historyToAdd) sessionReadDuration = readDuration,
)
)
} }
} }
} }
db.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() upsertHistory.awaitAll(historyToBeUpdated)
} }
/** /**
@ -228,43 +269,67 @@ class MangaBackupRestorer(
*/ */
private suspend fun restoreTrackForManga(manga: Manga, tracks: List<Track>) { private suspend fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
// Fix foreign keys with the current manga id // Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! } val actualTracks = tracks.map { it.copy(mangaId = manga.id!!) }
// Get tracks from database // Get tracks from database
val dbTracks = db.getTracks(manga).executeAsBlocking() val dbTracks = getTrack.awaitAllByMangaId(manga.id!!)
val trackToUpdate = mutableListOf<Track>() val trackToUpdate = mutableListOf<TrackUpdate>()
val trackToAdd = mutableListOf<Track>()
tracks.forEach { track -> actualTracks.forEach { track ->
var isInDatabase = false var isInDatabase = false
for (dbTrack in dbTracks) { for (dbTrack in dbTracks) {
if (track.sync_id == dbTrack.sync_id) { if (track.syncId == dbTrack.syncId) {
// The sync is already in the db, only update its fields val update = TrackUpdate(
if (track.media_id != dbTrack.media_id) { id = dbTrack.id,
dbTrack.media_id = track.media_id lastChapterRead = max(dbTrack.lastChapterRead, track.lastChapterRead),
} // The sync is already in the db, only update its fields
if (track.library_id != dbTrack.library_id) { mediaId = if (track.mediaId != dbTrack.mediaId) track.mediaId else null,
dbTrack.library_id = track.library_id libraryId = if (track.libraryId != dbTrack.libraryId) track.libraryId else null,
} )
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
isInDatabase = true isInDatabase = true
trackToUpdate.add(dbTrack) trackToUpdate.add(update)
break break
} }
} }
if (!isInDatabase) { if (!isInDatabase) {
// Insert new sync. Let the db assign the id // Insert new sync. Let the db assign the id
track.id = null trackToAdd.add(track.copy(id = -1L))
trackToUpdate.add(track)
} }
} }
// Update database // Update database
if (trackToUpdate.isNotEmpty()) { handler.await(true) {
db.insertTracks(trackToUpdate).executeAsBlocking() trackToUpdate.forEach { update ->
manga_syncQueries.update(
trackId = update.id,
mangaId = update.mangaId,
trackingUrl = update.trackingUrl,
lastChapterRead = update.lastChapterRead?.toDouble(),
mediaId = update.mediaId,
libraryId = update.libraryId,
)
}
trackToAdd.forEach { track ->
manga_syncQueries.insert(
mangaId = track.mangaId,
syncId = track.syncId.toLong(),
lastChapterRead = track.lastChapterRead.toDouble(),
mediaId = track.mediaId,
libraryId = track.libraryId,
title = track.title,
totalChapters = track.totalChapters.toLong(),
status = track.status.toLong(),
score = track.score.toDouble(),
trackingUrl = track.trackingUrl,
startDate = track.startedReadingDate,
finishDate = track.finishedReadingDate,
)
}
} }
} }
private suspend fun restoreFilteredScanlatorsForManga(manga: Manga, filteredScanlators: List<String>) { private suspend fun restoreFilteredScanlatorsForManga(manga: Manga, filteredScanlators: List<String>) {
val actualList = ChapterUtil.getScanlators(manga.filtered_scanlators) + filteredScanlators val actualList = ChapterUtil.getScanlators(manga.filteredScanlators) + filteredScanlators
MangaUtil.setScanlatorFilter(updateManga, manga, actualList.toSet()) MangaUtil.setScanlatorFilter(updateManga, manga, actualList.toSet())
} }
} }

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import yokai.domain.chapter.models.ChapterUpdate
import java.io.Serializable import java.io.Serializable
interface Chapter : SChapter, Serializable { interface Chapter : SChapter, Serializable {
@ -24,6 +25,23 @@ interface Chapter : SChapter, Serializable {
val isRecognizedNumber: Boolean val isRecognizedNumber: Boolean
get() = chapter_number >= 0f get() = chapter_number >= 0f
fun toChapterUpdate() =
ChapterUpdate(
id = id ?: -1,
mangaId = manga_id ?: -1,
url = url,
name = name,
scanlator = scanlator,
read = read,
bookmark = bookmark,
lastPageRead = last_page_read.toLong(),
pagesLeft = pages_left.toLong(),
chapterNumber = chapter_number.toDouble(),
sourceOrder = source_order.toLong(),
dateFetch = date_fetch,
dateUpload = date_upload
)
companion object { companion object {
fun create(): Chapter = ChapterImpl().apply { fun create(): Chapter = ChapterImpl().apply {

View file

@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.source.Source
import yokai.domain.manga.models.Manga import yokai.domain.manga.models.Manga
import yokai.domain.track.models.Track import yokai.domain.track.models.Track
import yokai.domain.track.models.TrackUpdate import yokai.domain.track.models.TrackUpdate
import kotlin.math.max
/** /**
* An Enhanced Track Service will never prompt the user to match a manga with the remote. * An Enhanced Track Service will never prompt the user to match a manga with the remote.
@ -45,6 +46,7 @@ interface EnhancedTrackService {
id = track.id, id = track.id,
mangaId = manga.id!!, mangaId = manga.id!!,
trackingUrl = manga.url, trackingUrl = manga.url,
lastChapterRead = max(dbTrack.lastChapterRead, track.lastChapterRead),
) )
} else { } else {
null null

View file

@ -1,17 +1,19 @@
package eu.kanade.tachiyomi.util.manga package eu.kanade.tachiyomi.util.manga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.chapter.ChapterUtil
import yokai.domain.manga.interactor.UpdateManga import yokai.domain.manga.interactor.UpdateManga
import yokai.domain.manga.models.Manga
import yokai.domain.manga.models.MangaUpdate import yokai.domain.manga.models.MangaUpdate
object MangaUtil { object MangaUtil {
suspend fun setScanlatorFilter(updateManga: UpdateManga, manga: Manga, filteredScanlators: Set<String>) { suspend fun setScanlatorFilter(updateManga: UpdateManga, manga: Manga, filteredScanlators: Set<String>) {
if (manga.id == null) return if (manga.id == null) return
manga.filtered_scanlators = updateManga.await(MangaUpdate(
if (filteredScanlators.isEmpty()) null else ChapterUtil.getScanlatorString(filteredScanlators) manga.id!!,
filteredScanlators = if (filteredScanlators.isEmpty()) null else ChapterUtil.getScanlatorString(
updateManga.await(MangaUpdate(manga.id!!, filteredScanlators = manga.filtered_scanlators)) filteredScanlators,
),
))
} }
} }

View file

@ -11,6 +11,8 @@ import yokai.domain.chapter.models.ChapterUpdate
class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepository { class ChapterRepositoryImpl(private val handler: DatabaseHandler) : ChapterRepository {
override suspend fun getChapter(chapterId: Long): Chapter? = override suspend fun getChapter(chapterId: Long): Chapter? =
handler.awaitOneOrNull { chaptersQueries.find(chapterId, Chapter::mapper) } handler.awaitOneOrNull { chaptersQueries.find(chapterId, Chapter::mapper) }
override suspend fun getChapter(url: String): Chapter? =
handler.awaitOneOrNull { chaptersQueries.findByUrl(url, Chapter::mapper) }
override suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter> = override suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter> =
handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, filterScanlators.toInt().toLong(), Chapter::mapper) } handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, filterScanlators.toInt().toLong(), Chapter::mapper) }

View file

@ -7,12 +7,15 @@ import yokai.domain.history.models.History
import yokai.domain.history.models.HistoryUpdate import yokai.domain.history.models.HistoryUpdate
class HistoryRepositoryImpl(private val handler: DatabaseHandler) : HistoryRepository { class HistoryRepositoryImpl(private val handler: DatabaseHandler) : HistoryRepository {
override suspend fun findByMangaId(mangaId: Long): List<History> = override suspend fun findAllByMangaId(mangaId: Long): List<History> =
handler.awaitList { historyQueries.findByMangaId(mangaId, History::mapper) } handler.awaitList { historyQueries.findByMangaId(mangaId, History::mapper) }
override suspend fun findBySourceUrl(url: String): List<History> = override suspend fun findAllByChapterUrl(url: String): List<History> =
handler.awaitList { historyQueries.findByChapterUrl(url, History::mapper) } handler.awaitList { historyQueries.findByChapterUrl(url, History::mapper) }
override suspend fun findByChapterUrl(url: String): History? =
handler.awaitOneOrNull { historyQueries.findByChapterUrl(url, History::mapper) }
override suspend fun upsert(update: HistoryUpdate): Boolean { override suspend fun upsert(update: HistoryUpdate): Boolean {
return try { return try {
partialUpsert(update) partialUpsert(update)

View file

@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_MANGA
import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_MANHUA import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_MANHUA
import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_MANHWA import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_MANHWA
import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_WEBTOON import eu.kanade.tachiyomi.data.database.models.Manga.Companion.TYPE_WEBTOON
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -20,23 +22,50 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.domain.chapter.interactor.GetChapter import yokai.domain.chapter.interactor.GetChapter
import yokai.domain.manga.models.Manga import yokai.domain.manga.models.Manga
import yokai.domain.manga.models.MangaUpdate
import yokai.i18n.MR import yokai.i18n.MR
import yokai.util.lang.getString import yokai.util.lang.getString
import java.util.* import java.util.*
fun Manga.toSManga() = SManga.create().also { fun Manga.toSManga() = SManga.create().also {
it.url = url it.url = url
it.title = title it.title = ogTitle
it.artist = artist it.artist = ogArtist
it.author = author it.author = ogAuthor
it.description = description it.description = ogDescription
it.genre = genres.joinToString() it.genre = ogGenres.joinToString()
it.status = status it.status = ogStatus
it.thumbnail_url = thumbnailUrl it.thumbnail_url = thumbnailUrl
it.initialized = initialized it.initialized = initialized
} }
fun Manga.copyFrom(other: Manga): Manga {
val title: String
if (other.ogTitle != ogTitle) {
title = other.ogTitle
val db: DownloadManager by injectLazy()
val provider = DownloadProvider(db.context)
provider.renameMangaFolder(ogTitle, other.title, source)
} else {
title = ogTitle
}
val author = other.ogAuthor ?: ogAuthor
val artist = other.ogArtist ?: ogArtist
val description = other.ogDescription ?: ogDescription
val genres = other.ogGenres.ifEmpty { ogGenres }
val status = other.ogStatus.takeIf { it != -1 } ?: ogStatus
val thumbnailUrl = other.thumbnailUrl ?: thumbnailUrl
return this.copy(
ogTitle = title,
ogAuthor = author,
ogArtist = artist,
ogDescription = description,
ogGenres = genres,
ogStatus = status,
thumbnailUrl = thumbnailUrl,
)
}
fun Manga.copyFrom(other: SManga): Manga { fun Manga.copyFrom(other: SManga): Manga {
val author = other.author ?: ogAuthor val author = other.author ?: ogAuthor
val artist = other.artist ?: ogArtist val artist = other.artist ?: ogArtist
@ -124,30 +153,6 @@ var Manga.dominantCoverColors: Pair<Int, Int>?
MangaCoverMetadata.addCoverColor(this, value.first, value.second) MangaCoverMetadata.addCoverColor(this, value.first, value.second)
} }
fun Manga.toMangaUpdate(): MangaUpdate {
return MangaUpdate(
id = id!!,
source = source,
url = url,
artist = artist,
author = author,
description = description,
genres = genres,
title = title,
status = status,
thumbnailUrl = thumbnailUrl,
favorite = favorite,
lastUpdate = lastUpdate,
initialized = initialized,
viewerFlags = viewerFlags,
hideTitle = hideTitle,
chapterFlags = chapterFlags,
dateAdded = dateAdded,
filteredScanlators = filteredScanlators,
updateStrategy = updateStrategy,
)
}
fun Manga.seriesType(context: Context, sourceManager: SourceManager? = null): String { fun Manga.seriesType(context: Context, sourceManager: SourceManager? = null): String {
return context.getString( return context.getString(
when (seriesType(sourceManager = sourceManager)) { when (seriesType(sourceManager = sourceManager)) {

View file

@ -40,6 +40,9 @@ class TrackRepositoryImpl(private val handler: DatabaseHandler) : TrackRepositor
trackId = update.id, trackId = update.id,
mangaId = update.mangaId, mangaId = update.mangaId,
trackingUrl = update.trackingUrl, trackingUrl = update.trackingUrl,
lastChapterRead = update.lastChapterRead?.toDouble(),
mediaId = update.mediaId,
libraryId = update.libraryId,
) )
} }
} }

View file

@ -6,6 +6,7 @@ import yokai.domain.chapter.models.ChapterUpdate
interface ChapterRepository { interface ChapterRepository {
suspend fun getChapter(chapterId: Long): Chapter? suspend fun getChapter(chapterId: Long): Chapter?
suspend fun getChapter(url: String): Chapter?
suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter> suspend fun getChapters(mangaId: Long, filterScanlators: Boolean): List<Chapter>
fun getChaptersAsFlow(mangaId: Long, filterScanlators: Boolean): Flow<List<Chapter>> fun getChaptersAsFlow(mangaId: Long, filterScanlators: Boolean): Flow<List<Chapter>>

View file

@ -7,6 +7,7 @@ class GetChapter(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun await(chapterId: Long) = chapterRepository.getChapter(chapterId) suspend fun await(chapterId: Long) = chapterRepository.getChapter(chapterId)
suspend fun await(url: String) = chapterRepository.getChapter(url)
suspend fun awaitAll(mangaId: Long, filterScanlators: Boolean) = chapterRepository.getChapters(mangaId, filterScanlators) suspend fun awaitAll(mangaId: Long, filterScanlators: Boolean) = chapterRepository.getChapters(mangaId, filterScanlators)
suspend fun awaitAll(manga: Manga, filterScanlators: Boolean? = null) = suspend fun awaitAll(manga: Manga, filterScanlators: Boolean? = null) =

View file

@ -4,8 +4,9 @@ import yokai.domain.history.models.History
import yokai.domain.history.models.HistoryUpdate import yokai.domain.history.models.HistoryUpdate
interface HistoryRepository { interface HistoryRepository {
suspend fun findByMangaId(mangaId: Long): List<History> suspend fun findAllByMangaId(mangaId: Long): List<History>
suspend fun findBySourceUrl(url: String): List<History> suspend fun findAllByChapterUrl(url: String): List<History>
suspend fun findByChapterUrl(url: String): History?
suspend fun upsert(update: HistoryUpdate): Boolean suspend fun upsert(update: HistoryUpdate): Boolean
suspend fun upsertAll(updates: List<HistoryUpdate>): Boolean suspend fun upsertAll(updates: List<HistoryUpdate>): Boolean
} }

View file

@ -5,6 +5,7 @@ import yokai.domain.history.HistoryRepository
class GetHistory( class GetHistory(
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
) { ) {
suspend fun awaitAllByMangaId(mangaId: Long) = historyRepository.findByMangaId(mangaId) suspend fun awaitAllByMangaId(mangaId: Long) = historyRepository.findAllByMangaId(mangaId)
suspend fun awaitAllBySourceUrl(url: String) = historyRepository.findBySourceUrl(url) suspend fun awaitAllByChapterUrl(url: String) = historyRepository.findAllByChapterUrl(url)
suspend fun awaitByChapterUrl(url: String) = historyRepository.findByChapterUrl(url)
} }

View file

@ -1,7 +1,7 @@
package yokai.domain.manga.interactor package yokai.domain.manga.interactor
import eu.kanade.tachiyomi.data.database.models.Manga
import yokai.domain.manga.MangaRepository import yokai.domain.manga.MangaRepository
import yokai.domain.manga.models.Manga
class InsertManga ( class InsertManga (
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,

View file

@ -26,6 +26,11 @@ SELECT *
FROM chapters FROM chapters
WHERE _id = :chapterId; WHERE _id = :chapterId;
findByUrl:
SELECT *
FROM chapters
WHERE url = :url;
getChaptersByMangaId: getChaptersByMangaId:
SELECT C.* SELECT C.*
FROM chapters AS C FROM chapters AS C

View file

@ -29,5 +29,12 @@ WHERE manga_id = :mangaId;
update: update:
UPDATE manga_sync SET UPDATE manga_sync SET
manga_id = coalesce(:mangaId, manga_id), manga_id = coalesce(:mangaId, manga_id),
remote_url = coalesce(:trackingUrl, remote_url) remote_id = coalesce(:mediaId, remote_id),
library_id = coalesce(:libraryId, library_id),
remote_url = coalesce(:trackingUrl, remote_url),
last_chapter_read = coalesce(:lastChapterRead, last_chapter_read)
WHERE _id = :trackId; WHERE _id = :trackId;
insert:
INSERT INTO manga_sync (manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score, remote_url, start_date, finish_date)
VALUES (:mangaId, :syncId, :mediaId, :libraryId, :title, :lastChapterRead, :totalChapters, :status, :score, :trackingUrl, :startDate, :finishDate);

View file

@ -126,6 +126,30 @@ data class Manga(
id?.let { vibrantCoverColorMap[it] = value } id?.let { vibrantCoverColorMap[it] = value }
} }
fun toMangaUpdate(): MangaUpdate {
return MangaUpdate(
id = id!!,
source = source,
url = url,
artist = ogArtist,
author = ogAuthor,
description = ogDescription,
genres = ogGenres,
title = ogTitle,
status = ogStatus,
thumbnailUrl = thumbnailUrl,
favorite = favorite,
lastUpdate = lastUpdate,
initialized = initialized,
viewerFlags = viewerFlags,
hideTitle = hideTitle,
chapterFlags = chapterFlags,
dateAdded = dateAdded,
filteredScanlators = filteredScanlators,
updateStrategy = updateStrategy,
)
}
companion object { companion object {
// Generic filter that does not filter anything // Generic filter that does not filter anything
const val SHOW_ALL = 0x00000000 const val SHOW_ALL = 0x00000000

View file

@ -4,4 +4,7 @@ data class TrackUpdate(
val id: Long, val id: Long,
val mangaId: Long? = null, val mangaId: Long? = null,
val trackingUrl: String? = null, val trackingUrl: String? = null,
val lastChapterRead: Float? = null,
val mediaId: Long? = null,
val libraryId: Long? = null,
) )