mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 06:42:07 +01:00
Feature/optimize backup import (#1270)
* Optimize restoring manga chapters
* Streamline restoring manga data
* Optimize restoring manga trackers
* Simplify passing manga category restore data
* Properly prevent mangas from getting added to default category
76595233fc never actually worked...
* Extract logic to add manga to categories from gql mutation
* Optimize restoring manga categories
* Optimize restoring categories
This commit is contained in:
@@ -6,7 +6,6 @@ import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
@@ -17,9 +16,8 @@ import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
||||
import suwayomi.tachidesk.graphql.types.CategoryType
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.manga.impl.Category
|
||||
import suwayomi.tachidesk.manga.impl.Category.DEFAULT_CATEGORY_ID
|
||||
import suwayomi.tachidesk.manga.impl.CategoryManga
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.isEmpty
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.isNotEmpty
|
||||
import suwayomi.tachidesk.manga.model.dataclass.IncludeOrExclude
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMetaTable
|
||||
@@ -398,28 +396,7 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
if (!patch.addToCategories.isNullOrEmpty()) {
|
||||
val newCategories =
|
||||
buildList {
|
||||
ids.filter { it != DEFAULT_CATEGORY_ID }.forEach { mangaId ->
|
||||
patch.addToCategories.forEach { categoryId ->
|
||||
val existingMapping =
|
||||
CategoryMangaTable
|
||||
.selectAll()
|
||||
.where {
|
||||
(CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId)
|
||||
}.isNotEmpty()
|
||||
|
||||
if (!existingMapping) {
|
||||
add(mangaId to categoryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CategoryMangaTable.batchInsert(newCategories) { (manga, category) ->
|
||||
this[CategoryMangaTable.manga] = manga
|
||||
this[CategoryMangaTable.category] = category
|
||||
}
|
||||
CategoryManga.addMangasToCategories(ids, patch.addToCategories)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import org.jetbrains.exposed.sql.SortOrder
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.andWhere
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
@@ -28,27 +28,36 @@ object Category {
|
||||
/**
|
||||
* The new category will be placed at the end of the list
|
||||
*/
|
||||
fun createCategory(name: String): Int {
|
||||
// creating a category named Default is illegal
|
||||
if (name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) return -1
|
||||
fun createCategory(name: String): Int = createCategories(listOf(name)).first()
|
||||
|
||||
return transaction {
|
||||
if (CategoryTable.selectAll().where { CategoryTable.name eq name }.firstOrNull() == null) {
|
||||
val newCategoryId =
|
||||
CategoryTable
|
||||
.insertAndGetId {
|
||||
it[CategoryTable.name] = name
|
||||
it[CategoryTable.order] = Int.MAX_VALUE
|
||||
}.value
|
||||
fun createCategories(names: List<String>): List<Int> =
|
||||
transaction {
|
||||
val categoryIdToName = getCategoryList().associate { it.id to it.name.lowercase() }
|
||||
|
||||
normalizeCategories()
|
||||
val categoriesToCreate =
|
||||
names
|
||||
.filter {
|
||||
!it.equals(DEFAULT_CATEGORY_NAME, true)
|
||||
}.filter { !categoryIdToName.values.contains(it.lowercase()) }
|
||||
|
||||
newCategoryId
|
||||
} else {
|
||||
-1
|
||||
val newCategoryIdsByName =
|
||||
CategoryTable
|
||||
.batchInsert(categoriesToCreate) {
|
||||
this[CategoryTable.name] = it
|
||||
this[CategoryTable.order] = Int.MAX_VALUE
|
||||
}.associate { it[CategoryTable.name] to it[CategoryTable.id].value }
|
||||
|
||||
normalizeCategories()
|
||||
|
||||
names.map {
|
||||
// creating a category named Default is illegal
|
||||
if (it.equals(DEFAULT_CATEGORY_NAME, true)) {
|
||||
DEFAULT_CATEGORY_ID
|
||||
} else {
|
||||
newCategoryIdsByName[it] ?: categoryIdToName.entries.find { (_, name) -> name.equals(it, true) }!!.key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCategory(
|
||||
categoryId: Int,
|
||||
|
||||
@@ -12,16 +12,15 @@ import org.jetbrains.exposed.sql.SortOrder
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.alias
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.count
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.leftJoin
|
||||
import org.jetbrains.exposed.sql.max
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.wrapAsExpression
|
||||
import suwayomi.tachidesk.manga.impl.Category.DEFAULT_CATEGORY_ID
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.isEmpty
|
||||
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||
@@ -35,22 +34,38 @@ object CategoryManga {
|
||||
mangaId: Int,
|
||||
categoryId: Int,
|
||||
) {
|
||||
if (categoryId == DEFAULT_CATEGORY_ID) return
|
||||
addMangaToCategories(mangaId, listOf(categoryId))
|
||||
}
|
||||
|
||||
fun notAlreadyInCategory() =
|
||||
CategoryMangaTable
|
||||
.selectAll()
|
||||
.where {
|
||||
(CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId)
|
||||
}.isEmpty()
|
||||
fun addMangaToCategories(
|
||||
mangaId: Int,
|
||||
categoryIds: List<Int>,
|
||||
) {
|
||||
addMangasToCategories(listOf(mangaId), categoryIds)
|
||||
}
|
||||
|
||||
transaction {
|
||||
if (notAlreadyInCategory()) {
|
||||
CategoryMangaTable.insert {
|
||||
it[CategoryMangaTable.category] = categoryId
|
||||
it[CategoryMangaTable.manga] = mangaId
|
||||
fun addMangasToCategories(
|
||||
mangaIds: List<Int>,
|
||||
categoryIds: List<Int>,
|
||||
) {
|
||||
val filteredCategoryIds = categoryIds.filter { it != DEFAULT_CATEGORY_ID }
|
||||
|
||||
val mangaIdsToCategoryIds = getMangasCategories(mangaIds).mapValues { it.value.map { category -> category.id } }
|
||||
val mangaIdsToNewCategoryIds =
|
||||
mangaIds.associateWith { mangaId ->
|
||||
filteredCategoryIds.filter { categoryId ->
|
||||
!(mangaIdsToCategoryIds[mangaId]?.contains(categoryId) ?: false)
|
||||
}
|
||||
}
|
||||
|
||||
val newMangaCategoryMappings =
|
||||
mangaIdsToNewCategoryIds.flatMap { (mangaId, newCategoryIds) ->
|
||||
newCategoryIds.map { mangaId to it }
|
||||
}
|
||||
|
||||
CategoryMangaTable.batchInsert(newMangaCategoryMappings) { (mangaId, categoryId) ->
|
||||
this[CategoryMangaTable.manga] = mangaId
|
||||
this[CategoryMangaTable.category] = categoryId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,17 +21,18 @@ import kotlinx.coroutines.sync.withLock
|
||||
import okio.buffer
|
||||
import okio.gzip
|
||||
import okio.source
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.graphql.types.toStatus
|
||||
import suwayomi.tachidesk.manga.impl.Category
|
||||
import suwayomi.tachidesk.manga.impl.CategoryManga
|
||||
import suwayomi.tachidesk.manga.impl.Manga.clearThumbnail
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.Chapter
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.Manga
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.ValidationResult
|
||||
@@ -45,7 +46,6 @@ import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrack
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrackRecordDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.TrackRecordDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.server.database.dbTransaction
|
||||
@@ -179,26 +179,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
restoreAmount = backup.backupManga.size + 1 // +1 for categories
|
||||
|
||||
updateRestoreState(id, BackupRestoreState.RestoringCategories(backup.backupManga.size))
|
||||
// Restore categories
|
||||
if (backup.backupCategories.isNotEmpty()) {
|
||||
restoreCategories(backup.backupCategories)
|
||||
}
|
||||
|
||||
val categoryMapping =
|
||||
transaction {
|
||||
backup.backupCategories.associate {
|
||||
val dbCategory =
|
||||
CategoryTable
|
||||
.selectAll()
|
||||
.where { CategoryTable.name eq it.name }
|
||||
.firstOrNull()
|
||||
val categoryId =
|
||||
dbCategory?.let { categoryResultRow ->
|
||||
categoryResultRow[CategoryTable.id].value
|
||||
} ?: Category.DEFAULT_CATEGORY_ID
|
||||
it.order to categoryId
|
||||
}
|
||||
}
|
||||
val categoryMapping = restoreCategories(backup.backupCategories)
|
||||
|
||||
// Store source mapping for error messages
|
||||
sourceMapping = backup.getSourceMap()
|
||||
@@ -216,7 +198,6 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
|
||||
restoreManga(
|
||||
backupManga = manga,
|
||||
backupCategories = backup.backupCategories,
|
||||
categoryMapping = categoryMapping,
|
||||
)
|
||||
}
|
||||
@@ -240,20 +221,16 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
return validationResult
|
||||
}
|
||||
|
||||
private fun restoreCategories(backupCategories: List<BackupCategory>) {
|
||||
val dbCategories = Category.getCategoryList()
|
||||
private fun restoreCategories(backupCategories: List<BackupCategory>): Map<Int, Int> {
|
||||
val categoryIds = Category.createCategories(backupCategories.map { it.name })
|
||||
|
||||
// Iterate over them and create missing categories
|
||||
backupCategories.forEach { category ->
|
||||
if (dbCategories.none { it.name == category.name }) {
|
||||
Category.createCategory(category.name)
|
||||
}
|
||||
return backupCategories.withIndex().associate { (index, backupCategory) ->
|
||||
backupCategory.order to categoryIds[index]
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreManga(
|
||||
backupManga: BackupManga,
|
||||
backupCategories: List<BackupCategory>,
|
||||
categoryMapping: Map<Int, Int>,
|
||||
) {
|
||||
val manga = backupManga.getMangaImpl()
|
||||
@@ -261,8 +238,10 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
val categories = backupManga.categories
|
||||
val history = backupManga.history
|
||||
|
||||
val dbCategoryIds = categories.map { categoryMapping[it]!! }
|
||||
|
||||
try {
|
||||
restoreMangaData(manga, chapters, categories, history, backupManga.tracking, backupCategories, categoryMapping)
|
||||
restoreMangaData(manga, chapters, dbCategoryIds, history, backupManga.tracking)
|
||||
} catch (e: Exception) {
|
||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
||||
@@ -273,11 +252,9 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
private fun restoreMangaData(
|
||||
manga: Manga,
|
||||
chapters: List<Chapter>,
|
||||
categories: List<Int>,
|
||||
categoryIds: List<Int>,
|
||||
history: List<BackupHistory>,
|
||||
tracks: List<BackupTracking>,
|
||||
backupCategories: List<BackupCategory>,
|
||||
categoryMapping: Map<Int, Int>,
|
||||
) {
|
||||
val dbManga =
|
||||
transaction {
|
||||
@@ -286,12 +263,13 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
.where { (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq manga.source) }
|
||||
.firstOrNull()
|
||||
}
|
||||
val restoreMode = if (dbManga != null) RestoreMode.EXISTING else RestoreMode.NEW
|
||||
|
||||
val mangaId =
|
||||
if (dbManga == null) { // Manga not in database
|
||||
transaction {
|
||||
// insert manga to database
|
||||
val mangaId =
|
||||
transaction {
|
||||
val mangaId =
|
||||
if (dbManga == null) {
|
||||
// insert manga to database
|
||||
MangaTable
|
||||
.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
@@ -313,47 +291,36 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
} else {
|
||||
val dbMangaId = dbManga[MangaTable.id].value
|
||||
|
||||
// delete thumbnail in case cached data still exists
|
||||
clearThumbnail(mangaId)
|
||||
// Merge manga data
|
||||
MangaTable.update({ MangaTable.id eq dbMangaId }) {
|
||||
it[artist] = manga.artist ?: dbManga[artist]
|
||||
it[author] = manga.author ?: dbManga[author]
|
||||
it[description] = manga.description ?: dbManga[description]
|
||||
it[genre] = manga.genre ?: dbManga[genre]
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url]
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
// insert chapter data
|
||||
restoreMangaChapterData(mangaId, RestoreMode.NEW, chapters)
|
||||
it[initialized] = dbManga[initialized] || manga.description != null
|
||||
|
||||
// insert categories
|
||||
restoreMangaCategoryData(mangaId, categories, categoryMapping)
|
||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||
|
||||
mangaId
|
||||
}
|
||||
} else { // Manga in database
|
||||
transaction {
|
||||
val mangaId = dbManga[MangaTable.id].value
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}
|
||||
|
||||
// Merge manga data
|
||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||
it[artist] = manga.artist ?: dbManga[artist]
|
||||
it[author] = manga.author ?: dbManga[author]
|
||||
it[description] = manga.description ?: dbManga[description]
|
||||
it[genre] = manga.genre ?: dbManga[genre]
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url]
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
it[initialized] = dbManga[initialized] || manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
dbMangaId
|
||||
}
|
||||
|
||||
// merge chapter data
|
||||
restoreMangaChapterData(mangaId, RestoreMode.EXISTING, chapters)
|
||||
// merge chapter data
|
||||
restoreMangaChapterData(mangaId, restoreMode, chapters)
|
||||
|
||||
// merge categories
|
||||
restoreMangaCategoryData(mangaId, categories, categoryMapping)
|
||||
// merge categories
|
||||
restoreMangaCategoryData(mangaId, categoryIds)
|
||||
|
||||
mangaId
|
||||
}
|
||||
mangaId
|
||||
}
|
||||
|
||||
restoreMangaTrackerData(mangaId, tracks)
|
||||
@@ -361,80 +328,72 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
// TODO: insert/merge history
|
||||
}
|
||||
|
||||
private fun getMangaChapterToRestoreInfo(
|
||||
mangaId: Int,
|
||||
restoreMode: RestoreMode,
|
||||
chapters: List<Chapter>,
|
||||
): Pair<List<Chapter>, List<Pair<Chapter, ResultRow>>> {
|
||||
val uniqueChapters = chapters.distinctBy { it.url }
|
||||
|
||||
if (restoreMode == RestoreMode.NEW) {
|
||||
return Pair(uniqueChapters, emptyList())
|
||||
}
|
||||
|
||||
val dbChaptersByUrl = ChapterTable.selectAll().where { ChapterTable.manga eq mangaId }.associateBy { it[ChapterTable.url] }
|
||||
|
||||
val (chaptersToUpdate, chaptersToInsert) = uniqueChapters.partition { dbChaptersByUrl.contains(it.url) }
|
||||
val chaptersToUpdateToDbChapter = chaptersToUpdate.map { it to dbChaptersByUrl[it.url]!! }
|
||||
|
||||
return chaptersToInsert to chaptersToUpdateToDbChapter
|
||||
}
|
||||
|
||||
private fun restoreMangaChapterData(
|
||||
mangaId: Int,
|
||||
restoreMode: RestoreMode,
|
||||
chapters: List<Chapter>,
|
||||
) = dbTransaction {
|
||||
val uniqueChapters = chapters.distinctBy { it.url }
|
||||
val chaptersLength = uniqueChapters.size
|
||||
val (chaptersToInsert, chaptersToUpdateToDbChapter) = getMangaChapterToRestoreInfo(mangaId, restoreMode, chapters)
|
||||
|
||||
if (restoreMode == RestoreMode.NEW) {
|
||||
ChapterTable.batchInsert(uniqueChapters) { chapter ->
|
||||
this[ChapterTable.url] = chapter.url
|
||||
this[ChapterTable.name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
this[ChapterTable.date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
this[ChapterTable.date_upload] = chapter.date_upload
|
||||
}
|
||||
this[ChapterTable.chapter_number] = chapter.chapter_number
|
||||
this[ChapterTable.scanlator] = chapter.scanlator
|
||||
|
||||
this[ChapterTable.sourceOrder] = chaptersLength - chapter.source_order
|
||||
this[ChapterTable.manga] = mangaId
|
||||
|
||||
this[ChapterTable.isRead] = chapter.read
|
||||
this[ChapterTable.lastPageRead] = chapter.last_page_read.coerceAtLeast(0)
|
||||
this[ChapterTable.isBookmarked] = chapter.bookmark
|
||||
|
||||
this[ChapterTable.fetchedAt] = TimeUnit.MILLISECONDS.toSeconds(chapter.date_fetch)
|
||||
ChapterTable.batchInsert(chaptersToInsert) { chapter ->
|
||||
this[ChapterTable.url] = chapter.url
|
||||
this[ChapterTable.name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
this[ChapterTable.date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
this[ChapterTable.date_upload] = chapter.date_upload
|
||||
}
|
||||
this[ChapterTable.chapter_number] = chapter.chapter_number
|
||||
this[ChapterTable.scanlator] = chapter.scanlator
|
||||
|
||||
this[ChapterTable.sourceOrder] = chaptersToInsert.size - chapter.source_order
|
||||
this[ChapterTable.manga] = mangaId
|
||||
|
||||
this[ChapterTable.isRead] = chapter.read
|
||||
this[ChapterTable.lastPageRead] = chapter.last_page_read.coerceAtLeast(0)
|
||||
this[ChapterTable.isBookmarked] = chapter.bookmark
|
||||
|
||||
this[ChapterTable.fetchedAt] = TimeUnit.MILLISECONDS.toSeconds(chapter.date_fetch)
|
||||
}
|
||||
|
||||
// merge chapter data
|
||||
val dbChapters = ChapterTable.selectAll().where { ChapterTable.manga eq mangaId }
|
||||
|
||||
uniqueChapters.forEach { chapter ->
|
||||
val dbChapter = dbChapters.find { it[ChapterTable.url] == chapter.url }
|
||||
|
||||
if (dbChapter == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = chapter.url
|
||||
it[name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
it[date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
it[date_upload] = chapter.date_upload
|
||||
}
|
||||
it[chapter_number] = chapter.chapter_number
|
||||
it[scanlator] = chapter.scanlator
|
||||
|
||||
it[sourceOrder] = chaptersLength - chapter.source_order
|
||||
it[ChapterTable.manga] = mangaId
|
||||
|
||||
it[isRead] = chapter.read
|
||||
it[lastPageRead] = chapter.last_page_read.coerceAtLeast(0)
|
||||
it[isBookmarked] = chapter.bookmark
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ (ChapterTable.url eq dbChapter[ChapterTable.url]) and (ChapterTable.manga eq mangaId) }) {
|
||||
it[isRead] = chapter.read || dbChapter[isRead]
|
||||
it[lastPageRead] = max(chapter.last_page_read, dbChapter[lastPageRead]).coerceAtLeast(0)
|
||||
it[isBookmarked] = chapter.bookmark || dbChapter[isBookmarked]
|
||||
if (chaptersToUpdateToDbChapter.isNotEmpty()) {
|
||||
BatchUpdateStatement(ChapterTable).apply {
|
||||
chaptersToUpdateToDbChapter.forEach { (backupChapter, dbChapter) ->
|
||||
addBatch(EntityID(dbChapter[ChapterTable.id].value, ChapterTable))
|
||||
this[ChapterTable.isRead] = backupChapter.read || dbChapter[ChapterTable.isRead]
|
||||
this[ChapterTable.lastPageRead] =
|
||||
max(backupChapter.last_page_read, dbChapter[ChapterTable.lastPageRead]).coerceAtLeast(0)
|
||||
this[ChapterTable.isBookmarked] = backupChapter.bookmark || dbChapter[ChapterTable.isBookmarked]
|
||||
}
|
||||
execute(this@dbTransaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreMangaCategoryData(
|
||||
mangaId: Int,
|
||||
categories: List<Int>,
|
||||
categoryMapping: Map<Int, Int>,
|
||||
categoryIds: List<Int>,
|
||||
) {
|
||||
categories.forEach { backupCategoryOrder ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryMapping[backupCategoryOrder]!!)
|
||||
}
|
||||
CategoryManga.addMangaToCategories(mangaId, categoryIds)
|
||||
}
|
||||
|
||||
private fun restoreMangaTrackerData(
|
||||
@@ -473,8 +432,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
}
|
||||
}.partition { (it.id ?: -1) > 0 }
|
||||
|
||||
existingTracks.forEach(Tracker::updateTrackRecord)
|
||||
newTracks.forEach(Tracker::insertTrackRecord)
|
||||
Tracker.updateTrackRecords(existingTracks)
|
||||
Tracker.insertTrackRecords(newTracks)
|
||||
}
|
||||
|
||||
private fun TrackRecordDataClass.forComparison() = this.copy(id = 0, mangaId = 0)
|
||||
|
||||
@@ -6,13 +6,15 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.SortOrder
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.DeletableTrackService
|
||||
@@ -25,6 +27,18 @@ import suwayomi.tachidesk.manga.model.dataclass.TrackSearchDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.TrackerDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.finishDate
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.lastChapterRead
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.libraryId
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.mangaId
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.remoteId
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.remoteUrl
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.score
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.startDate
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.status
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.title
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.totalChapters
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.trackerId
|
||||
import suwayomi.tachidesk.manga.model.table.TrackSearchTable
|
||||
import suwayomi.tachidesk.manga.model.table.insertAll
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
@@ -394,44 +408,49 @@ object Track {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTrackRecord(track: Track): Int =
|
||||
fun updateTrackRecord(track: Track) = updateTrackRecords(listOf(track))
|
||||
|
||||
fun updateTrackRecords(tracks: List<Track>) =
|
||||
transaction {
|
||||
TrackRecordTable.update(
|
||||
{
|
||||
(TrackRecordTable.mangaId eq track.manga_id) and
|
||||
(TrackRecordTable.trackerId eq track.sync_id)
|
||||
},
|
||||
) {
|
||||
it[remoteId] = track.media_id
|
||||
it[libraryId] = track.library_id
|
||||
it[title] = track.title
|
||||
it[lastChapterRead] = track.last_chapter_read.toDouble()
|
||||
it[totalChapters] = track.total_chapters
|
||||
it[status] = track.status
|
||||
it[score] = track.score.toDouble()
|
||||
it[remoteUrl] = track.tracking_url
|
||||
it[startDate] = track.started_reading_date
|
||||
it[finishDate] = track.finished_reading_date
|
||||
if (tracks.isNotEmpty()) {
|
||||
BatchUpdateStatement(TrackRecordTable).apply {
|
||||
tracks.forEach {
|
||||
addBatch(EntityID(it.id!!, TrackRecordTable))
|
||||
this[remoteId] = it.media_id
|
||||
this[libraryId] = it.library_id
|
||||
this[title] = it.title
|
||||
this[lastChapterRead] = it.last_chapter_read.toDouble()
|
||||
this[totalChapters] = it.total_chapters
|
||||
this[status] = it.status
|
||||
this[score] = it.score.toDouble()
|
||||
this[remoteUrl] = it.tracking_url
|
||||
this[startDate] = it.started_reading_date
|
||||
this[finishDate] = it.finished_reading_date
|
||||
}
|
||||
execute(this@transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun insertTrackRecord(track: Track): Int =
|
||||
fun insertTrackRecord(track: Track): Int = insertTrackRecords(listOf(track)).first()
|
||||
|
||||
fun insertTrackRecords(tracks: List<Track>): List<Int> =
|
||||
transaction {
|
||||
TrackRecordTable
|
||||
.insertAndGetId {
|
||||
it[mangaId] = track.manga_id
|
||||
it[trackerId] = track.sync_id
|
||||
it[remoteId] = track.media_id
|
||||
it[libraryId] = track.library_id
|
||||
it[title] = track.title
|
||||
it[lastChapterRead] = track.last_chapter_read.toDouble()
|
||||
it[totalChapters] = track.total_chapters
|
||||
it[status] = track.status
|
||||
it[score] = track.score.toDouble()
|
||||
it[remoteUrl] = track.tracking_url
|
||||
it[startDate] = track.started_reading_date
|
||||
it[finishDate] = track.finished_reading_date
|
||||
}.value
|
||||
.batchInsert(tracks) {
|
||||
this[mangaId] = it.manga_id
|
||||
this[trackerId] = it.sync_id
|
||||
this[remoteId] = it.media_id
|
||||
this[libraryId] = it.library_id
|
||||
this[title] = it.title
|
||||
this[lastChapterRead] = it.last_chapter_read.toDouble()
|
||||
this[totalChapters] = it.total_chapters
|
||||
this[status] = it.status
|
||||
this[score] = it.score.toDouble()
|
||||
this[remoteUrl] = it.tracking_url
|
||||
this[startDate] = it.started_reading_date
|
||||
this[finishDate] = it.finished_reading_date
|
||||
}.map { it[TrackRecordTable.id].value }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
Reference in New Issue
Block a user