diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt index e22e05fa..70fb7d31 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt @@ -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) } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt index 6dc65da5..3919ad3e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt @@ -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): List = + 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, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt index cbe66843..804c2f3f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt @@ -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, + ) { + addMangasToCategories(listOf(mangaId), categoryIds) + } - transaction { - if (notAlreadyInCategory()) { - CategoryMangaTable.insert { - it[CategoryMangaTable.category] = categoryId - it[CategoryMangaTable.manga] = mangaId + fun addMangasToCategories( + mangaIds: List, + categoryIds: List, + ) { + 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 } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt index ea834d50..f18d0732 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt @@ -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) { - val dbCategories = Category.getCategoryList() + private fun restoreCategories(backupCategories: List): Map { + 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, categoryMapping: Map, ) { 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, - categories: List, + categoryIds: List, history: List, tracks: List, - backupCategories: List, - categoryMapping: Map, ) { 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, + ): Pair, List>> { + 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, ) = 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, - categoryMapping: Map, + categoryIds: List, ) { - 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) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/Track.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/Track.kt index 55d693b8..7281ccda 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/Track.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/Track.kt @@ -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) = 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): List = 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