From f502884fdd58a9427a0eabb5be5800f04460e5e6 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Fri, 2 Apr 2021 17:57:29 +0430 Subject: [PATCH] a partially working legacy import... --- .../eu/kanade/tachiyomi/source/LocalSource.kt | 358 ++++++++++++++++++ .../tachidesk/impl/backup/BackupFlags.kt | 16 + .../impl/backup/legacy/LegacyBackupExport.kt | 137 +++++++ .../impl/backup/legacy/models/Backup.kt | 25 ++ .../impl/backup/legacy/models/DHistory.kt | 3 + .../legacy/serializer/CategoryTypeAdapter.kt | 31 ++ .../legacy/serializer/ChapterTypeAdapter.kt | 59 +++ .../legacy/serializer/HistoryTypeAdapter.kt | 32 ++ .../legacy/serializer/MangaTypeAdapter.kt | 37 ++ .../legacy/serializer/TrackTypeAdapter.kt | 59 +++ .../tachidesk/impl/backup/models/Category.kt | 23 ++ .../impl/backup/models/CategoryImpl.kt | 24 ++ .../tachidesk/impl/backup/models/Chapter.kt | 31 ++ .../impl/backup/models/ChapterImpl.kt | 53 +++ .../tachidesk/impl/backup/models/History.kt | 42 ++ .../impl/backup/models/HistoryImpl.kt | 27 ++ .../impl/backup/models/LibraryManga.kt | 8 + .../tachidesk/impl/backup/models/Manga.kt | 115 ++++++ .../impl/backup/models/MangaCategory.kt | 20 + .../impl/backup/models/MangaChapter.kt | 3 + .../impl/backup/models/MangaChapterHistory.kt | 10 + .../tachidesk/impl/backup/models/MangaImpl.kt | 79 ++++ .../tachidesk/impl/backup/models/Track.kt | 46 +++ .../tachidesk/impl/backup/models/TrackImpl.kt | 48 +++ .../ir/armor/tachidesk/server/JavalinSetup.kt | 19 +- 25 files changed, 1303 insertions(+), 2 deletions(-) create mode 100644 server/src/main/kotlin/eu/kanade/tachiyomi/source/LocalSource.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/BackupFlags.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupExport.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/Backup.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/DHistory.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/CategoryTypeAdapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/ChapterTypeAdapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/HistoryTypeAdapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/MangaTypeAdapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/TrackTypeAdapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Category.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/CategoryImpl.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Chapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/ChapterImpl.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/History.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/HistoryImpl.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/LibraryManga.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Manga.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaCategory.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapter.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapterHistory.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaImpl.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Track.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/TrackImpl.kt diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/LocalSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/LocalSource.kt new file mode 100644 index 00000000..7b36ee6b --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/LocalSource.kt @@ -0,0 +1,358 @@ +package eu.kanade.tachiyomi.source + +import android.content.Context +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import rx.Observable + +// import com.github.junrar.Archive +// import com.google.gson.JsonParser +// import eu.kanade.tachiyomi.R +// import eu.kanade.tachiyomi.source.model.Filter +// import eu.kanade.tachiyomi.source.model.FilterList +// import eu.kanade.tachiyomi.source.model.MangasPage +// import eu.kanade.tachiyomi.source.model.Page +// import eu.kanade.tachiyomi.source.model.SChapter +// import eu.kanade.tachiyomi.source.model.SManga +// import eu.kanade.tachiyomi.util.chapter.ChapterRecognition +// import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder +// import eu.kanade.tachiyomi.util.storage.DiskUtil +// import eu.kanade.tachiyomi.util.storage.EpubFile +// import eu.kanade.tachiyomi.util.system.ImageUtil +// import rx.Observable +// import timber.log.Timber +// import java.io.File +// import java.io.FileInputStream +// import java.io.InputStream +// import java.util.Locale +// import java.util.concurrent.TimeUnit +// import java.util.zip.ZipFile + +class LocalSource(private val context: Context) : CatalogueSource { + companion object { + const val ID = 0L +// const val HELP_URL = "https://tachiyomi.org/help/guides/reading-local-manga/" +// +// private const val COVER_NAME = "cover.jpg" +// private val SUPPORTED_ARCHIVE_TYPES = setOf("zip", "rar", "cbr", "cbz", "epub") +// +// private val POPULAR_FILTERS = FilterList(OrderBy()) +// private val LATEST_FILTERS = FilterList(OrderBy().apply { state = Filter.Sort.Selection(1, false) }) +// private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS) +// +// fun updateCover(context: Context, manga: SManga, input: InputStream): File? { +// val dir = getBaseDirectories(context).firstOrNull() +// if (dir == null) { +// input.close() +// return null +// } +// val cover = File("${dir.absolutePath}/${manga.url}", COVER_NAME) +// +// // It might not exist if using the external SD card +// cover.parentFile?.mkdirs() +// input.use { +// cover.outputStream().use { +// input.copyTo(it) +// } +// } +// return cover +// } +// +// private fun getBaseDirectories(context: Context): List { +// val c = context.getString(R.string.app_name) + File.separator + "local" +// return DiskUtil.getExternalStorages(context).map { File(it.absolutePath, c) } +// } + } + + override val id = ID + override val name = "Local source" + override val lang = "" + override val supportsLatest = true + + override fun fetchMangaDetails(manga: SManga): Observable { + TODO("Not yet implemented") + } + + override fun fetchChapterList(manga: SManga): Observable> { + TODO("Not yet implemented") + } + + override fun fetchPageList(chapter: SChapter): Observable> { + TODO("Not yet implemented") + } + override fun fetchPopularManga(page: Int): Observable { + TODO("Not yet implemented") + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + TODO("Not yet implemented") + } + + override fun fetchLatestUpdates(page: Int): Observable { + TODO("Not yet implemented") + } + + override fun getFilterList(): FilterList { + TODO("Not yet implemented") + } +// +// override fun toString() = context.getString(R.string.local_source) +// +// override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS) +// +// override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { +// val baseDirs = getBaseDirectories(context) +// +// val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L +// var mangaDirs = baseDirs +// .asSequence() +// .mapNotNull { it.listFiles()?.toList() } +// .flatten() +// .filter { it.isDirectory } +// .filterNot { it.name.startsWith('.') } +// .filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time } +// .distinctBy { it.name } +// +// val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state +// when (state?.index) { +// 0 -> { +// mangaDirs = if (state.ascending) { +// mangaDirs.sortedBy { it.name.toLowerCase(Locale.ENGLISH) } +// } else { +// mangaDirs.sortedByDescending { it.name.toLowerCase(Locale.ENGLISH) } +// } +// } +// 1 -> { +// mangaDirs = if (state.ascending) { +// mangaDirs.sortedBy(File::lastModified) +// } else { +// mangaDirs.sortedByDescending(File::lastModified) +// } +// } +// } +// +// val mangas = mangaDirs.map { mangaDir -> +// SManga.create().apply { +// title = mangaDir.name +// url = mangaDir.name +// +// // Try to find the cover +// for (dir in baseDirs) { +// val cover = File("${dir.absolutePath}/$url", COVER_NAME) +// if (cover.exists()) { +// thumbnail_url = cover.absolutePath +// break +// } +// } +// +// val chapters = fetchChapterList(this).toBlocking().first() +// if (chapters.isNotEmpty()) { +// val chapter = chapters.last() +// val format = getFormat(chapter) +// if (format is Format.Epub) { +// EpubFile(format.file).use { epub -> +// epub.fillMangaMetadata(this) +// } +// } +// +// // Copy the cover from the first chapter found. +// if (thumbnail_url == null) { +// try { +// val dest = updateCover(chapter, this) +// thumbnail_url = dest?.absolutePath +// } catch (e: Exception) { +// Timber.e(e) +// } +// } +// } +// } +// } +// +// return Observable.just(MangasPage(mangas.toList(), false)) +// } +// +// override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS) +// +// override fun fetchMangaDetails(manga: SManga): Observable { +// getBaseDirectories(context) +// .asSequence() +// .mapNotNull { File(it, manga.url).listFiles()?.toList() } +// .flatten() +// .firstOrNull { it.extension == "json" } +// ?.apply { +// val reader = this.inputStream().bufferedReader() +// val json = JsonParser.parseReader(reader).asJsonObject +// +// manga.title = json["title"]?.asString ?: manga.title +// manga.author = json["author"]?.asString ?: manga.author +// manga.artist = json["artist"]?.asString ?: manga.artist +// manga.description = json["description"]?.asString ?: manga.description +// manga.genre = json["genre"]?.asJsonArray?.joinToString(", ") { it.asString } +// ?: manga.genre +// manga.status = json["status"]?.asInt ?: manga.status +// } +// +// return Observable.just(manga) +// } +// +// override fun fetchChapterList(manga: SManga): Observable> { +// val chapters = getBaseDirectories(context) +// .asSequence() +// .mapNotNull { File(it, manga.url).listFiles()?.toList() } +// .flatten() +// .filter { it.isDirectory || isSupportedFile(it.extension) } +// .map { chapterFile -> +// SChapter.create().apply { +// url = "${manga.url}/${chapterFile.name}" +// name = if (chapterFile.isDirectory) { +// chapterFile.name +// } else { +// chapterFile.nameWithoutExtension +// } +// date_upload = chapterFile.lastModified() +// +// val format = getFormat(this) +// if (format is Format.Epub) { +// EpubFile(format.file).use { epub -> +// epub.fillChapterMetadata(this) +// } +// } +// +// val chapNameCut = stripMangaTitle(name, manga.title) +// if (chapNameCut.isNotEmpty()) name = chapNameCut +// ChapterRecognition.parseChapterNumber(this, manga) +// } +// } +// .sortedWith( +// Comparator { c1, c2 -> +// val c = c2.chapter_number.compareTo(c1.chapter_number) +// if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c +// } +// ) +// .toList() +// +// return Observable.just(chapters) +// } +// +// /** +// * Strips the manga title from a chapter name, matching only based on alphanumeric and whitespace +// * characters. +// */ +// private fun stripMangaTitle(chapterName: String, mangaTitle: String): String { +// var chapterNameIndex = 0 +// var mangaTitleIndex = 0 +// while (chapterNameIndex < chapterName.length && mangaTitleIndex < mangaTitle.length) { +// val chapterChar = chapterName[chapterNameIndex] +// val mangaChar = mangaTitle[mangaTitleIndex] +// if (!chapterChar.equals(mangaChar, true)) { +// val invalidChapterChar = !chapterChar.isLetterOrDigit() && !chapterChar.isWhitespace() +// val invalidMangaChar = !mangaChar.isLetterOrDigit() && !mangaChar.isWhitespace() +// +// if (!invalidChapterChar && !invalidMangaChar) { +// return chapterName +// } +// +// if (invalidChapterChar) { +// chapterNameIndex++ +// } +// +// if (invalidMangaChar) { +// mangaTitleIndex++ +// } +// } else { +// chapterNameIndex++ +// mangaTitleIndex++ +// } +// } +// +// return chapterName.substring(chapterNameIndex).trimStart(' ', '-', '_', ',', ':') +// } +// +// override fun fetchPageList(chapter: SChapter): Observable> { +// return Observable.error(Exception("Unused")) +// } +// +// private fun isSupportedFile(extension: String): Boolean { +// return extension.toLowerCase() in SUPPORTED_ARCHIVE_TYPES +// } +// +// fun getFormat(chapter: SChapter): Format { +// val baseDirs = getBaseDirectories(context) +// +// for (dir in baseDirs) { +// val chapFile = File(dir, chapter.url) +// if (!chapFile.exists()) continue +// +// return getFormat(chapFile) +// } +// throw Exception("Chapter not found") +// } +// +// private fun getFormat(file: File): Format { +// val extension = file.extension +// return if (file.isDirectory) { +// Format.Directory(file) +// } else if (extension.equals("zip", true) || extension.equals("cbz", true)) { +// Format.Zip(file) +// } else if (extension.equals("rar", true) || extension.equals("cbr", true)) { +// Format.Rar(file) +// } else if (extension.equals("epub", true)) { +// Format.Epub(file) +// } else { +// throw Exception("Invalid chapter format") +// } +// } +// +// private fun updateCover(chapter: SChapter, manga: SManga): File? { +// return when (val format = getFormat(chapter)) { +// is Format.Directory -> { +// val entry = format.file.listFiles() +// ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } +// ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } +// +// entry?.let { updateCover(context, manga, it.inputStream()) } +// } +// is Format.Zip -> { +// ZipFile(format.file).use { zip -> +// val entry = zip.entries().toList() +// .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } +// .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } +// +// entry?.let { updateCover(context, manga, zip.getInputStream(it)) } +// } +// } +// is Format.Rar -> { +// Archive(format.file).use { archive -> +// val entry = archive.fileHeaders +// .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } +// .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } +// +// entry?.let { updateCover(context, manga, archive.getInputStream(it)) } +// } +// } +// is Format.Epub -> { +// EpubFile(format.file).use { epub -> +// val entry = epub.getImagesFromPages() +// .firstOrNull() +// ?.let { epub.getEntry(it) } +// +// entry?.let { updateCover(context, manga, epub.getInputStream(it)) } +// } +// } +// } +// } +// +// private class OrderBy : Filter.Sort("Order by", arrayOf("Title", "Date"), Selection(0, true)) +// +// override fun getFilterList() = FilterList(OrderBy()) +// +// sealed class Format { +// data class Directory(val file: File) : Format() +// data class Zip(val file: File) : Format() +// data class Rar(val file: File) : Format() +// data class Epub(val file: File) : Format() +// } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/BackupFlags.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/BackupFlags.kt new file mode 100644 index 00000000..45c5014b --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/BackupFlags.kt @@ -0,0 +1,16 @@ +package ir.armor.tachidesk.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/. */ + +data class BackupFlags( + val includeManga: Boolean, + val includeCategories: Boolean, + val includeChapters: Boolean, + val includeTracking: Boolean, + val includeHistory: Boolean, +) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupExport.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupExport.kt new file mode 100644 index 00000000..ecc5ad35 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupExport.kt @@ -0,0 +1,137 @@ +package ir.armor.tachidesk.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.registerTypeAdapter +import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter +import com.github.salomonbrys.kotson.set +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import eu.kanade.tachiyomi.source.LocalSource +import ir.armor.tachidesk.impl.backup.BackupFlags +import ir.armor.tachidesk.impl.backup.legacy.models.Backup +import ir.armor.tachidesk.impl.backup.legacy.models.Backup.CURRENT_VERSION +import ir.armor.tachidesk.impl.backup.legacy.models.DHistory +import ir.armor.tachidesk.impl.backup.legacy.serializer.CategoryTypeAdapter +import ir.armor.tachidesk.impl.backup.legacy.serializer.ChapterTypeAdapter +import ir.armor.tachidesk.impl.backup.legacy.serializer.HistoryTypeAdapter +import ir.armor.tachidesk.impl.backup.legacy.serializer.MangaTypeAdapter +import ir.armor.tachidesk.impl.backup.legacy.serializer.TrackTypeAdapter +import ir.armor.tachidesk.impl.backup.models.CategoryImpl +import ir.armor.tachidesk.impl.backup.models.ChapterImpl +import ir.armor.tachidesk.impl.backup.models.Manga +import ir.armor.tachidesk.impl.backup.models.MangaImpl +import ir.armor.tachidesk.impl.backup.models.TrackImpl +import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource +import ir.armor.tachidesk.model.database.ChapterTable +import ir.armor.tachidesk.model.database.MangaTable +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.transactions.transaction + +object LegacyBackupExport { + const val version = 2 + + private val parser: Gson = when (version) { + 2 -> GsonBuilder() + .registerTypeAdapter(MangaTypeAdapter.build()) + .registerTypeHierarchyAdapter(ChapterTypeAdapter.build()) + .registerTypeAdapter(CategoryTypeAdapter.build()) + .registerTypeAdapter(HistoryTypeAdapter.build()) + .registerTypeHierarchyAdapter(TrackTypeAdapter.build()) + .create() + else -> throw Exception("Unknown backup version") + } + + suspend fun createBackup(flags: BackupFlags): String? { + // Create root object + val root = JsonObject() + + // Create manga array + val mangaEntries = JsonArray() + + // Create category array + val categoryEntries = JsonArray() + + // Create extension ID/name mapping + val extensionEntries = JsonArray() + + // Add values to root + root[Backup.VERSION] = CURRENT_VERSION + root[Backup.MANGAS] = mangaEntries + root[Backup.CATEGORIES] = categoryEntries + root[Backup.EXTENSIONS] = extensionEntries + + transaction { + val mangas = MangaTable.select { (MangaTable.inLibrary eq true) } + + val extensions: MutableSet = mutableSetOf() + + // Backup library manga and its dependencies + mangas.map { + MangaImpl.fromQuery(it) + }.forEach { manga -> + + mangaEntries.add(backupMangaObject(manga, flags)) + + // Maintain set of extensions/sources used (excludes local source) + if (manga.source != LocalSource.ID) { + getHttpSource(manga.source).let { + extensions.add("${it.id}:${it.name}") + } + } + } + + // Backup categories + if (flags.includeCategories) { + backupCategories(categoryEntries) + } + + // Backup extension ID/name mapping + backupExtensionInfo(extensionEntries, extensions) + } + + return parser.toJson(root) + } + + private fun backupMangaObject(manga: Manga, options: BackupFlags): JsonElement { + // Entry for this manga + val entry = JsonObject() + + // Backup manga fields + entry[Backup.MANGA] = parser.toJsonTree(manga) + + // Check if user wants chapter information in backup + if (options.includeChapters && false) { // TODO + // Backup all the chapters + val mangaId = manga.id!!.toInt() + val chapters = ChapterTable.select { ChapterTable.manga eq mangaId }.map { ChapterImpl.fromQuery(it) } + if (chapters.count() > 0) { + val chaptersJson = parser.toJsonTree(chapters) + if (chaptersJson.asJsonArray.size() > 0) { + entry[Backup.CHAPTERS] = chaptersJson + } + } + } + + return entry + } + + private fun backupCategories(root: JsonArray) { // TODO +// val categories = databaseHelper.getCategories().executeAsBlocking() +// categories.forEach { root.add(parser.toJsonTree(it)) } + } + + private fun backupExtensionInfo(root: JsonArray, extensions: Set) { // TODO +// extensions.sorted().forEach { +// root.add(it) +// } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/Backup.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/Backup.kt new file mode 100644 index 00000000..7ff54b23 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/Backup.kt @@ -0,0 +1,25 @@ +package ir.armor.tachidesk.impl.backup.legacy.models + +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * Json values + */ +object Backup { + const val CURRENT_VERSION = 2 + const val MANGA = "manga" + const val MANGAS = "mangas" + const val TRACK = "track" + const val CHAPTERS = "chapters" + const val CATEGORIES = "categories" + const val EXTENSIONS = "extensions" + const val HISTORY = "history" + const val VERSION = "version" + + fun getDefaultFilename(): String { + val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date()) + return "tachiyomi_$date.json" + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/DHistory.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/DHistory.kt new file mode 100644 index 00000000..d8483bea --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/models/DHistory.kt @@ -0,0 +1,3 @@ +package ir.armor.tachidesk.impl.backup.legacy.models + +data class DHistory(val url: String, val lastRead: Long) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/CategoryTypeAdapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/CategoryTypeAdapter.kt new file mode 100644 index 00000000..5440a300 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/CategoryTypeAdapter.kt @@ -0,0 +1,31 @@ +package ir.armor.tachidesk.impl.backup.legacy.serializer + +import com.github.salomonbrys.kotson.typeAdapter +import com.google.gson.TypeAdapter +import ir.armor.tachidesk.impl.backup.models.CategoryImpl + +/** + * JSON Serializer used to write / read [CategoryImpl] to / from json + */ +object CategoryTypeAdapter { + + fun build(): TypeAdapter { + return typeAdapter { + write { + beginArray() + value(it.name) + value(it.order) + endArray() + } + + read { + beginArray() + val category = CategoryImpl() + category.name = nextString() + category.order = nextInt() + endArray() + category + } + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/ChapterTypeAdapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/ChapterTypeAdapter.kt new file mode 100644 index 00000000..0a0fd3ff --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/ChapterTypeAdapter.kt @@ -0,0 +1,59 @@ +package ir.armor.tachidesk.impl.backup.legacy.serializer + +import com.github.salomonbrys.kotson.typeAdapter +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonToken +import ir.armor.tachidesk.impl.backup.models.ChapterImpl + +/** + * JSON Serializer used to write / read [ChapterImpl] to / from json + */ +object ChapterTypeAdapter { + + private const val URL = "u" + private const val READ = "r" + private const val BOOKMARK = "b" + private const val LAST_READ = "l" + + fun build(): TypeAdapter { + return typeAdapter { + write { + if (it.read || it.bookmark || it.last_page_read != 0) { + beginObject() + name(URL) + value(it.url) + if (it.read) { + name(READ) + value(1) + } + if (it.bookmark) { + name(BOOKMARK) + value(1) + } + if (it.last_page_read != 0) { + name(LAST_READ) + value(it.last_page_read) + } + endObject() + } + } + + read { + val chapter = ChapterImpl() + beginObject() + while (hasNext()) { + if (peek() == JsonToken.NAME) { + when (nextName()) { + URL -> chapter.url = nextString() + READ -> chapter.read = nextInt() == 1 + BOOKMARK -> chapter.bookmark = nextInt() == 1 + LAST_READ -> chapter.last_page_read = nextInt() + } + } + } + endObject() + chapter + } + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/HistoryTypeAdapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/HistoryTypeAdapter.kt new file mode 100644 index 00000000..d6709da5 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/HistoryTypeAdapter.kt @@ -0,0 +1,32 @@ +package ir.armor.tachidesk.impl.backup.legacy.serializer + +import com.github.salomonbrys.kotson.typeAdapter +import com.google.gson.TypeAdapter +import ir.armor.tachidesk.impl.backup.legacy.models.DHistory + +/** + * JSON Serializer used to write / read [DHistory] to / from json + */ +object HistoryTypeAdapter { + + fun build(): TypeAdapter { + return typeAdapter { + write { + if (it.lastRead != 0L) { + beginArray() + value(it.url) + value(it.lastRead) + endArray() + } + } + + read { + beginArray() + val url = nextString() + val lastRead = nextLong() + endArray() + DHistory(url, lastRead) + } + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/MangaTypeAdapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/MangaTypeAdapter.kt new file mode 100644 index 00000000..331ba27e --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/MangaTypeAdapter.kt @@ -0,0 +1,37 @@ +package ir.armor.tachidesk.impl.backup.legacy.serializer + +import com.github.salomonbrys.kotson.typeAdapter +import com.google.gson.TypeAdapter +import ir.armor.tachidesk.impl.backup.models.MangaImpl + +/** + * JSON Serializer used to write / read [MangaImpl] to / from json + */ +object MangaTypeAdapter { + + fun build(): TypeAdapter { + return typeAdapter { + write { + beginArray() + value(it.url) + value(it.title) + value(it.source) + value(it.viewer) + value(it.chapter_flags) + endArray() + } + + read { + beginArray() + val manga = MangaImpl() + manga.url = nextString() + manga.title = nextString() + manga.source = nextLong() + manga.viewer = nextInt() + manga.chapter_flags = nextInt() + endArray() + manga + } + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/TrackTypeAdapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/TrackTypeAdapter.kt new file mode 100644 index 00000000..1b576554 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/serializer/TrackTypeAdapter.kt @@ -0,0 +1,59 @@ +package ir.armor.tachidesk.impl.backup.legacy.serializer + +import com.github.salomonbrys.kotson.typeAdapter +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonToken +import ir.armor.tachidesk.impl.backup.models.TrackImpl + +/** + * JSON Serializer used to write / read [TrackImpl] to / from json + */ +object TrackTypeAdapter { + + private const val SYNC = "s" + private const val MEDIA = "r" + private const val LIBRARY = "ml" + private const val TITLE = "t" + private const val LAST_READ = "l" + private const val TRACKING_URL = "u" + + fun build(): TypeAdapter { + return typeAdapter { + write { + beginObject() + name(TITLE) + value(it.title) + name(SYNC) + value(it.sync_id) + name(MEDIA) + value(it.media_id) + name(LIBRARY) + value(it.library_id) + name(LAST_READ) + value(it.last_chapter_read) + name(TRACKING_URL) + value(it.tracking_url) + endObject() + } + + read { + val track = TrackImpl() + beginObject() + while (hasNext()) { + if (peek() == JsonToken.NAME) { + when (nextName()) { + TITLE -> track.title = nextString() + SYNC -> track.sync_id = nextInt() + MEDIA -> track.media_id = nextInt() + LIBRARY -> track.library_id = nextLong() + LAST_READ -> track.last_chapter_read = nextInt() + TRACKING_URL -> track.tracking_url = nextString() + } + } + } + endObject() + track + } + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Category.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Category.kt new file mode 100644 index 00000000..b403d92e --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Category.kt @@ -0,0 +1,23 @@ +package ir.armor.tachidesk.impl.backup.models + +import java.io.Serializable + +interface Category : Serializable { + + var id: Int? + + var name: String + + var order: Int + + var flags: Int + + companion object { + + fun create(name: String): Category = CategoryImpl().apply { + this.name = name + } + + fun createDefault(): Category = create("Default").apply { id = 0 } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/CategoryImpl.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/CategoryImpl.kt new file mode 100644 index 00000000..20c0c4ed --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/CategoryImpl.kt @@ -0,0 +1,24 @@ +package ir.armor.tachidesk.impl.backup.models + +class CategoryImpl : Category { + + override var id: Int? = null + + override lateinit var name: String + + override var order: Int = 0 + + override var flags: Int = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + val category = other as Category + return name == category.name + } + + override fun hashCode(): Int { + return name.hashCode() + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Chapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Chapter.kt new file mode 100644 index 00000000..99e21b30 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Chapter.kt @@ -0,0 +1,31 @@ +package ir.armor.tachidesk.impl.backup.models + +import eu.kanade.tachiyomi.source.model.SChapter +import java.io.Serializable + +interface Chapter : SChapter, Serializable { + + var id: Long? + + var manga_id: Long? + + var read: Boolean + + var bookmark: Boolean + + var last_page_read: Int + + var date_fetch: Long + + var source_order: Int + + val isRecognizedNumber: Boolean + get() = chapter_number >= 0f + + companion object { + + fun create(): Chapter = ChapterImpl().apply { + chapter_number = -1f + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/ChapterImpl.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/ChapterImpl.kt new file mode 100644 index 00000000..4a294175 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/ChapterImpl.kt @@ -0,0 +1,53 @@ +package ir.armor.tachidesk.impl.backup.models + +import org.jetbrains.exposed.sql.ResultRow + +class ChapterImpl : Chapter { + + override var id: Long? = null + + override var manga_id: Long? = null + + override lateinit var url: String + + override lateinit var name: String + + override var scanlator: String? = null + + override var read: Boolean = false + + override var bookmark: Boolean = false + + override var last_page_read: Int = 0 + + override var date_fetch: Long = 0 + + override var date_upload: Long = 0 + + override var chapter_number: Float = 0f + + override var source_order: Int = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + val chapter = other as Chapter + if (url != chapter.url) return false + return id == chapter.id + } + + override fun hashCode(): Int { + return url.hashCode() + id.hashCode() + } + + // Tachidesk --> + companion object { + fun fromQuery(chapterRecord: ResultRow): ChapterImpl { + return ChapterImpl().apply { + // TODO + } + } + } + // Tachidesk <-- +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/History.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/History.kt new file mode 100644 index 00000000..25b0f425 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/History.kt @@ -0,0 +1,42 @@ +package ir.armor.tachidesk.impl.backup.models + +import java.io.Serializable + +/** + * Object containing the history statistics of a chapter + */ +interface History : Serializable { + + /** + * Id of history object. + */ + var id: Long? + + /** + * Chapter id of history object. + */ + var chapter_id: Long + + /** + * Last time chapter was read in time long format + */ + var last_read: Long + + /** + * Total time chapter was read - todo not yet implemented + */ + var time_read: Long + + companion object { + + /** + * History constructor + * + * @param chapter chapter object + * @return history object + */ + fun create(chapter: Chapter): History = HistoryImpl().apply { + this.chapter_id = chapter.id!! + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/HistoryImpl.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/HistoryImpl.kt new file mode 100644 index 00000000..eb6036dc --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/HistoryImpl.kt @@ -0,0 +1,27 @@ +package ir.armor.tachidesk.impl.backup.models + +/** + * Object containing the history statistics of a chapter + */ +class HistoryImpl : History { + + /** + * Id of history object. + */ + override var id: Long? = null + + /** + * Chapter id of history object. + */ + override var chapter_id: Long = 0 + + /** + * Last time chapter was read in time long format + */ + override var last_read: Long = 0 + + /** + * Total time chapter was read - todo not yet implemented + */ + override var time_read: Long = 0 +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/LibraryManga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/LibraryManga.kt new file mode 100644 index 00000000..9a5c102b --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/LibraryManga.kt @@ -0,0 +1,8 @@ +package ir.armor.tachidesk.impl.backup.models + +class LibraryManga : MangaImpl() { + + var unread: Int = 0 + + var category: Int = 0 +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Manga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Manga.kt new file mode 100644 index 00000000..cfa3d069 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Manga.kt @@ -0,0 +1,115 @@ +package ir.armor.tachidesk.impl.backup.models + +import eu.kanade.tachiyomi.source.model.SManga +// import tachiyomi.source.model.MangaInfo + +interface Manga : SManga { + + var id: Long? + + var source: Long + + /** is in library */ + var favorite: Boolean + + var last_update: Long + + var date_added: Long + + var viewer: Int + + var chapter_flags: Int + + var cover_last_modified: Long + + fun setChapterOrder(order: Int) { + setFlags(order, SORT_MASK) + } + + fun sortDescending(): Boolean { + return chapter_flags and SORT_MASK == SORT_DESC + } + + fun getGenres(): List? { + return genre?.split(", ")?.map { it.trim() } + } + + private fun setFlags(flag: Int, mask: Int) { + chapter_flags = chapter_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) + + var readFilter: Int + get() = chapter_flags and READ_MASK + set(filter) = setFlags(filter, READ_MASK) + + var downloadedFilter: Int + get() = chapter_flags and DOWNLOADED_MASK + set(filter) = setFlags(filter, DOWNLOADED_MASK) + + var bookmarkedFilter: Int + get() = chapter_flags and BOOKMARKED_MASK + set(filter) = setFlags(filter, BOOKMARKED_MASK) + + var sorting: Int + get() = chapter_flags and SORTING_MASK + set(sort) = setFlags(sort, SORTING_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 SHOW_DOWNLOADED = 0x00000008 + const val SHOW_NOT_DOWNLOADED = 0x00000010 + const val DOWNLOADED_MASK = 0x00000018 + + const val SHOW_BOOKMARKED = 0x00000020 + const val SHOW_NOT_BOOKMARKED = 0x00000040 + const val BOOKMARKED_MASK = 0x00000060 + + const val SORTING_SOURCE = 0x00000000 + const val SORTING_NUMBER = 0x00000100 + const val SORTING_UPLOAD_DATE = 0x00000200 + const val SORTING_MASK = 0x00000300 + + const val DISPLAY_NAME = 0x00000000 + const val DISPLAY_NUMBER = 0x00100000 + const val DISPLAY_MASK = 0x00100000 + + fun create(source: Long): Manga = MangaImpl().apply { + this.source = source + } + + fun create(pathUrl: String, title: String, source: Long = 0): Manga = MangaImpl().apply { + url = pathUrl + this.title = title + this.source = source + } + } +} + +// fun Manga.toMangaInfo(): MangaInfo { +// return MangaInfo( +// artist = this.artist ?: "", +// author = this.author ?: "", +// cover = this.thumbnail_url ?: "", +// description = this.description ?: "", +// genres = this.getGenres() ?: emptyList(), +// key = this.url, +// status = this.status, +// title = this.title +// ) +// } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaCategory.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaCategory.kt new file mode 100644 index 00000000..a385edd1 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaCategory.kt @@ -0,0 +1,20 @@ +package ir.armor.tachidesk.impl.backup.models + +class MangaCategory { + + var id: Long? = null + + var manga_id: Long = 0 + + var category_id: Int = 0 + + companion object { + + fun create(manga: Manga, category: Category): MangaCategory { + val mc = MangaCategory() + mc.manga_id = manga.id!! + mc.category_id = category.id!! + return mc + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapter.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapter.kt new file mode 100644 index 00000000..d77f0e60 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapter.kt @@ -0,0 +1,3 @@ +package ir.armor.tachidesk.impl.backup.models + +class MangaChapter(val manga: Manga, val chapter: Chapter) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapterHistory.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapterHistory.kt new file mode 100644 index 00000000..eb1360f8 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaChapterHistory.kt @@ -0,0 +1,10 @@ +package ir.armor.tachidesk.impl.backup.models + +/** + * Object containing manga, chapter and history + * + * @param manga object containing manga + * @param chapter object containing chater + * @param history object containing history + */ +data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaImpl.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaImpl.kt new file mode 100644 index 00000000..a39a29ce --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/MangaImpl.kt @@ -0,0 +1,79 @@ +package ir.armor.tachidesk.impl.backup.models + +import ir.armor.tachidesk.model.database.MangaTable +import org.jetbrains.exposed.sql.ResultRow + +open class MangaImpl : Manga { + + override var id: Long? = 0 + + override var source: Long = -1 + + override lateinit var url: String + + override lateinit var title: String + + override var artist: String? = null + + override var author: String? = null + + override var description: String? = null + + override var genre: String? = null + + override var status: Int = 0 + + override var thumbnail_url: String? = null + + override var favorite: Boolean = false + + override var last_update: Long = 0 + + override var date_added: Long = 0 + + override var initialized: Boolean = false + + /** Reader mode value + * ref: https://github.com/tachiyomiorg/tachiyomi/blob/ff369010074b058bb734ce24c66508300e6e9ac6/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt#L8 + * 0 -> Default + * 1 -> Left to Right + * 2 -> Right to Left + * 3 -> Vertical + * 4 -> Webtoon + * 5 -> Continues Vertical + */ + override var viewer: Int = 0 + + /** Contains some useful info about + */ + override var chapter_flags: Int = 0 + + override var cover_last_modified: Long = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + val manga = other as Manga + if (url != manga.url) return false + return id == manga.id + } + + override fun hashCode(): Int { + return url.hashCode() + id.hashCode() + } + + // Tachidesk --> + companion object { + fun fromQuery(mangaRecord: ResultRow): MangaImpl { + return MangaImpl().apply { + url = mangaRecord[MangaTable.url] + title = mangaRecord[MangaTable.title] + source = mangaRecord[MangaTable.sourceReference] + viewer = 0 // TODO: implement + chapter_flags = 0 // TODO: implement + } + } + } + // Tachidesk <-- +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Track.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Track.kt new file mode 100644 index 00000000..a2080a10 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/Track.kt @@ -0,0 +1,46 @@ +package ir.armor.tachidesk.impl.backup.models + +import java.io.Serializable + +interface Track : Serializable { + + var id: Long? + + var manga_id: Long + + var sync_id: Int + + var media_id: Int + + var library_id: Long? + + var title: String + + var last_chapter_read: Int + + var total_chapters: Int + + var score: Float + + var status: Int + + var started_reading_date: Long + + var finished_reading_date: Long + + var tracking_url: String + + fun copyPersonalFrom(other: Track) { + last_chapter_read = other.last_chapter_read + score = other.score + status = other.status + started_reading_date = other.started_reading_date + finished_reading_date = other.finished_reading_date + } + + companion object { + fun create(serviceId: Int): Track = TrackImpl().apply { + sync_id = serviceId + } + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/TrackImpl.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/TrackImpl.kt new file mode 100644 index 00000000..9ad51aa0 --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/models/TrackImpl.kt @@ -0,0 +1,48 @@ +package ir.armor.tachidesk.impl.backup.models + +class TrackImpl : Track { + + override var id: Long? = null + + override var manga_id: Long = 0 + + override var sync_id: Int = 0 + + override var media_id: Int = 0 + + override var library_id: Long? = null + + override lateinit var title: String + + override var last_chapter_read: Int = 0 + + override var total_chapters: Int = 0 + + override var score: Float = 0f + + override var status: Int = 0 + + override var started_reading_date: Long = 0 + + override var finished_reading_date: Long = 0 + + override var tracking_url: String = "" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + other as Track + + if (manga_id != other.manga_id) return false + if (sync_id != other.sync_id) return false + return media_id == other.media_id + } + + override fun hashCode(): Int { + var result = (manga_id xor manga_id.ushr(32)).toInt() + result = 31 * result + sync_id + result = 31 * result + media_id + return result + } +} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt index 925a9aef..0f9a049f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt @@ -30,6 +30,8 @@ import ir.armor.tachidesk.impl.Search.sourceGlobalSearch import ir.armor.tachidesk.impl.Search.sourceSearch import ir.armor.tachidesk.impl.Source.getSource import ir.armor.tachidesk.impl.Source.getSourceList +import ir.armor.tachidesk.impl.backup.BackupFlags +import ir.armor.tachidesk.impl.backup.legacy.LegacyBackupExport.createBackup import ir.armor.tachidesk.server.util.openInBrowser import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -325,8 +327,21 @@ object JavalinSetup { // expects a Tachiyomi legacy backup file to be uploaded app.get("/api/v1/backup/legacy/import") { ctx -> - val categoryId = ctx.pathParam("categoryId").toInt() - ctx.json(getCategoryMangaList(categoryId)) + + ctx.contentType("application/json") + ctx.result( + future { + createBackup( + BackupFlags( + includeManga = true, + includeCategories = true, + includeChapters = true, + includeTracking = true, + includeHistory = true, + ) + ) + } + ) } // returns a Tachiyomi legacy backup file created from the current database