Update parsers and adjust database

This commit is contained in:
Koitharu
2025-02-28 15:35:56 +02:00
parent 8724f5b30c
commit 7a01fdd04c
13 changed files with 81 additions and 35 deletions

View File

@@ -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"),
)

View File

@@ -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)
},
)

View File

@@ -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<Migration> = arrayOf(
Migration22To23(),
Migration23To24(),
Migration24To23(),
Migration24To25(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room

View File

@@ -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<TagEntity>.toMangaTags() = mapToSet(TagEntity::toMangaTag)
fun Collection<TagEntity>.toMangaTagsList() = map(TagEntity::toMangaTag)
fun MangaEntity.toManga(tags: Set<MangaTag>, chapters: List<ChapterEntity>?) = Manga( // TODO
fun MangaEntity.toManga(tags: Set<MangaTag>, chapters: List<ChapterEntity>?) = 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()

View File

@@ -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,
)

View File

@@ -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)")
}
}

View File

@@ -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<ParcelableMangaTags>()).tags,
state = parcel.readSerializableCompat(),
author = parcel.readString(),
authors = parcel.readStringSet(),
chapters = null,
source = MangaSource(parcel.readString()),
),

View File

@@ -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<SortOrder>
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<Manga> = stub(null)
override suspend fun getFilterOptions(): MangaListFilterOptions = stub(null)
override suspend fun getList(query: MangaSearchQuery): List<Manga> = stub(null)
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = stub(null)

View File

@@ -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 <E : Enum<E>> Parcel.readEnumSet(cls: Class<E>): Set<E>? {
return set
}
fun Parcel.writeStringSet(set: Set<String>?) {
writeStringArray(set?.toTypedArray().orEmpty())
}
fun Parcel.readStringSet(): Set<String> {
return this.createStringArray()?.toArraySet().orEmpty()
}
fun <T> SavedStateHandle.require(key: String): T {
return checkNotNull(get(key)) {
"Value $key not found in SavedStateHandle or has a wrong type"

View File

@@ -31,6 +31,7 @@ class FavouritesContainerViewModel @Inject constructor(
val onActionDone = MutableEventFlow<ReversibleAction>()
private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
val categories = combine(

View File

@@ -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"

View File

@@ -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,
)

View File

@@ -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"