diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 15bad474..6a53416a 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -7,6 +7,7 @@ import java.time.Instant plugins { application + kotlin("plugin.serialization") id("com.github.johnrengelman.shadow") version "7.0.0" id("org.jmailen.kotlinter") version "3.4.3" id("com.github.gmazzo.buildconfig") version "3.0.2" @@ -143,7 +144,8 @@ tasks { freeCompilerArgs = listOf( "-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi" + "-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi", + "-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi", ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt index a50c255c..ce400b8e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt @@ -20,7 +20,7 @@ object BackupController { fun legacyImport(ctx: Context) { ctx.result( JavalinSetup.future { - LegacyBackupImport.restoreLegacyBackup(ctx.bodyAsInputStream()) + LegacyBackupImport.performRestore(ctx.bodyAsInputStream()) } ) } @@ -29,7 +29,7 @@ object BackupController { fun legacyImportFile(ctx: Context) { ctx.result( JavalinSetup.future { - LegacyBackupImport.restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content) + LegacyBackupImport.performRestore(ctx.uploadedFile("backup.json")!!.content) } ) } @@ -78,7 +78,7 @@ object BackupController { fun protobufImport(ctx: Context) { // TODO ctx.result( JavalinSetup.future { - LegacyBackupImport.restoreLegacyBackup(ctx.bodyAsInputStream()) + LegacyBackupImport.performRestore(ctx.bodyAsInputStream()) } ) } @@ -87,7 +87,7 @@ object BackupController { fun protobufImportFile(ctx: Context) { // TODO ctx.result( JavalinSetup.future { - LegacyBackupImport.restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content) + LegacyBackupImport.performRestore(ctx.uploadedFile("backup.json")!!.content) } ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/AbstractBackupValidator.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/AbstractBackupValidator.kt new file mode 100644 index 00000000..a207de49 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/AbstractBackupValidator.kt @@ -0,0 +1,12 @@ +package suwayomi.tachidesk.manga.impl.backup + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +abstract class AbstractBackupValidator { + data class ValidationResult(val missingSources: List, val missingTrackers: List) +} \ No newline at end of file diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupImport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupImport.kt index aaf90173..7f5d01c5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupImport.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupImport.kt @@ -1,5 +1,12 @@ package suwayomi.tachidesk.manga.impl.backup.legacy +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + import com.github.salomonbrys.kotson.fromJson import com.google.gson.JsonArray import com.google.gson.JsonElement @@ -15,7 +22,7 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update import suwayomi.tachidesk.manga.impl.Category.createCategory import suwayomi.tachidesk.manga.impl.Category.getCategoryList -import suwayomi.tachidesk.manga.impl.backup.legacy.LegacyBackupValidator.ValidationResult +import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator.ValidationResult import suwayomi.tachidesk.manga.impl.backup.legacy.LegacyBackupValidator.validate import suwayomi.tachidesk.manga.impl.backup.legacy.models.Backup import suwayomi.tachidesk.manga.impl.backup.legacy.models.DHistory @@ -32,17 +39,10 @@ import suwayomi.tachidesk.manga.model.table.MangaTable import java.io.InputStream import java.util.Date -/* - * Copyright (C) Contributors to the Suwayomi project - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - private val logger = KotlinLogging.logger {} object LegacyBackupImport : LegacyBackupBase() { - suspend fun restoreLegacyBackup(sourceStream: InputStream): ValidationResult { + suspend fun performRestore(sourceStream: InputStream): ValidationResult { val reader = sourceStream.bufferedReader() val json = JsonParser.parseReader(reader).asJsonObject diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupValidator.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupValidator.kt index 889c90fb..f57ba9ff 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupValidator.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/legacy/LegacyBackupValidator.kt @@ -10,11 +10,11 @@ package suwayomi.tachidesk.manga.impl.backup.legacy import com.google.gson.JsonObject import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator import suwayomi.tachidesk.manga.impl.backup.legacy.models.Backup import suwayomi.tachidesk.manga.model.table.SourceTable -object LegacyBackupValidator { - data class ValidationResult(val missingSources: List, val missingTrackers: List) +object LegacyBackupValidator: AbstractBackupValidator() { /** * Checks for critical backup file data. diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt index 4d035018..d4109284 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt @@ -1,8 +1,20 @@ package suwayomi.tachidesk.manga.impl.backup.models import eu.kanade.tachiyomi.source.model.SManga +//import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +//import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType + + +// substitute for eu.kanade.tachiyomi.ui.reader.setting.OrientationType +object OrientationType { + const val MASK = 0x00000038 +} + +// substitute for eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +object ReadingModeType { + const val MASK = 0x00000007 +} -// import tachiyomi.source.model.MangaInfo interface Manga : SManga { @@ -10,85 +22,100 @@ interface Manga : SManga { var source: Long - /** is in library */ var favorite: Boolean + // last time the chapter list changed in any way var last_update: Long + // predicted next update time based on latest (by date) 4 chapters' deltas + var next_update: Long + var date_added: Long - var viewer: Int + var viewer_flags: Int var chapter_flags: Int var cover_last_modified: Long fun setChapterOrder(order: Int) { - setFlags(order, SORT_MASK) + setChapterFlags(order, CHAPTER_SORT_MASK) } fun sortDescending(): Boolean { - return chapter_flags and SORT_MASK == SORT_DESC + return chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC } fun getGenres(): List? { return genre?.split(", ")?.map { it.trim() } } - private fun setFlags(flag: Int, mask: Int) { + private fun setChapterFlags(flag: Int, mask: Int) { chapter_flags = chapter_flags and mask.inv() or (flag and mask) } + private fun setViewerFlags(flag: Int, mask: Int) { + viewer_flags = viewer_flags and mask.inv() or (flag and mask) + } + // Used to display the chapter's title one way or another var displayMode: Int - get() = chapter_flags and DISPLAY_MASK - set(mode) = setFlags(mode, DISPLAY_MASK) + get() = chapter_flags and CHAPTER_DISPLAY_MASK + set(mode) = setChapterFlags(mode, CHAPTER_DISPLAY_MASK) var readFilter: Int - get() = chapter_flags and READ_MASK - set(filter) = setFlags(filter, READ_MASK) + get() = chapter_flags and CHAPTER_READ_MASK + set(filter) = setChapterFlags(filter, CHAPTER_READ_MASK) var downloadedFilter: Int - get() = chapter_flags and DOWNLOADED_MASK - set(filter) = setFlags(filter, DOWNLOADED_MASK) + get() = chapter_flags and CHAPTER_DOWNLOADED_MASK + set(filter) = setChapterFlags(filter, CHAPTER_DOWNLOADED_MASK) var bookmarkedFilter: Int - get() = chapter_flags and BOOKMARKED_MASK - set(filter) = setFlags(filter, BOOKMARKED_MASK) + get() = chapter_flags and CHAPTER_BOOKMARKED_MASK + set(filter) = setChapterFlags(filter, CHAPTER_BOOKMARKED_MASK) var sorting: Int - get() = chapter_flags and SORTING_MASK - set(sort) = setFlags(sort, SORTING_MASK) + get() = chapter_flags and CHAPTER_SORTING_MASK + set(sort) = setChapterFlags(sort, CHAPTER_SORTING_MASK) + + var readingModeType: Int + get() = viewer_flags and ReadingModeType.MASK + set(readingMode) = setViewerFlags(readingMode, ReadingModeType.MASK) + + var orientationType: Int + get() = viewer_flags and OrientationType.MASK + set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK) companion object { - const val SORT_DESC = 0x00000000 - const val SORT_ASC = 0x00000001 - const val SORT_MASK = 0x00000001 - // Generic filter that does not filter anything const val SHOW_ALL = 0x00000000 - const val SHOW_UNREAD = 0x00000002 - const val SHOW_READ = 0x00000004 - const val READ_MASK = 0x00000006 + const val CHAPTER_SORT_DESC = 0x00000000 + const val CHAPTER_SORT_ASC = 0x00000001 + const val CHAPTER_SORT_MASK = 0x00000001 - const val SHOW_DOWNLOADED = 0x00000008 - const val SHOW_NOT_DOWNLOADED = 0x00000010 - const val DOWNLOADED_MASK = 0x00000018 + const val CHAPTER_SHOW_UNREAD = 0x00000002 + const val CHAPTER_SHOW_READ = 0x00000004 + const val CHAPTER_READ_MASK = 0x00000006 - const val SHOW_BOOKMARKED = 0x00000020 - const val SHOW_NOT_BOOKMARKED = 0x00000040 - const val BOOKMARKED_MASK = 0x00000060 + const val CHAPTER_SHOW_DOWNLOADED = 0x00000008 + const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010 + const val CHAPTER_DOWNLOADED_MASK = 0x00000018 - const val SORTING_SOURCE = 0x00000000 - const val SORTING_NUMBER = 0x00000100 - const val SORTING_UPLOAD_DATE = 0x00000200 - const val SORTING_MASK = 0x00000300 + const val CHAPTER_SHOW_BOOKMARKED = 0x00000020 + const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040 + const val CHAPTER_BOOKMARKED_MASK = 0x00000060 - const val DISPLAY_NAME = 0x00000000 - const val DISPLAY_NUMBER = 0x00100000 - const val DISPLAY_MASK = 0x00100000 + const val CHAPTER_SORTING_SOURCE = 0x00000000 + const val CHAPTER_SORTING_NUMBER = 0x00000100 + const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200 + const val CHAPTER_SORTING_MASK = 0x00000300 + + const val CHAPTER_DISPLAY_NAME = 0x00000000 + const val CHAPTER_DISPLAY_NUMBER = 0x00100000 + const val CHAPTER_DISPLAY_MASK = 0x00100000 fun create(source: Long): Manga = MangaImpl().apply { this.source = source @@ -102,7 +129,7 @@ interface Manga : SManga { } } -// fun Manga.toMangaInfo(): MangaInfo { +//fun Manga.toMangaInfo(): MangaInfo { // return MangaInfo( // artist = this.artist ?: "", // author = this.author ?: "", @@ -113,4 +140,4 @@ interface Manga : SManga { // status = this.status, // title = this.title // ) -// } +//} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt new file mode 100644 index 00000000..049ce3f3 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt @@ -0,0 +1,11 @@ +package suwayomi.tachidesk.manga.impl.backup.proto + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +object ProtoBackupExport { +} \ No newline at end of file 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 new file mode 100644 index 00000000..88f497fa --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt @@ -0,0 +1,31 @@ +package suwayomi.tachidesk.manga.impl.backup.proto + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import kotlinx.serialization.protobuf.ProtoBuf +import mu.KotlinLogging +import okio.buffer +import okio.gzip +import okio.source +import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator.ValidationResult +import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer +import java.io.InputStream + +private val logger = KotlinLogging.logger {} + +object ProtoBackupImport { + suspend fun performRestore(sourceStream: InputStream): ValidationResult { + + val backupString = sourceStream.source().gzip().buffer().use { it.readByteArray() } + val backup = ProtoBuf.decodeFromByteArray(BackupSerializer, backupString) + + + + TODO() + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt new file mode 100644 index 00000000..fe463555 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt @@ -0,0 +1,17 @@ +package suwayomi.tachidesk.manga.impl.backup.proto + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import com.google.gson.JsonObject +import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator + +object ProtoBackupValidator: AbstractBackupValidator() { + fun validate(json: JsonObject): ValidationResult { + TODO() + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt new file mode 100644 index 00000000..f764af7b --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt @@ -0,0 +1,12 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber + +@Serializable +data class Backup( + @ProtoNumber(1) val backupManga: List, + @ProtoNumber(2) var backupCategories: List = emptyList(), + // Bump by 100 to specify this is a 0.x value + @ProtoNumber(100) var backupSources: List = emptyList(), +) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt new file mode 100644 index 00000000..33479aaa --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt @@ -0,0 +1,33 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import suwayomi.tachidesk.manga.impl.backup.models.Category +import suwayomi.tachidesk.manga.impl.backup.models.CategoryImpl + +@Serializable +class BackupCategory( + @ProtoNumber(1) var name: String, + @ProtoNumber(2) var order: Int = 0, + // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x + // Bump by 100 to specify this is a 0.x value + @ProtoNumber(100) var flags: Int = 0, +) { + fun getCategoryImpl(): CategoryImpl { + return CategoryImpl().apply { + name = this@BackupCategory.name + flags = this@BackupCategory.flags + order = this@BackupCategory.order + } + } + + companion object { + fun copyFrom(category: Category): BackupCategory { + return BackupCategory( + name = category.name, + order = category.order, + flags = category.flags + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt new file mode 100644 index 00000000..38691c0e --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt @@ -0,0 +1,56 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import suwayomi.tachidesk.manga.impl.backup.models.Chapter +import suwayomi.tachidesk.manga.impl.backup.models.ChapterImpl + +@Serializable +data class BackupChapter( + // in 1.x some of these values have different names + // url is called key in 1.x + @ProtoNumber(1) var url: String, + @ProtoNumber(2) var name: String, + @ProtoNumber(3) var scanlator: String? = null, + @ProtoNumber(4) var read: Boolean = false, + @ProtoNumber(5) var bookmark: Boolean = false, + // lastPageRead is called progress in 1.x + @ProtoNumber(6) var lastPageRead: Int = 0, + @ProtoNumber(7) var dateFetch: Long = 0, + @ProtoNumber(8) var dateUpload: Long = 0, + // chapterNumber is called number is 1.x + @ProtoNumber(9) var chapterNumber: Float = 0F, + @ProtoNumber(10) var sourceOrder: Int = 0, +) { + fun toChapterImpl(): ChapterImpl { + return ChapterImpl().apply { + url = this@BackupChapter.url + name = this@BackupChapter.name + chapter_number = this@BackupChapter.chapterNumber + scanlator = this@BackupChapter.scanlator + read = this@BackupChapter.read + bookmark = this@BackupChapter.bookmark + last_page_read = this@BackupChapter.lastPageRead + date_fetch = this@BackupChapter.dateFetch + date_upload = this@BackupChapter.dateUpload + source_order = this@BackupChapter.sourceOrder + } + } + + companion object { + fun copyFrom(chapter: Chapter): BackupChapter { + return BackupChapter( + url = chapter.url, + name = chapter.name, + chapterNumber = chapter.chapter_number, + scanlator = chapter.scanlator, + read = chapter.read, + bookmark = chapter.bookmark, + lastPageRead = chapter.last_page_read, + dateFetch = chapter.date_fetch, + dateUpload = chapter.date_upload, + sourceOrder = chapter.source_order + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupFull.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupFull.kt new file mode 100644 index 00000000..c83402f0 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupFull.kt @@ -0,0 +1,12 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +object BackupFull { + fun getDefaultFilename(): String { + val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date()) + return "tachiyomi_$date.proto.gz" + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt new file mode 100644 index 00000000..9ad1c80e --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt @@ -0,0 +1,10 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber + +@Serializable +data class BackupHistory( + @ProtoNumber(0) var url: String, + @ProtoNumber(1) var lastRead: Long +) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt new file mode 100644 index 00000000..b3fb583c --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt @@ -0,0 +1,89 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import suwayomi.tachidesk.manga.impl.backup.models.ChapterImpl +import suwayomi.tachidesk.manga.impl.backup.models.Manga +import suwayomi.tachidesk.manga.impl.backup.models.MangaImpl +import suwayomi.tachidesk.manga.impl.backup.models.TrackImpl + +@Serializable +data class BackupManga( + // in 1.x some of these values have different names + @ProtoNumber(1) var source: Long, + // url is called key in 1.x + @ProtoNumber(2) var url: String, + @ProtoNumber(3) var title: String = "", + @ProtoNumber(4) var artist: String? = null, + @ProtoNumber(5) var author: String? = null, + @ProtoNumber(6) var description: String? = null, + @ProtoNumber(7) var genre: List = emptyList(), + @ProtoNumber(8) var status: Int = 0, + // thumbnailUrl is called cover in 1.x + @ProtoNumber(9) var thumbnailUrl: String? = null, + // @ProtoNumber(10) val customCover: String = "", 1.x value, not used in 0.x + // @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x + // @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x + @ProtoNumber(13) var dateAdded: Long = 0, + @ProtoNumber(14) var viewer: Int = 0, // Replaced by viewer_flags + // @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x + @ProtoNumber(16) var chapters: List = emptyList(), + @ProtoNumber(17) var categories: List = emptyList(), + @ProtoNumber(18) var tracking: List = emptyList(), + // Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x + @ProtoNumber(100) var favorite: Boolean = true, + @ProtoNumber(101) var chapterFlags: Int = 0, + @ProtoNumber(102) var history: List = emptyList(), + @ProtoNumber(103) var viewer_flags: Int? = null +) { + fun getMangaImpl(): MangaImpl { + return MangaImpl().apply { + url = this@BackupManga.url + title = this@BackupManga.title + artist = this@BackupManga.artist + author = this@BackupManga.author + description = this@BackupManga.description + genre = this@BackupManga.genre.joinToString() + status = this@BackupManga.status + thumbnail_url = this@BackupManga.thumbnailUrl + favorite = this@BackupManga.favorite + source = this@BackupManga.source + date_added = this@BackupManga.dateAdded + viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer + chapter_flags = this@BackupManga.chapterFlags + } + } + + fun getChaptersImpl(): List { + return chapters.map { + it.toChapterImpl() + } + } + + fun getTrackingImpl(): List { + return tracking.map { + it.getTrackingImpl() + } + } + + companion object { + fun copyFrom(manga: Manga): BackupManga { + return BackupManga( + url = manga.url, + title = manga.title, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.getGenres() ?: emptyList(), + status = manga.status, + thumbnailUrl = manga.thumbnail_url, + favorite = manga.favorite, + source = manga.source, + dateAdded = manga.date_added, + viewer = manga.readingModeType, + viewer_flags = manga.viewer_flags, + chapterFlags = manga.chapter_flags + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSerializer.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSerializer.kt new file mode 100644 index 00000000..9c4a6734 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSerializer.kt @@ -0,0 +1,6 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializer + +@Serializer(forClass = Backup::class) +object BackupSerializer diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt new file mode 100644 index 00000000..7f7ddbe1 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt @@ -0,0 +1,20 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import eu.kanade.tachiyomi.source.Source +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber + +@Serializable +data class BackupSource( + @ProtoNumber(0) var name: String = "", + @ProtoNumber(1) var sourceId: Long +) { + companion object { + fun copyFrom(source: Source): BackupSource { + return BackupSource( + name = source.name, + sourceId = source.id + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt new file mode 100644 index 00000000..662c70ff --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt @@ -0,0 +1,65 @@ +package suwayomi.tachidesk.manga.impl.backup.proto.models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import suwayomi.tachidesk.manga.impl.backup.models.Track +import suwayomi.tachidesk.manga.impl.backup.models.TrackImpl + +@Serializable +data class BackupTracking( + // in 1.x some of these values have different types or names + // syncId is called siteId in 1,x + @ProtoNumber(1) var syncId: Int, + // LibraryId is not null in 1.x + @ProtoNumber(2) var libraryId: Long, + @ProtoNumber(3) var mediaId: Int = 0, + // trackingUrl is called mediaUrl in 1.x + @ProtoNumber(4) var trackingUrl: String = "", + @ProtoNumber(5) var title: String = "", + // lastChapterRead is called last read, and it has been changed to a float in 1.x + @ProtoNumber(6) var lastChapterRead: Float = 0F, + @ProtoNumber(7) var totalChapters: Int = 0, + @ProtoNumber(8) var score: Float = 0F, + @ProtoNumber(9) var status: Int = 0, + // startedReadingDate is called startReadTime in 1.x + @ProtoNumber(10) var startedReadingDate: Long = 0, + // finishedReadingDate is called endReadTime in 1.x + @ProtoNumber(11) var finishedReadingDate: Long = 0, +) { + fun getTrackingImpl(): TrackImpl { + return TrackImpl().apply { + sync_id = this@BackupTracking.syncId + media_id = this@BackupTracking.mediaId + library_id = this@BackupTracking.libraryId + title = this@BackupTracking.title + // convert from float to int because of 1.x types + last_chapter_read = this@BackupTracking.lastChapterRead.toInt() + total_chapters = this@BackupTracking.totalChapters + score = this@BackupTracking.score + status = this@BackupTracking.status + started_reading_date = this@BackupTracking.startedReadingDate + finished_reading_date = this@BackupTracking.finishedReadingDate + tracking_url = this@BackupTracking.trackingUrl + } + } + + companion object { + fun copyFrom(track: Track): BackupTracking { + return BackupTracking( + syncId = track.sync_id, + mediaId = track.media_id, + // forced not null so its compatible with 1.x backup system + libraryId = track.library_id!!, + title = track.title, + // convert to float for 1.x + lastChapterRead = track.last_chapter_read.toFloat(), + totalChapters = track.total_chapters, + score = track.score, + status = track.status, + startedReadingDate = track.started_reading_date, + finishedReadingDate = track.finished_reading_date, + trackingUrl = track.tracking_url + ) + } + } +}