Local manga parsing fixes

master
Koitharu 1 year ago
parent e39e5bf9c4
commit b3933848e9
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -12,13 +12,15 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.find import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getEnumValueOrNull
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
@ -34,120 +36,120 @@ class MangaIndex(source: String?) {
fun setMangaInfo(manga: Manga) { fun setMangaInfo(manga: Manga) {
require(!manga.isLocal) { "Local manga information cannot be stored" } require(!manga.isLocal) { "Local manga information cannot be stored" }
json.put("id", manga.id) json.put(KEY_ID, manga.id)
json.put("title", manga.title) json.put(KEY_TITLE, manga.title)
json.put("title_alt", manga.altTitle) json.put(KEY_TITLE_ALT, manga.altTitle)
json.put("url", manga.url) json.put(KEY_URL, manga.url)
json.put("public_url", manga.publicUrl) json.put(KEY_PUBLIC_URL, manga.publicUrl)
json.put("author", manga.author) json.put(KEY_AUTHOR, manga.author)
json.put("cover", manga.coverUrl) json.put(KEY_COVER, manga.coverUrl)
json.put("description", manga.description) json.put(KEY_DESCRIPTION, manga.description)
json.put("rating", manga.rating) json.put(KEY_RATING, manga.rating)
json.put("nsfw", manga.isNsfw) json.put(KEY_CONTENT_RATING, manga.contentRating)
json.put("state", manga.state?.name) json.put(KEY_NSFW, manga.isNsfw) // for backward compatibility
json.put("source", manga.source.name) json.put(KEY_STATE, manga.state?.name)
json.put("cover_large", manga.largeCoverUrl) json.put(KEY_SOURCE, manga.source.name)
json.put(KEY_COVER_LARGE, manga.largeCoverUrl)
json.put( json.put(
"tags", KEY_TAGS,
JSONArray().also { a -> JSONArray().also { a ->
for (tag in manga.tags) { for (tag in manga.tags) {
val jo = JSONObject() val jo = JSONObject()
jo.put("key", tag.key) jo.put(KEY_KEY, tag.key)
jo.put("title", tag.title) jo.put(KEY_TITLE, tag.title)
a.put(jo) a.put(jo)
} }
}, },
) )
if (!json.has("chapters")) { if (!json.has(KEY_CHAPTERS)) {
json.put("chapters", JSONObject()) json.put(KEY_CHAPTERS, JSONObject())
} }
json.put("app_id", BuildConfig.APPLICATION_ID) json.put(KEY_APP_ID, BuildConfig.APPLICATION_ID)
json.put("app_version", BuildConfig.VERSION_CODE) json.put(KEY_APP_VERSION, BuildConfig.VERSION_CODE)
} }
fun getMangaInfo(): Manga? = if (json.length() == 0) null else runCatching { fun getMangaInfo(): Manga? = if (json.length() == 0) null else runCatching {
val source = MangaSource(json.getString("source")) val source = MangaSource(json.getString(KEY_SOURCE))
Manga( Manga(
id = json.getLong("id"), id = json.getLong(KEY_ID),
title = json.getString("title"), title = json.getString(KEY_TITLE),
altTitle = json.getStringOrNull("title_alt"), altTitle = json.getStringOrNull(KEY_TITLE_ALT),
url = json.getString("url"), url = json.getString(KEY_URL),
publicUrl = json.getStringOrNull("public_url").orEmpty(), publicUrl = json.getStringOrNull(KEY_PUBLIC_URL).orEmpty(),
author = json.getStringOrNull("author"), author = json.getStringOrNull(KEY_AUTHOR),
largeCoverUrl = json.getStringOrNull("cover_large"), largeCoverUrl = json.getStringOrNull(KEY_COVER_LARGE),
source = source, source = source,
rating = json.getDouble("rating").toFloat(), rating = json.getFloatOrDefault(KEY_RATING, RATING_UNKNOWN),
isNsfw = json.getBooleanOrDefault("nsfw", false), contentRating = json.getEnumValueOrNull(KEY_CONTENT_RATING, ContentRating::class.java)
coverUrl = json.getString("cover"), ?: if (json.getBooleanOrDefault(KEY_NSFW, false)) ContentRating.ADULT else null,
state = json.getStringOrNull("state")?.let { stateString -> coverUrl = json.getStringOrNull(KEY_COVER),
MangaState.entries.find(stateString) state = json.getEnumValueOrNull(KEY_STATE, MangaState::class.java),
}, description = json.getStringOrNull(KEY_DESCRIPTION),
description = json.getStringOrNull("description"), tags = json.getJSONArray(KEY_TAGS).mapJSONToSet { x ->
tags = json.getJSONArray("tags").mapJSONToSet { x ->
MangaTag( MangaTag(
title = x.getString("title").toTitleCase(), title = x.getString(KEY_TITLE).toTitleCase(),
key = x.getString("key"), key = x.getString(KEY_KEY),
source = source, source = source,
) )
}, },
chapters = getChapters(json.getJSONObject("chapters"), source), chapters = getChapters(json.getJSONObject(KEY_CHAPTERS), source),
) )
}.getOrNull() }.getOrNull()
fun getCoverEntry(): String? = json.getStringOrNull("cover_entry") fun getCoverEntry(): String? = json.getStringOrNull(KEY_COVER_ENTRY)
fun addChapter(chapter: IndexedValue<MangaChapter>, filename: String?) { fun addChapter(chapter: IndexedValue<MangaChapter>, filename: String?) {
val chapters = json.getJSONObject("chapters") val chapters = json.getJSONObject(KEY_CHAPTERS)
if (!chapters.has(chapter.value.id.toString())) { if (!chapters.has(chapter.value.id.toString())) {
val jo = JSONObject() val jo = JSONObject()
jo.put("number", chapter.value.number) jo.put(KEY_NUMBER, chapter.value.number)
jo.put("volume", chapter.value.volume) jo.put(KEY_VOLUME, chapter.value.volume)
jo.put("url", chapter.value.url) jo.put(KEY_URL, chapter.value.url)
jo.put("name", chapter.value.name) jo.put(KEY_NAME, chapter.value.name)
jo.put("uploadDate", chapter.value.uploadDate) jo.put(KEY_UPLOAD_DATE, chapter.value.uploadDate)
jo.put("scanlator", chapter.value.scanlator) jo.put(KEY_SCANLATOR, chapter.value.scanlator)
jo.put("branch", chapter.value.branch) jo.put(KEY_BRANCH, chapter.value.branch)
jo.put("entries", "%08d_%03d\\d{3}".format(chapter.value.branch.hashCode(), chapter.index + 1)) jo.put(KEY_ENTRIES, "%08d_%03d\\d{3}".format(chapter.value.branch.hashCode(), chapter.index + 1))
jo.put("file", filename) jo.put(KEY_FILE, filename)
chapters.put(chapter.value.id.toString(), jo) chapters.put(chapter.value.id.toString(), jo)
} }
} }
fun removeChapter(id: Long): Boolean { fun removeChapter(id: Long): Boolean {
return json.has("chapters") && json.getJSONObject("chapters").remove(id.toString()) != null return json.has(KEY_CHAPTERS) && json.getJSONObject(KEY_CHAPTERS).remove(id.toString()) != null
} }
fun getChapterFileName(chapterId: Long): String? { fun getChapterFileName(chapterId: Long): String? {
return json.optJSONObject("chapters")?.optJSONObject(chapterId.toString())?.getStringOrNull("file") return json.optJSONObject(KEY_CHAPTERS)?.optJSONObject(chapterId.toString())?.getStringOrNull(KEY_FILE)
} }
fun setCoverEntry(name: String) { fun setCoverEntry(name: String) {
json.put("cover_entry", name) json.put(KEY_COVER_ENTRY, name)
} }
fun getChapterNamesPattern(chapter: MangaChapter) = Regex( fun getChapterNamesPattern(chapter: MangaChapter) = Regex(
json.getJSONObject("chapters") json.getJSONObject(KEY_CHAPTERS)
.getJSONObject(chapter.id.toString()) .getJSONObject(chapter.id.toString())
.getString("entries"), .getString(KEY_ENTRIES),
) )
fun sortChaptersByName() { fun sortChaptersByName() {
val jo = json.getJSONObject("chapters") val jo = json.getJSONObject(KEY_CHAPTERS)
val list = ArrayList<JSONObject>(jo.length()) val list = ArrayList<JSONObject>(jo.length())
jo.keys().forEach { id -> jo.keys().forEach { id ->
val item = jo.getJSONObject(id) val item = jo.getJSONObject(id)
item.put("id", id) item.put(KEY_ID, id)
list.add(item) list.add(item)
} }
val comparator = org.koitharu.kotatsu.core.util.AlphanumComparator() val comparator = org.koitharu.kotatsu.core.util.AlphanumComparator()
list.sortWith(compareBy(comparator) { it.getString("name") }) list.sortWith(compareBy(comparator) { it.getString(KEY_NAME) })
val newJo = JSONObject() val newJo = JSONObject()
list.forEachIndexed { i, obj -> list.forEachIndexed { i, obj ->
obj.put("number", i + 1) obj.put(KEY_NUMBER, i + 1)
val id = obj.remove("id") as String val id = obj.remove(KEY_ID) as String
newJo.put(id, obj) newJo.put(id, obj)
} }
json.put("chapters", newJo) json.put(KEY_CHAPTERS, newJo)
} }
fun clear() { fun clear() {
@ -171,13 +173,13 @@ class MangaIndex(source: String?) {
chapters.add( chapters.add(
MangaChapter( MangaChapter(
id = k.toLong(), id = k.toLong(),
name = v.getString("name"), name = v.getString(KEY_NAME),
url = v.getString("url"), url = v.getString(KEY_URL),
number = v.getFloatOrDefault("number", 0f), number = v.getFloatOrDefault(KEY_NUMBER, 0f),
volume = v.getIntOrDefault("volume", 0), volume = v.getIntOrDefault(KEY_VOLUME, 0),
uploadDate = v.getLongOrDefault("uploadDate", 0L), uploadDate = v.getLongOrDefault(KEY_UPLOAD_DATE, 0L),
scanlator = v.getStringOrNull("scanlator"), scanlator = v.getStringOrNull(KEY_SCANLATOR),
branch = v.getStringOrNull("branch"), branch = v.getStringOrNull(KEY_BRANCH),
source = source, source = source,
), ),
) )
@ -193,6 +195,35 @@ class MangaIndex(source: String?) {
companion object { companion object {
private const val KEY_ID = "id"
private const val KEY_TITLE = "title"
private const val KEY_TITLE_ALT = "title_alt"
private const val KEY_URL = "url"
private const val KEY_PUBLIC_URL = "public_url"
private const val KEY_AUTHOR = "author"
private const val KEY_COVER = "cover"
private const val KEY_DESCRIPTION = "description"
private const val KEY_RATING = "rating"
private const val KEY_CONTENT_RATING = "content_rating"
private const val KEY_NSFW = "nsfw"
private const val KEY_STATE = "state"
private const val KEY_SOURCE = "source"
private const val KEY_COVER_LARGE = "cover_large"
private const val KEY_TAGS = "tags"
private const val KEY_CHAPTERS = "chapters"
private const val KEY_NUMBER = "number"
private const val KEY_VOLUME = "volume"
private const val KEY_NAME = "name"
private const val KEY_UPLOAD_DATE = "uploadDate"
private const val KEY_SCANLATOR = "scanlator"
private const val KEY_BRANCH = "branch"
private const val KEY_ENTRIES = "entries"
private const val KEY_FILE = "file"
private const val KEY_COVER_ENTRY = "cover_entry"
private const val KEY_KEY = "key"
private const val KEY_APP_ID = "app_id"
private const val KEY_APP_VERSION = "app_version"
@Blocking @Blocking
@WorkerThread @WorkerThread
fun read(fileSystem: FileSystem, path: Path): MangaIndex? = runCatchingCancellable { fun read(fileSystem: FileSystem, path: Path): MangaIndex? = runCatchingCancellable {

@ -128,7 +128,7 @@ class LocalMangaParser(private val uri: Uri) {
}, },
altTitle = null, altTitle = null,
rating = -1f, rating = -1f,
isNsfw = false, contentRating = null,
tags = setOf(), tags = setOf(),
state = null, state = null,
author = null, author = null,
@ -171,7 +171,7 @@ class LocalMangaParser(private val uri: Uri) {
} }
private fun Uri.child(path: Path, resolve: Boolean): Uri { private fun Uri.child(path: Path, resolve: Boolean): Uri {
val file = toFile() val file = fileFromPath()
val builder = buildUpon() val builder = buildUpon()
val isZip = isZipUri() || file.isZipArchive val isZip = isZipUri() || file.isZipArchive
if (isZip) { if (isZip) {
@ -278,6 +278,8 @@ class LocalMangaParser(private val uri: Uri) {
this this
} }
private fun Uri.fileFromPath(): File = File(requireNotNull(path) { "Uri path is null: $this" })
@Blocking @Blocking
private fun Uri.resolveFsAndPath(): FsAndPath { private fun Uri.resolveFsAndPath(): FsAndPath {
val resolved = resolve() val resolved = resolve()

@ -31,7 +31,7 @@ material = "1.13.0-alpha09"
moshi = "1.15.2" moshi = "1.15.2"
okhttp = "4.12.0" okhttp = "4.12.0"
okio = "3.10.2" okio = "3.10.2"
parsers = "b0a1cc48a6" parsers = "51ed1b2db8"
preference = "1.2.1" preference = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
room = "2.6.1" room = "2.6.1"

Loading…
Cancel
Save