mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor(backup/restore/manga): Use SQLDelight for manga backup restorer
This commit is contained in:
parent
76414e60c1
commit
5ddc44dcd7
19 changed files with 276 additions and 133 deletions
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>
|
||||||
|
|
|
@ -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) =
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue