mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 10:54:38 -05:00
initial support for portobuf backup
This commit is contained in:
@@ -61,3 +61,31 @@ interface SManga : Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//fun SManga.toMangaInfo(): MangaInfo {
|
||||||
|
// return MangaInfo(
|
||||||
|
// key = this.url,
|
||||||
|
// title = this.title,
|
||||||
|
// artist = this.artist ?: "",
|
||||||
|
// author = this.author ?: "",
|
||||||
|
// description = this.description ?: "",
|
||||||
|
// genres = this.genre?.split(", ") ?: emptyList(),
|
||||||
|
// status = this.status,
|
||||||
|
// cover = this.thumbnail_url ?: ""
|
||||||
|
// )
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//fun MangaInfo.toSManga(): SManga {
|
||||||
|
// val mangaInfo = this
|
||||||
|
// return SManga.create().apply {
|
||||||
|
// url = mangaInfo.key
|
||||||
|
// title = mangaInfo.title
|
||||||
|
// artist = mangaInfo.artist
|
||||||
|
// author = mangaInfo.author
|
||||||
|
// description = mangaInfo.description
|
||||||
|
// genre = mangaInfo.genres.joinToString(", ")
|
||||||
|
// status = mangaInfo.status
|
||||||
|
// thumbnail_url = mangaInfo.cover
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ object MangaTypeAdapter {
|
|||||||
value(it.url)
|
value(it.url)
|
||||||
value(it.title)
|
value(it.title)
|
||||||
value(it.source)
|
value(it.source)
|
||||||
value(it.viewer)
|
value(it.viewer_flags)
|
||||||
value(it.chapter_flags)
|
value(it.chapter_flags)
|
||||||
endArray()
|
endArray()
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ object MangaTypeAdapter {
|
|||||||
manga.url = nextString()
|
manga.url = nextString()
|
||||||
manga.title = nextString()
|
manga.title = nextString()
|
||||||
manga.source = nextLong()
|
manga.source = nextLong()
|
||||||
manga.viewer = nextInt()
|
manga.viewer_flags = nextInt()
|
||||||
manga.chapter_flags = nextInt()
|
manga.chapter_flags = nextInt()
|
||||||
endArray()
|
endArray()
|
||||||
manga
|
manga
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
|||||||
|
|
||||||
open class MangaImpl : Manga {
|
open class MangaImpl : Manga {
|
||||||
|
|
||||||
override var id: Long? = 0
|
override var id: Long? = null
|
||||||
|
|
||||||
override var source: Long = -1
|
override var source: Long = -1
|
||||||
|
|
||||||
@@ -29,6 +29,8 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override var last_update: Long = 0
|
override var last_update: Long = 0
|
||||||
|
|
||||||
|
override var next_update: Long = 0
|
||||||
|
|
||||||
override var date_added: Long = 0
|
override var date_added: Long = 0
|
||||||
|
|
||||||
override var initialized: Boolean = false
|
override var initialized: Boolean = false
|
||||||
@@ -42,7 +44,7 @@ open class MangaImpl : Manga {
|
|||||||
* 4 -> Webtoon
|
* 4 -> Webtoon
|
||||||
* 5 -> Continues Vertical
|
* 5 -> Continues Vertical
|
||||||
*/
|
*/
|
||||||
override var viewer: Int = 0
|
override var viewer_flags: Int = 0
|
||||||
|
|
||||||
/** Contains some useful info about
|
/** Contains some useful info about
|
||||||
*/
|
*/
|
||||||
@@ -70,7 +72,7 @@ open class MangaImpl : Manga {
|
|||||||
url = mangaRecord[MangaTable.url]
|
url = mangaRecord[MangaTable.url]
|
||||||
title = mangaRecord[MangaTable.title]
|
title = mangaRecord[MangaTable.title]
|
||||||
source = mangaRecord[MangaTable.sourceReference]
|
source = mangaRecord[MangaTable.sourceReference]
|
||||||
viewer = 0 // TODO: implement
|
viewer_flags = 0 // TODO: implement
|
||||||
chapter_flags = 0 // TODO: implement
|
chapter_flags = 0 // TODO: implement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl.backup.proto
|
||||||
|
|
||||||
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
open class ProtoBackupBase {
|
||||||
|
var sourceMapping: Map<Long, String> = emptyMap()
|
||||||
|
|
||||||
|
val parser = ProtoBuf
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package suwayomi.tachidesk.manga.impl.backup.proto
|
package suwayomi.tachidesk.manga.impl.backup.proto
|
||||||
|
|
||||||
import suwayomi.tachidesk.manga.impl.backup.BackupFlags
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) Contributors to the Suwayomi project
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
*
|
*
|
||||||
@@ -9,8 +7,35 @@ import suwayomi.tachidesk.manga.impl.backup.BackupFlags
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
object ProtoBackupExport {
|
import okio.buffer
|
||||||
suspend fun createBackup(flags: BackupFlags): String? {
|
import okio.gzip
|
||||||
TODO()
|
import okio.sink
|
||||||
|
import suwayomi.tachidesk.manga.impl.backup.BackupFlags
|
||||||
|
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
|
||||||
|
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
object ProtoBackupExport: ProtoBackupBase() {
|
||||||
|
suspend fun createBackup(flags: BackupFlags): ByteArray {
|
||||||
|
// Create root object
|
||||||
|
var backup: Backup? = null
|
||||||
|
|
||||||
|
// databaseHelper.inTransaction {
|
||||||
|
// val databaseManga = getFavoriteManga()
|
||||||
|
//
|
||||||
|
// backup = Backup(
|
||||||
|
// backupManga(databaseManga, flags),
|
||||||
|
// backupCategories(),
|
||||||
|
// backupExtensionInfo(databaseManga)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!)
|
||||||
|
byteArray.inputStream()
|
||||||
|
|
||||||
|
val byteStream = ByteArrayOutputStream()
|
||||||
|
byteStream.sink().gzip().buffer().use { it.write(byteArray) }
|
||||||
|
|
||||||
|
return byteStream.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,25 +7,74 @@ package suwayomi.tachidesk.manga.impl.backup.proto
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import kotlinx.serialization.protobuf.ProtoBuf
|
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.gzip
|
import okio.gzip
|
||||||
import okio.source
|
import okio.source
|
||||||
import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator.ValidationResult
|
import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator.ValidationResult
|
||||||
|
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.BackupManga
|
||||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
|
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
object ProtoBackupImport {
|
object ProtoBackupImport: ProtoBackupBase() {
|
||||||
|
var restoreAmount = 0
|
||||||
|
|
||||||
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 = ProtoBuf.decodeFromByteArray(BackupSerializer, backupString)
|
val backup = parser.decodeFromByteArray(BackupSerializer, backupString)
|
||||||
|
|
||||||
|
val validationResult = validate(backup)
|
||||||
|
|
||||||
|
restoreAmount = backup.backupManga.size + 1 // +1 for categories
|
||||||
|
|
||||||
|
// Restore categories
|
||||||
|
if (backup.backupCategories.isNotEmpty()) {
|
||||||
|
restoreCategories(backup.backupCategories)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store source mapping for error messages
|
||||||
|
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
|
||||||
|
|
||||||
|
// Restore individual manga
|
||||||
|
backup.backupManga.forEach {
|
||||||
|
restoreManga(it, backup.backupCategories)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: optionally trigger online library + tracker update
|
||||||
|
|
||||||
|
return validationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreCategories(backupCategories: List<BackupCategory>) { // TODO
|
||||||
|
// db.inTransaction {
|
||||||
|
// backupManager.restoreCategories(backupCategories)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// restoreProgress += 1
|
||||||
|
// showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) { // TODO
|
||||||
TODO()
|
// val manga = backupManga.getMangaImpl()
|
||||||
|
// val chapters = backupManga.getChaptersImpl()
|
||||||
|
// val categories = backupManga.categories
|
||||||
|
// val history = backupManga.history
|
||||||
|
// val tracks = backupManga.getTrackingImpl()
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// restoreMangaData(manga, chapters, categories, history, tracks, backupCategories)
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||||
|
// errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// restoreProgress += 1
|
||||||
|
// showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,11 +7,39 @@ package suwayomi.tachidesk.manga.impl.backup.proto
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
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.AbstractBackupValidator
|
||||||
|
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
|
||||||
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
|
|
||||||
object ProtoBackupValidator: AbstractBackupValidator() {
|
object ProtoBackupValidator: AbstractBackupValidator() {
|
||||||
fun validate(json: JsonObject): ValidationResult {
|
fun validate(backup: Backup): ValidationResult {
|
||||||
TODO()
|
if (backup.backupManga.isEmpty()) {
|
||||||
|
throw Exception("Backup does not contain any manga.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val sources = backup.backupSources.map { it.sourceId to it.name }.toMap()
|
||||||
|
|
||||||
|
val missingSources = transaction {
|
||||||
|
sources
|
||||||
|
.filter { SourceTable.select { SourceTable.id eq it.key }.firstOrNull() == null }
|
||||||
|
.map { "${it.value} (${it.key})" }
|
||||||
|
.sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
// val trackers = backup.backupManga
|
||||||
|
// .flatMap { it.tracking }
|
||||||
|
// .map { it.syncId }
|
||||||
|
// .distinct()
|
||||||
|
|
||||||
|
val missingTrackers = listOf("")
|
||||||
|
// val missingTrackers = trackers
|
||||||
|
// .mapNotNull { trackManager.getService(it) }
|
||||||
|
// .filter { !it.isLogged }
|
||||||
|
// .map { context.getString(it.nameRes()) }
|
||||||
|
// .sorted()
|
||||||
|
|
||||||
|
return ValidationResult(missingSources, missingTrackers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user