restoring with clean db and not installed extensions work

This commit is contained in:
Aria Moradi
2021-08-21 00:18:03 +04:30
parent 2586202772
commit d70e68495a
3 changed files with 131 additions and 34 deletions
@@ -46,12 +46,12 @@ object Chapter {
} }
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> { private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
val mangaDetails = getManga(mangaId) val manga = getManga(mangaId)
val source = getHttpSource(mangaDetails.sourceId.toLong()) val source = getHttpSource(manga.sourceId.toLong())
val chapterList = source.fetchChapterList( val chapterList = source.fetchChapterList(
SManga.create().apply { SManga.create().apply {
title = mangaDetails.title title = manga.title
url = mangaDetails.url url = manga.url
} }
).awaitSingle() ).awaitSingle()
@@ -69,7 +69,7 @@ object Chapter {
it[scanlator] = fetchedChapter.scanlator it[scanlator] = fetchedChapter.scanlator
it[chapterIndex] = index + 1 it[chapterIndex] = index + 1
it[manga] = mangaId it[ChapterTable.manga] = mangaId
} }
} else { } else {
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) { ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
@@ -79,7 +79,7 @@ object Chapter {
it[scanlator] = fetchedChapter.scanlator it[scanlator] = fetchedChapter.scanlator
it[chapterIndex] = index + 1 it[chapterIndex] = index + 1
it[manga] = mangaId it[ChapterTable.manga] = mangaId
} }
} }
} }
@@ -11,20 +11,36 @@ import mu.KotlinLogging
import okio.buffer import okio.buffer
import okio.gzip import okio.gzip
import okio.source import okio.source
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.manga.impl.Category
import suwayomi.tachidesk.manga.impl.CategoryManga
import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator.ValidationResult import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator.ValidationResult
import suwayomi.tachidesk.manga.impl.backup.models.Chapter
import suwayomi.tachidesk.manga.impl.backup.models.Manga
import suwayomi.tachidesk.manga.impl.backup.models.Track
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.validate import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.validate
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupCategory import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupCategory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupHistory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupManga import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupManga
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
import suwayomi.tachidesk.manga.model.table.CategoryTable
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import java.io.InputStream import java.io.InputStream
import java.util.Date
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
object ProtoBackupImport : ProtoBackupBase() { object ProtoBackupImport : ProtoBackupBase() {
var restoreAmount = 0 private var restoreAmount = 0
private val errors = mutableListOf<Pair<Date, String>>()
suspend fun performRestore(sourceStream: InputStream): ValidationResult { suspend fun performRestore(sourceStream: InputStream): ValidationResult {
val backupString = sourceStream.source().gzip().buffer().use { it.readByteArray() } val backupString = sourceStream.source().gzip().buffer().use { it.readByteArray() }
val backup = parser.decodeFromByteArray(BackupSerializer, backupString) val backup = parser.decodeFromByteArray(BackupSerializer, backupString)
@@ -37,43 +53,124 @@ object ProtoBackupImport : ProtoBackupBase() {
restoreCategories(backup.backupCategories) restoreCategories(backup.backupCategories)
} }
val categoryMapping = transaction {
backup.backupCategories.associate {
it.order to CategoryTable.select { CategoryTable.name eq it.name }.first()[CategoryTable.id].value
}
}
// Store source mapping for error messages // Store source mapping for error messages
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap() sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
// Restore individual manga // Restore individual manga
backup.backupManga.forEach { backup.backupManga.forEach {
restoreManga(it, backup.backupCategories) restoreManga(it, backup.backupCategories, categoryMapping)
} }
// TODO: optionally trigger online library + tracker update logger.info {
"""
Restore Errors:
${ errors.joinToString("\n") { "${it.first} - ${it.second}" } }
Restore Summary:
- Missing Sources:
${validationResult.missingSources.joinToString("\n ")}
- Missing Trackers:
${validationResult.missingTrackers.joinToString("\n")}
""".trimIndent()
}
return validationResult return validationResult
} }
private fun restoreCategories(backupCategories: List<BackupCategory>) { // TODO private fun restoreCategories(backupCategories: List<BackupCategory>) {
// db.inTransaction { val dbCategories = Category.getCategoryList()
// backupManager.restoreCategories(backupCategories)
// } // Iterate over them and create missing categories
// backupCategories.forEach { category ->
// restoreProgress += 1 if (dbCategories.none { it.name == category.name }) {
// showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories)) Category.createCategory(category.name)
}
}
} }
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) { // TODO private fun restoreManga(
// val manga = backupManga.getMangaImpl() backupManga: BackupManga,
// val chapters = backupManga.getChaptersImpl() backupCategories: List<BackupCategory>,
// val categories = backupManga.categories categoryMapping: Map<Int, Int>
// val history = backupManga.history ) { // TODO
// val tracks = backupManga.getTrackingImpl() val manga = backupManga.getMangaImpl()
// val chapters = backupManga.getChaptersImpl()
// try { val categories = backupManga.categories
// restoreMangaData(manga, chapters, categories, history, tracks, backupCategories) val history = backupManga.history
// } catch (e: Exception) { val tracks = backupManga.getTrackingImpl()
// val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
// errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") try {
// } restoreMangaData(manga, chapters, categories, history, tracks, backupCategories, categoryMapping)
// } catch (e: Exception) {
// restoreProgress += 1 val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
// showRestoreProgress(restoreProgress, restoreAmount, manga.title) errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
}
}
private fun restoreMangaData(
manga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
categoryMapping: Map<Int, Int>
) {
val dbManga = transaction {
MangaTable.select { (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq manga.source) }
.firstOrNull()
}
if (dbManga == null) { // Manga not in database
transaction {
// insert manga to database
val mangaId = MangaTable.insertAndGetId {
it[url] = manga.url
it[title] = manga.title
it[artist] = manga.artist
it[author] = manga.author
it[description] = manga.description
it[genre] = manga.genre
it[status] = manga.status
it[thumbnail_url] = manga.thumbnail_url
it[sourceReference] = manga.source
it[initialized] = manga.description != null
}.value
// insert chapter data
chapters.forEach { chapter ->
ChapterTable.insert {
it[url] = chapter.url
it[name] = chapter.name
it[date_upload] = chapter.date_upload
it[chapter_number] = chapter.chapter_number
it[scanlator] = chapter.scanlator
it[chapterIndex] = chapter.source_order
it[ChapterTable.manga] = mangaId
}
}
// insert categories
categories.forEach { backupCategoryOrder ->
CategoryManga.addMangaToCategory(mangaId, categoryMapping[backupCategoryOrder]!!)
}
}
} else { // Manga in database
// merge chapter data
// merge categories
}
// TODO: insert/merge history
// TODO: insert/merge tracking
} }
} }
@@ -32,7 +32,7 @@ object GetHttpSource {
} }
val sourceRecord = transaction { val sourceRecord = transaction {
SourceTable.select { SourceTable.id eq sourceId }.first() SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
} }
val extensionId = sourceRecord[SourceTable.extension] val extensionId = sourceRecord[SourceTable.extension]