mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2026-01-30 15:34:15 +01:00
a partially working legacy import...
This commit is contained in:
358
server/src/main/kotlin/eu/kanade/tachiyomi/source/LocalSource.kt
Normal file
358
server/src/main/kotlin/eu/kanade/tachiyomi/source/LocalSource.kt
Normal file
@@ -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<File> {
|
||||
// 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<SManga> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
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<MangasPage> {
|
||||
// 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<SManga> {
|
||||
// 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<List<SChapter>> {
|
||||
// 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<List<Page>> {
|
||||
// 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()
|
||||
// }
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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<MangaImpl>(MangaTypeAdapter.build())
|
||||
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
|
||||
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
|
||||
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
|
||||
.registerTypeHierarchyAdapter<TrackImpl>(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<String> = 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<String>) { // TODO
|
||||
// extensions.sorted().forEach {
|
||||
// root.add(it)
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package ir.armor.tachidesk.impl.backup.legacy.models
|
||||
|
||||
data class DHistory(val url: String, val lastRead: Long)
|
||||
@@ -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<CategoryImpl> {
|
||||
return typeAdapter {
|
||||
write {
|
||||
beginArray()
|
||||
value(it.name)
|
||||
value(it.order)
|
||||
endArray()
|
||||
}
|
||||
|
||||
read {
|
||||
beginArray()
|
||||
val category = CategoryImpl()
|
||||
category.name = nextString()
|
||||
category.order = nextInt()
|
||||
endArray()
|
||||
category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ChapterImpl> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<DHistory> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<MangaImpl> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TrackImpl> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <--
|
||||
}
|
||||
@@ -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!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ir.armor.tachidesk.impl.backup.models
|
||||
|
||||
class LibraryManga : MangaImpl() {
|
||||
|
||||
var unread: Int = 0
|
||||
|
||||
var category: Int = 0
|
||||
}
|
||||
@@ -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<String>? {
|
||||
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
|
||||
// )
|
||||
// }
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package ir.armor.tachidesk.impl.backup.models
|
||||
|
||||
class MangaChapter(val manga: Manga, val chapter: Chapter)
|
||||
@@ -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)
|
||||
@@ -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 <--
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user