Add chapter lastReadAt to backups as BackupHistory (#1477)

* Add chapter lastReadAt to backups as BackupHistory

* MaxOrNull
This commit is contained in:
Mitchell Syer
2025-06-28 17:04:04 -04:00
committed by GitHub
parent 16d4893480
commit 8c4a2cb529
2 changed files with 47 additions and 24 deletions
@@ -34,6 +34,7 @@ import suwayomi.tachidesk.manga.impl.backup.BackupFlags
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupCategory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupChapter
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.BackupServerSettings
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSource
@@ -53,8 +54,8 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.io.InputStream
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
object ProtoBackupExport : ProtoBackupBase() {
private val logger = KotlinLogging.logger { }
@@ -222,7 +223,7 @@ object ProtoBackupExport : ProtoBackupBase() {
genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
thumbnailUrl = mangaRow[MangaTable.thumbnail_url],
dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
dateAdded = mangaRow[MangaTable.inLibraryAt].seconds.inWholeMilliseconds,
viewer = 0, // not supported in Tachidesk
updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy]),
)
@@ -233,7 +234,7 @@ object ProtoBackupExport : ProtoBackupBase() {
backupManga.meta = Manga.getMangaMetaMap(mangaId)
}
if (flags.includeChapters) {
if (flags.includeChapters || flags.includeHistory) {
val chapters =
transaction {
ChapterTable
@@ -244,27 +245,42 @@ object ProtoBackupExport : ProtoBackupBase() {
ChapterTable.toDataClass(it)
}
}
val chapterToMeta = Chapter.getChaptersMetaMaps(chapters.map { it.id })
if (flags.includeChapters) {
val chapterToMeta = Chapter.getChaptersMetaMaps(chapters.map { it.id })
backupManga.chapters =
chapters.map {
BackupChapter(
it.url,
it.name,
it.scanlator,
it.read,
it.bookmarked,
it.lastPageRead,
TimeUnit.SECONDS.toMillis(it.fetchedAt),
it.uploadDate,
it.chapterNumber,
chapters.size - it.index,
).apply {
if (flags.includeClientData) {
this.meta = chapterToMeta[it.id] ?: emptyMap()
backupManga.chapters =
chapters.map {
BackupChapter(
it.url,
it.name,
it.scanlator,
it.read,
it.bookmarked,
it.lastPageRead,
it.fetchedAt.seconds.inWholeMilliseconds,
it.uploadDate,
it.chapterNumber,
chapters.size - it.index,
).apply {
if (flags.includeClientData) {
this.meta = chapterToMeta[it.id] ?: emptyMap()
}
}
}
}
}
if (flags.includeHistory) {
backupManga.history =
chapters.mapNotNull {
if (it.lastReadAt > 0) {
BackupHistory(
url = it.url,
lastRead = it.lastReadAt.seconds.inWholeMilliseconds,
)
} else {
null
}
}
}
}
if (flags.includeCategories) {
@@ -61,6 +61,7 @@ import java.io.InputStream
import java.util.Date
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.max
import kotlin.time.Duration.Companion.milliseconds
import suwayomi.tachidesk.manga.impl.track.Track as Tracker
@@ -106,7 +107,7 @@ object ProtoBackupImport : ProtoBackupBase() {
) : BackupRestoreState()
}
private val backupRestoreIdToState = mutableMapOf<String, BackupRestoreState>()
private val backupRestoreIdToState = ConcurrentHashMap<String, BackupRestoreState>()
val notifyFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = DROP_OLDEST)
@@ -294,7 +295,6 @@ object ProtoBackupImport : ProtoBackupBase() {
}
}
@Suppress("UNUSED_PARAMETER") // TODO: remove
private fun restoreMangaData(
manga: BackupManga,
chapters: List<BackupChapter>,
@@ -368,7 +368,7 @@ object ProtoBackupImport : ProtoBackupBase() {
}
// merge chapter data
restoreMangaChapterData(mangaId, restoreMode, chapters)
restoreMangaChapterData(mangaId, restoreMode, chapters, history)
// merge categories
restoreMangaCategoryData(mangaId, categoryIds)
@@ -404,8 +404,10 @@ object ProtoBackupImport : ProtoBackupBase() {
mangaId: Int,
restoreMode: RestoreMode,
chapters: List<BackupChapter>,
history: List<BackupHistory>,
) = dbTransaction {
val (chaptersToInsert, chaptersToUpdateToDbChapter) = getMangaChapterToRestoreInfo(mangaId, restoreMode, chapters)
val historyByChapter = history.groupBy({ it.url }, { it.lastRead })
val insertedChapterIds =
ChapterTable
@@ -428,6 +430,8 @@ object ProtoBackupImport : ProtoBackupBase() {
this[ChapterTable.isBookmarked] = chapter.bookmark
this[ChapterTable.fetchedAt] = chapter.dateFetch.milliseconds.inWholeSeconds
this[ChapterTable.lastReadAt] = historyByChapter[chapter.url]?.maxOrNull()?.milliseconds?.inWholeSeconds ?: 0
}.map { it[ChapterTable.id].value }
if (chaptersToUpdateToDbChapter.isNotEmpty()) {
@@ -438,6 +442,9 @@ object ProtoBackupImport : ProtoBackupBase() {
this[ChapterTable.lastPageRead] =
max(backupChapter.lastPageRead, dbChapter[ChapterTable.lastPageRead]).coerceAtLeast(0)
this[ChapterTable.isBookmarked] = backupChapter.bookmark || dbChapter[ChapterTable.isBookmarked]
this[ChapterTable.lastReadAt] =
(historyByChapter[backupChapter.url]?.maxOrNull()?.milliseconds?.inWholeSeconds ?: 0)
.coerceAtLeast(dbChapter[ChapterTable.lastReadAt])
}
execute(this@dbTransaction)
}