From 7a01fdd04c18d4f90d4374a3ebb72dbf4f92c2ca Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 28 Feb 2025 15:35:56 +0200 Subject: [PATCH] Update parsers and adjust database --- .../kotatsu/core/backup/JsonDeserializer.kt | 5 +++-- .../kotatsu/core/backup/JsonSerializer.kt | 5 +++-- .../koitharu/kotatsu/core/db/MangaDatabase.kt | 4 +++- .../kotatsu/core/db/entity/EntityMapping.kt | 22 ++++++++++++++----- .../kotatsu/core/db/entity/MangaEntity.kt | 7 +++--- .../core/db/migrations/Migration24To25.kt | 12 ++++++++++ .../core/model/parcelable/ParcelableManga.kt | 10 +++++---- .../kotatsu/core/parser/DummyParser.kt | 18 +++++++-------- .../koitharu/kotatsu/core/util/ext/Bundle.kt | 9 ++++++++ .../container/FavouritesContainerViewModel.kt | 1 + .../koitharu/kotatsu/local/data/MangaIndex.kt | 15 +++++++++---- .../local/data/input/LocalMangaParser.kt | 6 ++--- gradle/libs.versions.toml | 2 +- 13 files changed, 81 insertions(+), 35 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration24To25.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt index cc042d7fe..8ecc60e98 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt @@ -28,15 +28,16 @@ class JsonDeserializer(private val json: JSONObject) { fun toMangaEntity() = MangaEntity( id = json.getLong("id"), title = json.getString("title"), - altTitle = json.getStringOrNull("alt_title"), + altTitles = json.getStringOrNull("alt_title"), url = json.getString("url"), publicUrl = json.getStringOrNull("public_url").orEmpty(), rating = json.getDouble("rating").toFloat(), isNsfw = json.getBooleanOrDefault("nsfw", false), + contentRating = json.getStringOrNull("content_rating"), coverUrl = json.getString("cover_url"), largeCoverUrl = json.getStringOrNull("large_cover_url"), state = json.getStringOrNull("state"), - author = json.getStringOrNull("author"), + authors = json.getStringOrNull("author"), source = json.getString("source"), ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt index 28bf270da..16c3af122 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt @@ -58,15 +58,16 @@ class JsonSerializer private constructor(private val json: JSONObject) { JSONObject().apply { put("id", e.id) put("title", e.title) - put("alt_title", e.altTitle) + put("alt_title", e.altTitles) put("url", e.url) put("public_url", e.publicUrl) put("rating", e.rating) put("nsfw", e.isNsfw) + put("content_rating", e.contentRating) put("cover_url", e.coverUrl) put("large_cover_url", e.largeCoverUrl) put("state", e.state) - put("author", e.author) + put("author", e.authors) put("source", e.source) }, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 116d08ce2..52579ee8f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -40,6 +40,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration21To22 import org.koitharu.kotatsu.core.db.migrations.Migration22To23 import org.koitharu.kotatsu.core.db.migrations.Migration23To24 import org.koitharu.kotatsu.core.db.migrations.Migration24To23 +import org.koitharu.kotatsu.core.db.migrations.Migration24To25 import org.koitharu.kotatsu.core.db.migrations.Migration2To3 import org.koitharu.kotatsu.core.db.migrations.Migration3To4 import org.koitharu.kotatsu.core.db.migrations.Migration4To5 @@ -67,7 +68,7 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity import org.koitharu.kotatsu.tracker.data.TrackLogEntity import org.koitharu.kotatsu.tracker.data.TracksDao -const val DATABASE_VERSION = 24 +const val DATABASE_VERSION = 25 @Database( entities = [ @@ -136,6 +137,7 @@ fun getDatabaseMigrations(context: Context): Array = arrayOf( Migration22To23(), Migration23To24(), Migration24To23(), + Migration24To25(), ) fun MangaDatabase(context: Context): MangaDatabase = Room diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt index fd9d83f28..1689a5115 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.core.db.entity import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.parsers.model.ContentRating import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaState @@ -8,8 +9,11 @@ import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.util.longHashCode import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.parsers.util.toArraySet import org.koitharu.kotatsu.parsers.util.toTitleCase +private const val VALUES_DIVIDER = '\n' + // Entity to model fun TagEntity.toMangaTag() = MangaTag( @@ -22,18 +26,19 @@ fun Collection.toMangaTags() = mapToSet(TagEntity::toMangaTag) fun Collection.toMangaTagsList() = map(TagEntity::toMangaTag) -fun MangaEntity.toManga(tags: Set, chapters: List?) = Manga( // TODO +fun MangaEntity.toManga(tags: Set, chapters: List?) = Manga( id = this.id, title = this.title, - altTitle = this.altTitle, + altTitles = this.altTitles?.split(VALUES_DIVIDER)?.toArraySet().orEmpty(), state = this.state?.let { MangaState(it) }, rating = this.rating, - isNsfw = this.isNsfw, + contentRating = ContentRating(this.contentRating) + ?: if (isNsfw) ContentRating.ADULT else null, url = this.url, publicUrl = this.publicUrl, coverUrl = this.coverUrl, largeCoverUrl = this.largeCoverUrl, - author = this.author, + authors = this.authors?.split(VALUES_DIVIDER)?.toArraySet().orEmpty(), source = MangaSource(this.source), tags = tags, chapters = chapters?.toMangaChapters(), @@ -66,12 +71,13 @@ fun Manga.toEntity() = MangaEntity( source = source.name, largeCoverUrl = largeCoverUrl, coverUrl = coverUrl.orEmpty(), - altTitle = altTitle, + altTitles = altTitles.joinToString(VALUES_DIVIDER.toString()), rating = rating, isNsfw = isNsfw, + contentRating = contentRating?.name, state = state?.name, title = title, - author = author, + authors = authors.joinToString(VALUES_DIVIDER.toString()), ) fun MangaTag.toEntity() = TagEntity( @@ -108,3 +114,7 @@ fun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching { fun MangaState(name: String): MangaState? = runCatching { MangaState.valueOf(name) }.getOrNull() + +fun ContentRating(name: String?): ContentRating? = runCatching { + ContentRating.valueOf(name ?: return@runCatching null) +}.getOrNull() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt index 9156db7b7..fd4a23015 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt @@ -10,14 +10,15 @@ data class MangaEntity( @PrimaryKey(autoGenerate = false) @ColumnInfo(name = "manga_id") val id: Long, @ColumnInfo(name = "title") val title: String, - @ColumnInfo(name = "alt_title") val altTitle: String?, + @ColumnInfo(name = "alt_title") val altTitles: String?, @ColumnInfo(name = "url") val url: String, @ColumnInfo(name = "public_url") val publicUrl: String, @ColumnInfo(name = "rating") val rating: Float, // normalized value [0..1] or -1 - @ColumnInfo(name = "nsfw") val isNsfw: Boolean, // TODO change to contentRating + @ColumnInfo(name = "nsfw") val isNsfw: Boolean, + @ColumnInfo(name = "content_rating") val contentRating: String?, @ColumnInfo(name = "cover_url") val coverUrl: String, @ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?, @ColumnInfo(name = "state") val state: String?, - @ColumnInfo(name = "author") val author: String?, + @ColumnInfo(name = "author") val authors: String?, @ColumnInfo(name = "source") val source: String, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration24To25.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration24To25.kt new file mode 100644 index 000000000..6916efa60 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration24To25.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.core.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration24To25 : Migration(24, 25) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE manga ADD COLUMN content_rating TEXT DEFAULT NULL") + db.execSQL("UPDATE manga SET content_rating = (SELECT IIF(m.nsfw, 'ADULT', NULL) FROM manga AS m WHERE manga.manga_id = m.manga_id)") + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt index 58b28adde..5d8824be1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt @@ -7,6 +7,8 @@ import kotlinx.parcelize.Parcelize import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.util.ext.readParcelableCompat import org.koitharu.kotatsu.core.util.ext.readSerializableCompat +import org.koitharu.kotatsu.core.util.ext.readStringSet +import org.koitharu.kotatsu.core.util.ext.writeStringSet import org.koitharu.kotatsu.parsers.model.Manga @Parcelize @@ -20,7 +22,7 @@ data class ParcelableManga( override fun ParcelableManga.write(parcel: Parcel, flags: Int) = with(manga) { parcel.writeLong(id) parcel.writeString(title) - parcel.writeString(altTitle) + parcel.writeStringSet(altTitles) parcel.writeString(url) parcel.writeString(publicUrl) parcel.writeFloat(rating) @@ -30,7 +32,7 @@ data class ParcelableManga( parcel.writeString(description.takeIf { withDescription }) parcel.writeParcelable(ParcelableMangaTags(tags), flags) parcel.writeSerializable(state) - parcel.writeString(author) + parcel.writeStringSet(authors) parcel.writeString(source.name) } @@ -38,7 +40,7 @@ data class ParcelableManga( Manga( id = parcel.readLong(), title = requireNotNull(parcel.readString()), - altTitle = parcel.readString(), + altTitles = parcel.readStringSet(), url = requireNotNull(parcel.readString()), publicUrl = requireNotNull(parcel.readString()), rating = parcel.readFloat(), @@ -48,7 +50,7 @@ data class ParcelableManga( description = parcel.readString(), tags = requireNotNull(parcel.readParcelableCompat()).tags, state = parcel.readSerializableCompat(), - author = parcel.readString(), + authors = parcel.readStringSet(), chapters = null, source = MangaSource(parcel.readString()), ), diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/DummyParser.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/DummyParser.kt index 74371571c..fd21f811a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/DummyParser.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/DummyParser.kt @@ -2,22 +2,22 @@ package org.koitharu.kotatsu.core.parser import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException import org.koitharu.kotatsu.parsers.MangaLoaderContext -import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.core.AbstractMangaParser import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.parsers.model.MangaListFilter -import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery +import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities import java.util.EnumSet /** * This parser is just for parser development, it should not be used in releases */ -class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaParserSource.DUMMY) { +class DummyParser(context: MangaLoaderContext) : AbstractMangaParser(context, MangaParserSource.DUMMY) { override val configKeyDomain: ConfigKey.Domain get() = ConfigKey.Domain("localhost") @@ -25,14 +25,14 @@ class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaParse override val availableSortOrders: Set get() = EnumSet.allOf(SortOrder::class.java) - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities() - - override suspend fun getFilterOptions(): MangaListFilterOptions = stub(null) + override val searchQueryCapabilities: MangaSearchQueryCapabilities + get() = MangaSearchQueryCapabilities() override suspend fun getDetails(manga: Manga): Manga = stub(manga) - override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List = stub(null) + override suspend fun getFilterOptions(): MangaListFilterOptions = stub(null) + + override suspend fun getList(query: MangaSearchQuery): List = stub(null) override suspend fun getPages(chapter: MangaChapter): List = stub(null) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt index 3a892c297..ee848731f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Bundle.kt @@ -11,6 +11,7 @@ import androidx.core.content.IntentCompat import androidx.core.os.BundleCompat import androidx.core.os.ParcelCompat import androidx.lifecycle.SavedStateHandle +import org.koitharu.kotatsu.parsers.util.toArraySet import java.io.Serializable import java.util.EnumSet @@ -84,6 +85,14 @@ fun > Parcel.readEnumSet(cls: Class): Set? { return set } +fun Parcel.writeStringSet(set: Set?) { + writeStringArray(set?.toTypedArray().orEmpty()) +} + +fun Parcel.readStringSet(): Set { + return this.createStringArray()?.toArraySet().orEmpty() +} + fun SavedStateHandle.require(key: String): T { return checkNotNull(get(key)) { "Value $key not found in SavedStateHandle or has a wrong type" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt index 8797e48ff..7614750c5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerViewModel.kt @@ -31,6 +31,7 @@ class FavouritesContainerViewModel @Inject constructor( val onActionDone = MutableEventFlow() private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary() + .withErrorHandling() .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null) val categories = combine( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/MangaIndex.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/MangaIndex.kt index c43a1a0a5..c9a591c44 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/MangaIndex.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/MangaIndex.kt @@ -26,6 +26,7 @@ import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet +import org.koitharu.kotatsu.parsers.util.json.toStringSet import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.toTitleCase import java.io.File @@ -38,10 +39,12 @@ class MangaIndex(source: String?) { require(!manga.isLocal) { "Local manga information cannot be stored" } json.put(KEY_ID, manga.id) json.put(KEY_TITLE, manga.title) - json.put(KEY_TITLE_ALT, manga.altTitle) + json.put(KEY_TITLE_ALT, manga.altTitle) // for backward compatibility + json.put(KEY_ALT_TITLES, JSONArray(manga.altTitles)) json.put(KEY_URL, manga.url) json.put(KEY_PUBLIC_URL, manga.publicUrl) - json.put(KEY_AUTHOR, manga.author) + json.put(KEY_AUTHOR, manga.author) // for backward compatibility + json.put(KEY_AUTHORS, JSONArray(manga.authors)) json.put(KEY_COVER, manga.coverUrl) json.put(KEY_DESCRIPTION, manga.description) json.put(KEY_RATING, manga.rating) @@ -73,10 +76,12 @@ class MangaIndex(source: String?) { Manga( id = json.getLong(KEY_ID), title = json.getString(KEY_TITLE), - altTitle = json.getStringOrNull(KEY_TITLE_ALT), + altTitles = json.optJSONArray(KEY_ALT_TITLES)?.toStringSet() + ?: setOfNotNull(json.getStringOrNull(KEY_TITLE_ALT)), url = json.getString(KEY_URL), publicUrl = json.getStringOrNull(KEY_PUBLIC_URL).orEmpty(), - author = json.getStringOrNull(KEY_AUTHOR), + authors = json.optJSONArray(KEY_AUTHORS)?.toStringSet() + ?: setOfNotNull(json.getStringOrNull(KEY_AUTHOR)), largeCoverUrl = json.getStringOrNull(KEY_COVER_LARGE), source = source, rating = json.getFloatOrDefault(KEY_RATING, RATING_UNKNOWN), @@ -198,9 +203,11 @@ class MangaIndex(source: String?) { private const val KEY_ID = "id" private const val KEY_TITLE = "title" private const val KEY_TITLE_ALT = "title_alt" + private const val KEY_ALT_TITLES = "alt_titles" private const val KEY_URL = "url" private const val KEY_PUBLIC_URL = "public_url" private const val KEY_AUTHOR = "author" + private const val KEY_AUTHORS = "authors" private const val KEY_COVER = "cover" private const val KEY_DESCRIPTION = "description" private const val KEY_RATING = "rating" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt index 89b3d66f0..7fffabad4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt @@ -126,12 +126,12 @@ class LocalMangaParser(private val uri: Uri) { } else { null }, - altTitle = null, + altTitles = emptySet(), rating = -1f, contentRating = null, - tags = setOf(), + tags = emptySet(), state = null, - author = null, + authors = emptySet(), largeCoverUrl = null, description = null, ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5fc3b10fc..f396f9500 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ material = "1.13.0-alpha10" moshi = "1.15.2" okhttp = "4.12.0" okio = "3.10.2" -parsers = "1.6" +parsers = "ddb9b13df7" preference = "1.2.1" recyclerview = "1.4.0" room = "2.6.1"