mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Legacy backup conversion to Kotlin Serialization (#5282)
* Legacy backup conversion to Kotlin Serialization * Fix BackupTest compiling
This commit is contained in:
		| @@ -2,44 +2,52 @@ package eu.kanade.tachiyomi.data.backup.legacy | ||||
|  | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import com.github.salomonbrys.kotson.fromJson | ||||
| import com.github.salomonbrys.kotson.registerTypeAdapter | ||||
| import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter | ||||
| import com.google.gson.Gson | ||||
| import com.google.gson.GsonBuilder | ||||
| import com.google.gson.JsonArray | ||||
| import eu.kanade.tachiyomi.data.backup.AbstractBackupManager | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CURRENT_VERSION | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.Companion.CURRENT_VERSION | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeAdapter | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeAdapter | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeAdapter | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeAdapter | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeAdapter | ||||
| import eu.kanade.tachiyomi.data.database.models.CategoryImpl | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryImplTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterImplTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaImplTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackImplTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeSerializer | ||||
| import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.ChapterImpl | ||||
| import eu.kanade.tachiyomi.data.database.models.History | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaCategory | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaImpl | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.database.models.TrackImpl | ||||
| import eu.kanade.tachiyomi.data.database.models.toMangaInfo | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import eu.kanade.tachiyomi.source.model.toSManga | ||||
| import kotlinx.serialization.json.Json | ||||
| import kotlinx.serialization.modules.SerializersModule | ||||
| import kotlinx.serialization.modules.contextual | ||||
| import kotlin.math.max | ||||
|  | ||||
| class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) { | ||||
|  | ||||
|     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() | ||||
|     val parser: Json = when (version) { | ||||
|         2 -> Json { | ||||
|             // Forks may have added items to backup | ||||
|             ignoreUnknownKeys = true | ||||
|  | ||||
|             // Register custom serializers | ||||
|             serializersModule = SerializersModule { | ||||
|                 contextual(MangaTypeSerializer) | ||||
|                 contextual(MangaImplTypeSerializer) | ||||
|                 contextual(ChapterTypeSerializer) | ||||
|                 contextual(ChapterImplTypeSerializer) | ||||
|                 contextual(CategoryTypeSerializer) | ||||
|                 contextual(CategoryImplTypeSerializer) | ||||
|                 contextual(TrackTypeSerializer) | ||||
|                 contextual(TrackImplTypeSerializer) | ||||
|                 contextual(HistoryTypeSerializer) | ||||
|             } | ||||
|         } | ||||
|         else -> throw Exception("Unknown backup version") | ||||
|     } | ||||
|  | ||||
| @@ -79,12 +87,11 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab | ||||
|     /** | ||||
|      * Restore the categories from Json | ||||
|      * | ||||
|      * @param jsonCategories array containing categories | ||||
|      * @param backupCategories array containing categories | ||||
|      */ | ||||
|     internal fun restoreCategories(jsonCategories: JsonArray) { | ||||
|     internal fun restoreCategories(backupCategories: List<Category>) { | ||||
|         // Get categories from file and from db | ||||
|         val dbCategories = databaseHelper.getCategories().executeAsBlocking() | ||||
|         val backupCategories = parser.fromJson<List<CategoryImpl>>(jsonCategories) | ||||
|  | ||||
|         // Iterate over them | ||||
|         backupCategories.forEach { category -> | ||||
|   | ||||
| @@ -2,88 +2,80 @@ package eu.kanade.tachiyomi.data.backup.legacy | ||||
|  | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import com.github.salomonbrys.kotson.fromJson | ||||
| import com.google.gson.JsonArray | ||||
| import com.google.gson.JsonElement | ||||
| import com.google.gson.JsonObject | ||||
| import com.google.gson.JsonParser | ||||
| import com.google.gson.stream.JsonReader | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore | ||||
| import eu.kanade.tachiyomi.data.backup.BackupNotifier | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.Backup | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MANGAS | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.MangaObject | ||||
| import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.ChapterImpl | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaImpl | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.database.models.TrackImpl | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import kotlinx.serialization.decodeFromString | ||||
| import kotlinx.serialization.json.Json | ||||
| import kotlinx.serialization.json.JsonObject | ||||
| import kotlinx.serialization.json.decodeFromJsonElement | ||||
| import kotlinx.serialization.json.intOrNull | ||||
| import kotlinx.serialization.json.jsonPrimitive | ||||
| import okio.buffer | ||||
| import okio.source | ||||
| import java.util.Date | ||||
|  | ||||
| class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) { | ||||
|  | ||||
|     override suspend fun performRestore(uri: Uri): Boolean { | ||||
|         val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader()) | ||||
|         val json = JsonParser.parseReader(reader).asJsonObject | ||||
|         // Read the json and create a Json Object, | ||||
|         // cannot use the backupManager json deserializer one because its not initialized yet | ||||
|         val backupObject = Json.decodeFromString<JsonObject>( | ||||
|             context.contentResolver.openInputStream(uri)!!.source().buffer().use { it.readUtf8() } | ||||
|         ) | ||||
|  | ||||
|         val version = json.get(Backup.VERSION)?.asInt ?: 1 | ||||
|         // Get parser version | ||||
|         val version = backupObject["version"]?.jsonPrimitive?.intOrNull ?: 1 | ||||
|  | ||||
|         // Initialize manager | ||||
|         backupManager = LegacyBackupManager(context, version) | ||||
|  | ||||
|         val mangasJson = json.get(MANGAS).asJsonArray | ||||
|         restoreAmount = mangasJson.size() + 1 // +1 for categories | ||||
|         // Decode the json object to a Backup object | ||||
|         val backup = backupManager.parser.decodeFromJsonElement<Backup>(backupObject) | ||||
|  | ||||
|         restoreAmount = backup.mangas.size + 1 // +1 for categories | ||||
|  | ||||
|         // Restore categories | ||||
|         json.get(Backup.CATEGORIES)?.let { restoreCategories(it) } | ||||
|         backup.categories?.let { restoreCategories(it) } | ||||
|  | ||||
|         // Store source mapping for error messages | ||||
|         sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(json) | ||||
|         sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(backup.extensions ?: emptyList()) | ||||
|  | ||||
|         // Restore individual manga | ||||
|         mangasJson.forEach { | ||||
|         backup.mangas.forEach { | ||||
|             if (job?.isActive != true) { | ||||
|                 return false | ||||
|             } | ||||
|  | ||||
|             restoreManga(it.asJsonObject) | ||||
|             restoreManga(it) | ||||
|         } | ||||
|  | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     private fun restoreCategories(categoriesJson: JsonElement) { | ||||
|     private fun restoreCategories(categoriesJson: List<Category>) { | ||||
|         db.inTransaction { | ||||
|             backupManager.restoreCategories(categoriesJson.asJsonArray) | ||||
|             backupManager.restoreCategories(categoriesJson) | ||||
|         } | ||||
|  | ||||
|         restoreProgress += 1 | ||||
|         showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories)) | ||||
|     } | ||||
|  | ||||
|     private suspend fun restoreManga(mangaJson: JsonObject) { | ||||
|         val manga = backupManager.parser.fromJson<MangaImpl>( | ||||
|             mangaJson.get( | ||||
|                 Backup.MANGA | ||||
|             ) | ||||
|         ) | ||||
|         val chapters = backupManager.parser.fromJson<List<ChapterImpl>>( | ||||
|             mangaJson.get(Backup.CHAPTERS) | ||||
|                 ?: JsonArray() | ||||
|         ) | ||||
|         val categories = backupManager.parser.fromJson<List<String>>( | ||||
|             mangaJson.get(Backup.CATEGORIES) | ||||
|                 ?: JsonArray() | ||||
|         ) | ||||
|         val history = backupManager.parser.fromJson<List<DHistory>>( | ||||
|             mangaJson.get(Backup.HISTORY) | ||||
|                 ?: JsonArray() | ||||
|         ) | ||||
|         val tracks = backupManager.parser.fromJson<List<TrackImpl>>( | ||||
|             mangaJson.get(Backup.TRACK) | ||||
|                 ?: JsonArray() | ||||
|         ) | ||||
|     private suspend fun restoreManga(mangaJson: MangaObject) { | ||||
|         val manga = mangaJson.manga | ||||
|         val chapters = mangaJson.chapters ?: emptyList() | ||||
|         val categories = mangaJson.categories ?: emptyList() | ||||
|         val history = mangaJson.history ?: emptyList() | ||||
|         val tracks = mangaJson.track ?: emptyList() | ||||
|  | ||||
|         val source = backupManager.sourceManager.get(manga.source) | ||||
|         val sourceName = sourceMapping[manga.source] ?: manga.source.toString() | ||||
|   | ||||
| @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.data.backup.legacy | ||||
|  | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import com.google.gson.JsonObject | ||||
| import com.google.gson.JsonParser | ||||
| import com.google.gson.stream.JsonReader | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.Backup | ||||
| import kotlinx.serialization.decodeFromString | ||||
| import okio.buffer | ||||
| import okio.source | ||||
|  | ||||
| class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() { | ||||
|     /** | ||||
| @@ -17,30 +17,30 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() { | ||||
|      * @return List of missing sources or missing trackers. | ||||
|      */ | ||||
|     override fun validate(context: Context, uri: Uri): Results { | ||||
|         val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader()) | ||||
|         val json = JsonParser.parseReader(reader).asJsonObject | ||||
|         val backupManager = LegacyBackupManager(context) | ||||
|  | ||||
|         val version = json.get(Backup.VERSION) | ||||
|         val mangasJson = json.get(Backup.MANGAS) | ||||
|         if (version == null || mangasJson == null) { | ||||
|         val backup = backupManager.parser.decodeFromString<Backup>( | ||||
|             context.contentResolver.openInputStream(uri)!!.source().buffer().use { it.readUtf8() } | ||||
|         ) | ||||
|  | ||||
|         if (backup.version == null) { | ||||
|             throw Exception(context.getString(R.string.invalid_backup_file_missing_data)) | ||||
|         } | ||||
|  | ||||
|         val mangas = mangasJson.asJsonArray | ||||
|         if (mangas.size() == 0) { | ||||
|         if (backup.mangas.isEmpty()) { | ||||
|             throw Exception(context.getString(R.string.invalid_backup_file_missing_manga)) | ||||
|         } | ||||
|  | ||||
|         val sources = getSourceMapping(json) | ||||
|         val sources = getSourceMapping(backup.extensions ?: emptyList()) | ||||
|         val missingSources = sources | ||||
|             .filter { sourceManager.get(it.key) == null } | ||||
|             .values | ||||
|             .sorted() | ||||
|  | ||||
|         val trackers = mangas | ||||
|             .filter { it.asJsonObject.has("track") } | ||||
|             .flatMap { it.asJsonObject["track"].asJsonArray } | ||||
|             .map { it.asJsonObject["s"].asInt } | ||||
|         val trackers = backup.mangas | ||||
|             .filterNot { it.track.isNullOrEmpty() } | ||||
|             .flatMap { it.track ?: emptyList() } | ||||
|             .map { it.sync_id } | ||||
|             .distinct() | ||||
|         val missingTrackers = trackers | ||||
|             .mapNotNull { trackManager.getService(it) } | ||||
| @@ -52,12 +52,10 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() { | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         fun getSourceMapping(json: JsonObject): Map<Long, String> { | ||||
|             val extensionsMapping = json.get(Backup.EXTENSIONS) ?: return emptyMap() | ||||
|  | ||||
|             return extensionsMapping.asJsonArray | ||||
|         fun getSourceMapping(extensionsMapping: List<String>): Map<Long, String> { | ||||
|             return extensionsMapping | ||||
|                 .map { | ||||
|                     val items = it.asString.split(":") | ||||
|                     val items = it.split(":") | ||||
|                     items[0].toLong() to items[1] | ||||
|                 } | ||||
|                 .toMap() | ||||
|   | ||||
| @@ -1,25 +1,37 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.models | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import kotlinx.serialization.Contextual | ||||
| import kotlinx.serialization.Serializable | ||||
| 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" | ||||
| @Serializable | ||||
| data class Backup( | ||||
|     val version: Int? = null, | ||||
|     var mangas: MutableList<MangaObject> = mutableListOf(), | ||||
|     var categories: List<@Contextual Category>? = null, | ||||
|     var extensions: List<String>? = null | ||||
| ) { | ||||
|     companion object { | ||||
|         const val CURRENT_VERSION = 2 | ||||
|  | ||||
|     fun getDefaultFilename(): String { | ||||
|         val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date()) | ||||
|         return "tachiyomi_$date.json" | ||||
|         fun getDefaultFilename(): String { | ||||
|             val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date()) | ||||
|             return "tachiyomi_$date.json" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Serializable | ||||
| data class MangaObject( | ||||
|     var manga: @Contextual Manga, | ||||
|     var chapters: List<@Contextual Chapter>? = null, | ||||
|     var categories: List<String>? = null, | ||||
|     var track: List<@Contextual Track>? = null, | ||||
|     var history: List<@Contextual DHistory>? = null | ||||
| ) | ||||
|   | ||||
| @@ -1,31 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import com.github.salomonbrys.kotson.typeAdapter | ||||
| import com.google.gson.TypeAdapter | ||||
| import eu.kanade.tachiyomi.data.database.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,49 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.CategoryImpl | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.descriptors.buildClassSerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import kotlinx.serialization.json.JsonDecoder | ||||
| import kotlinx.serialization.json.JsonEncoder | ||||
| import kotlinx.serialization.json.add | ||||
| import kotlinx.serialization.json.buildJsonArray | ||||
| import kotlinx.serialization.json.int | ||||
| import kotlinx.serialization.json.jsonArray | ||||
| import kotlinx.serialization.json.jsonPrimitive | ||||
|  | ||||
| /** | ||||
|  * JSON Serializer used to write / read [CategoryImpl] to / from json | ||||
|  */ | ||||
| open class CategoryBaseSerializer<T : Category> : KSerializer<T> { | ||||
|     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Category") | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: T) { | ||||
|         encoder as JsonEncoder | ||||
|         encoder.encodeJsonElement( | ||||
|             buildJsonArray { | ||||
|                 add(value.name) | ||||
|                 add(value.order) | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Suppress("UNCHECKED_CAST") | ||||
|     override fun deserialize(decoder: Decoder): T { | ||||
|         // make a category impl and cast as T so that the serializer accepts it | ||||
|         return CategoryImpl().apply { | ||||
|             decoder as JsonDecoder | ||||
|             val array = decoder.decodeJsonElement().jsonArray | ||||
|             name = array[0].jsonPrimitive.content | ||||
|             order = array[1].jsonPrimitive.int | ||||
|         } as T | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Allow for serialization of a category and category impl | ||||
| object CategoryTypeSerializer : CategoryBaseSerializer<Category>() | ||||
|  | ||||
| object CategoryImplTypeSerializer : CategoryBaseSerializer<CategoryImpl>() | ||||
| @@ -1,59 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import com.github.salomonbrys.kotson.typeAdapter | ||||
| import com.google.gson.TypeAdapter | ||||
| import com.google.gson.stream.JsonToken | ||||
| import eu.kanade.tachiyomi.data.database.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,66 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.ChapterImpl | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.descriptors.buildClassSerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import kotlinx.serialization.json.JsonDecoder | ||||
| import kotlinx.serialization.json.JsonEncoder | ||||
| import kotlinx.serialization.json.buildJsonObject | ||||
| import kotlinx.serialization.json.intOrNull | ||||
| import kotlinx.serialization.json.jsonObject | ||||
| import kotlinx.serialization.json.jsonPrimitive | ||||
| import kotlinx.serialization.json.put | ||||
|  | ||||
| /** | ||||
|  * JSON Serializer used to write / read [ChapterImpl] to / from json | ||||
|  */ | ||||
| open class ChapterBaseSerializer<T : Chapter> : KSerializer<T> { | ||||
|  | ||||
|     override val descriptor = buildClassSerialDescriptor("Chapter") | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: T) { | ||||
|         encoder as JsonEncoder | ||||
|         encoder.encodeJsonElement( | ||||
|             buildJsonObject { | ||||
|                 put(URL, value.url) | ||||
|                 if (value.read) { | ||||
|                     put(READ, 1) | ||||
|                 } | ||||
|                 if (value.bookmark) { | ||||
|                     put(BOOKMARK, 1) | ||||
|                 } | ||||
|                 if (value.last_page_read != 0) { | ||||
|                     put(LAST_READ, value.last_page_read) | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Suppress("UNCHECKED_CAST") | ||||
|     override fun deserialize(decoder: Decoder): T { | ||||
|         // make a chapter impl and cast as T so that the serializer accepts it | ||||
|         return ChapterImpl().apply { | ||||
|             decoder as JsonDecoder | ||||
|             val jsonObject = decoder.decodeJsonElement().jsonObject | ||||
|             url = jsonObject[URL]!!.jsonPrimitive.content | ||||
|             read = jsonObject[READ]?.jsonPrimitive?.intOrNull == 1 | ||||
|             bookmark = jsonObject[BOOKMARK]?.jsonPrimitive?.intOrNull == 1 | ||||
|             last_page_read = jsonObject[LAST_READ]?.jsonPrimitive?.intOrNull ?: last_page_read | ||||
|         } as T | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private const val URL = "u" | ||||
|         private const val READ = "r" | ||||
|         private const val BOOKMARK = "b" | ||||
|         private const val LAST_READ = "l" | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Allow for serialization of a chapter and chapter impl | ||||
| object ChapterTypeSerializer : ChapterBaseSerializer<Chapter>() | ||||
|  | ||||
| object ChapterImplTypeSerializer : ChapterBaseSerializer<ChapterImpl>() | ||||
| @@ -1,32 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import com.github.salomonbrys.kotson.typeAdapter | ||||
| import com.google.gson.TypeAdapter | ||||
| import eu.kanade.tachiyomi.data.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,41 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.descriptors.buildClassSerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import kotlinx.serialization.json.JsonDecoder | ||||
| import kotlinx.serialization.json.JsonEncoder | ||||
| import kotlinx.serialization.json.add | ||||
| import kotlinx.serialization.json.buildJsonArray | ||||
| import kotlinx.serialization.json.jsonArray | ||||
| import kotlinx.serialization.json.jsonPrimitive | ||||
| import kotlinx.serialization.json.long | ||||
|  | ||||
| /** | ||||
|  * JSON Serializer used to write / read [DHistory] to / from json | ||||
|  */ | ||||
| object HistoryTypeSerializer : KSerializer<DHistory> { | ||||
|     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("History") | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: DHistory) { | ||||
|         encoder as JsonEncoder | ||||
|         encoder.encodeJsonElement( | ||||
|             buildJsonArray { | ||||
|                 add(value.url) | ||||
|                 add(value.lastRead) | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     override fun deserialize(decoder: Decoder): DHistory { | ||||
|         decoder as JsonDecoder | ||||
|         val array = decoder.decodeJsonElement().jsonArray | ||||
|         return DHistory( | ||||
|             url = array[0].jsonPrimitive.content, | ||||
|             lastRead = array[1].jsonPrimitive.long | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import com.github.salomonbrys.kotson.typeAdapter | ||||
| import com.google.gson.TypeAdapter | ||||
| import eu.kanade.tachiyomi.data.database.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_flags) | ||||
|                 value(it.chapter_flags) | ||||
|                 endArray() | ||||
|             } | ||||
|  | ||||
|             read { | ||||
|                 beginArray() | ||||
|                 val manga = MangaImpl() | ||||
|                 manga.url = nextString() | ||||
|                 manga.title = nextString() | ||||
|                 manga.source = nextLong() | ||||
|                 manga.viewer_flags = nextInt() | ||||
|                 manga.chapter_flags = nextInt() | ||||
|                 endArray() | ||||
|                 manga | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,56 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaImpl | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.descriptors.buildClassSerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import kotlinx.serialization.json.JsonDecoder | ||||
| import kotlinx.serialization.json.JsonEncoder | ||||
| import kotlinx.serialization.json.add | ||||
| import kotlinx.serialization.json.buildJsonArray | ||||
| import kotlinx.serialization.json.int | ||||
| import kotlinx.serialization.json.jsonArray | ||||
| import kotlinx.serialization.json.jsonPrimitive | ||||
| import kotlinx.serialization.json.long | ||||
|  | ||||
| /** | ||||
|  * JSON Serializer used to write / read [MangaImpl] to / from json | ||||
|  */ | ||||
| open class MangaBaseSerializer<T : Manga> : KSerializer<T> { | ||||
|     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Manga") | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: T) { | ||||
|         encoder as JsonEncoder | ||||
|         encoder.encodeJsonElement( | ||||
|             buildJsonArray { | ||||
|                 add(value.url) | ||||
|                 add(value.title) | ||||
|                 add(value.source) | ||||
|                 add(value.viewer_flags) | ||||
|                 add(value.chapter_flags) | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Suppress("UNCHECKED_CAST") | ||||
|     override fun deserialize(decoder: Decoder): T { | ||||
|         // make a manga impl and cast as T so that the serializer accepts it | ||||
|         return MangaImpl().apply { | ||||
|             decoder as JsonDecoder | ||||
|             val array = decoder.decodeJsonElement().jsonArray | ||||
|             url = array[0].jsonPrimitive.content | ||||
|             title = array[1].jsonPrimitive.content | ||||
|             source = array[2].jsonPrimitive.long | ||||
|             viewer_flags = array[3].jsonPrimitive.int | ||||
|             chapter_flags = array[4].jsonPrimitive.int | ||||
|         } as T | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Allow for serialization of a manga and manga impl | ||||
| object MangaTypeSerializer : MangaBaseSerializer<Manga>() | ||||
|  | ||||
| object MangaImplTypeSerializer : MangaBaseSerializer<MangaImpl>() | ||||
| @@ -1,59 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import com.github.salomonbrys.kotson.typeAdapter | ||||
| import com.google.gson.TypeAdapter | ||||
| import com.google.gson.stream.JsonToken | ||||
| import eu.kanade.tachiyomi.data.database.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,67 @@ | ||||
| package eu.kanade.tachiyomi.data.backup.legacy.serializer | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.database.models.Track | ||||
| import eu.kanade.tachiyomi.data.database.models.TrackImpl | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.descriptors.buildClassSerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import kotlinx.serialization.json.JsonDecoder | ||||
| import kotlinx.serialization.json.JsonEncoder | ||||
| import kotlinx.serialization.json.buildJsonObject | ||||
| import kotlinx.serialization.json.int | ||||
| import kotlinx.serialization.json.jsonObject | ||||
| import kotlinx.serialization.json.jsonPrimitive | ||||
| import kotlinx.serialization.json.long | ||||
| import kotlinx.serialization.json.put | ||||
|  | ||||
| /** | ||||
|  * JSON Serializer used to write / read [TrackImpl] to / from json | ||||
|  */ | ||||
| open class TrackBaseSerializer<T : Track> : KSerializer<T> { | ||||
|     override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Track") | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: T) { | ||||
|         encoder as JsonEncoder | ||||
|         encoder.encodeJsonElement( | ||||
|             buildJsonObject { | ||||
|                 put(TITLE, value.title) | ||||
|                 put(SYNC, value.sync_id) | ||||
|                 put(MEDIA, value.media_id) | ||||
|                 put(LIBRARY, value.library_id) | ||||
|                 put(LAST_READ, value.last_chapter_read) | ||||
|                 put(TRACKING_URL, value.tracking_url) | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Suppress("UNCHECKED_CAST") | ||||
|     override fun deserialize(decoder: Decoder): T { | ||||
|         // make a track impl and cast as T so that the serializer accepts it | ||||
|         return TrackImpl().apply { | ||||
|             decoder as JsonDecoder | ||||
|             val jsonObject = decoder.decodeJsonElement().jsonObject | ||||
|             title = jsonObject[TITLE]!!.jsonPrimitive.content | ||||
|             sync_id = jsonObject[SYNC]!!.jsonPrimitive.int | ||||
|             media_id = jsonObject[MEDIA]!!.jsonPrimitive.int | ||||
|             library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long | ||||
|             last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.int | ||||
|             tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content | ||||
|         } as T | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         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" | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Allow for serialization of a track and track impl | ||||
| object TrackTypeSerializer : TrackBaseSerializer<Track>() | ||||
|  | ||||
| object TrackImplTypeSerializer : TrackBaseSerializer<TrackImpl>() | ||||
		Reference in New Issue
	
	Block a user