From c42f011a058c4352acd572d141df6caba13eeadd Mon Sep 17 00:00:00 2001 From: NerdNumber9 Date: Tue, 7 Mar 2017 22:02:16 -0500 Subject: [PATCH] Implement Perv Eden source. --- .../kanade/tachiyomi/source/SourceManager.kt | 8 +- .../tachiyomi/source/online/all/EHentai.kt | 29 +- .../source/online/all/EHentaiMetadata.kt | 2 +- .../tachiyomi/source/online/all/PervEden.kt | 282 ++++++++++++++++++ .../ui/library/LibraryCategoryAdapter.kt | 19 +- app/src/main/java/exh/EHSourceHelpers.kt | 12 +- app/src/main/java/exh/GalleryAdder.kt | 2 +- .../main/java/exh/metadata/MetadataHelper.kt | 36 ++- .../main/java/exh/metadata/MetdataCopier.kt | 77 ++++- .../exh/metadata/models/ExGalleryMetadata.kt | 12 +- .../models/PervEdenGalleryMetadata.kt | 32 ++ .../models/SearchableGalleryMetadata.kt | 19 ++ app/src/main/java/exh/search/SearchEngine.kt | 8 +- .../exh/ui/migration/MetadataFetchDialog.kt | 2 +- .../main/java/exh/ui/migration/UrlMigrator.kt | 16 +- app/src/main/java/exh/util/UriFilter.kt | 10 + app/src/main/java/exh/util/UriGroup.kt | 15 + 17 files changed, 505 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt create mode 100644 app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt create mode 100644 app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt create mode 100644 app/src/main/java/exh/util/UriFilter.kt create mode 100644 app/src/main/java/exh/util/UriGroup.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 814bbd903..84e9f4dbf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -13,16 +13,14 @@ import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.YamlHttpSource +import eu.kanade.tachiyomi.source.online.all.PervEden import eu.kanade.tachiyomi.source.online.english.* import eu.kanade.tachiyomi.source.online.german.WieManga import eu.kanade.tachiyomi.source.online.russian.Mangachan import eu.kanade.tachiyomi.source.online.russian.Mintmanga import eu.kanade.tachiyomi.source.online.russian.Readmanga import eu.kanade.tachiyomi.util.hasPermission -import exh.EH_METADATA_SOURCE_ID -import exh.EH_SOURCE_ID -import exh.EXH_METADATA_SOURCE_ID -import exh.EXH_SOURCE_ID +import exh.* import org.yaml.snakeyaml.Yaml import rx.functions.Action1 import timber.log.Timber @@ -99,6 +97,8 @@ open class SourceManager(private val context: Context) { exSrcs += EHentai(EXH_SOURCE_ID, true, context) exSrcs += EHentaiMetadata(EXH_METADATA_SOURCE_ID, true, context) } + exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, "en") + exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, "it") return exSrcs } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt index 9edf3d88e..edcee3422 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt @@ -20,6 +20,8 @@ import uy.kohesive.injekt.injectLazy import java.net.URLEncoder import java.util.* import exh.ui.login.LoginActivity +import exh.util.UriFilter +import exh.util.UriGroup import okhttp3.Request class EHentai(override val id: Long, @@ -158,11 +160,14 @@ class EHentai(override val id: Long, override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) { val metdata = ExGalleryMetadata() with(metdata) { - val manga = SManga.create() url = response.request().url().toString() exh = this@EHentai.exh title = select("#gn").text().nullIfBlank()?.trim() - altTitle = select("#gj").text().nullIfBlank()?.trim() + + altTitles.clear() + select("#gj").text().nullIfBlank()?.trim()?.let { newAltTitle -> + altTitles.add(newAltTitle) + } thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()?.trim() @@ -227,12 +232,13 @@ class EHentai(override val id: Long, } //Save metadata - metadataHelper.writeGallery(this) + metadataHelper.writeGallery(this, id) //Copy metadata to manga - copyTo(manga) - - manga + SManga.create().let { + copyTo(it) + it + } } } @@ -333,9 +339,6 @@ class EHentai(override val id: Long, GenreGroup(), AdvancedGroup() ) - private interface UriFilter { - fun addToUri(builder: Uri.Builder) - } class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter { override fun addToUri(builder: Uri.Builder) { @@ -386,14 +389,6 @@ class EHentai(override val id: Long, RatingOption() )) - open class UriGroup(name: String, state: List) : Filter.Group(name, state), UriFilter { - override fun addToUri(builder: Uri.Builder) { - state.forEach { - if(it is UriFilter) it.addToUri(builder) - } - } - } - override val name = if(exh) "ExHentai" else diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt index 628c937e6..d053dc9d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentaiMetadata.kt @@ -111,7 +111,7 @@ class EHentaiMetadata(override val id: Long, override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable { //Hack to convert the gallery into an online gallery when favoriting it or reading it - metadataHelper.fetchMetadata(manga.url, exh)?.copyTo(manga) + metadataHelper.fetchEhMetadata(manga.url, exh)?.copyTo(manga) manga }!! diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt new file mode 100644 index 000000000..c0448207b --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/PervEden.kt @@ -0,0 +1,282 @@ +package eu.kanade.tachiyomi.source.online.all + +import android.net.Uri +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.ChapterRecognition +import eu.kanade.tachiyomi.util.asJsoup +import exh.metadata.MetadataHelper +import exh.metadata.copyTo +import exh.metadata.models.PervEdenGalleryMetadata +import exh.metadata.models.Tag +import exh.util.UriFilter +import exh.util.UriGroup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import org.jsoup.nodes.TextNode +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.* + +class PervEden(override val id: Long, override val lang: String) : ParsedHttpSource() { + + override val supportsLatest = true + override val name = "Perv Eden" + override val baseUrl = "http://www.perveden.com" + + val metadataHelper by lazy { MetadataHelper() } + + override fun popularMangaSelector() = "#topManga > ul > li" + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + manga.thumbnail_url = "http:" + element.select(".hottestImage > img").attr("data-src") + + val titleElement = element.getElementsByClass("hottestInfo").first().child(0) + manga.url = titleElement.attr("href") + manga.title = titleElement.text() + + return manga + } + + override fun popularMangaNextPageSelector(): String? = null + + override fun searchMangaSelector() = "#mangaList > tbody > tr" + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + val titleElement = element.child(0).child(0) + manga.url = titleElement.attr("href") + manga.title = titleElement.text().trim() + return manga + } + + override fun searchMangaNextPageSelector() = ".next" + + override fun popularMangaRequest(page: Int): Request { + val urlLang = if(lang == "en") + "eng" + else "it" + return GET("$baseUrl/$urlLang/") + } + + override fun latestUpdatesSelector() = ".newsManga" + + override fun latestUpdatesFromElement(element: Element): SManga { + val manga = SManga.create() + val header = element.getElementsByClass("manga_tooltop_header").first() + val titleElement = header.child(0) + manga.url = titleElement.attr("href") + manga.title = titleElement.text().trim() + manga.thumbnail_url = "http:" + titleElement.getElementsByClass("mangaImage").first().attr("tmpsrc") + return manga + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(latestUpdatesSelector()).map { element -> + latestUpdatesFromElement(element) + } + + return MangasPage(mangas, mangas.isNotEmpty()) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val uri = Uri.parse("$baseUrl/$lang/$lang-directory/").buildUpon() + uri.appendQueryParameter("page", page.toString()) + filters.forEach { + if(it is UriFilter) it.addToUri(uri) + } + return GET(uri.toString()) + } + + override fun latestUpdatesNextPageSelector(): String? { + throw NotImplementedError("Unused method called!") + } + + override fun mangaDetailsParse(document: Document): SManga { + val metadata = PervEdenGalleryMetadata() + with(metadata) { + url = document.location() + + lang = this@PervEden.lang + + title = document.getElementsByClass("manga-title").first()?.text() + + thumbnailUrl = "http:" + document.getElementsByClass("mangaImage2").first()?.child(0)?.attr("src") + + val rightBoxElement = document.select(".rightBox:not(.info)").first() + + tags.clear() + var inStatus: String? = null + rightBoxElement.childNodes().forEach { + if(it is Element && it.tagName().toLowerCase() == "h4") { + inStatus = it.text().trim() + } else { + when(inStatus) { + "Alternative name(s)" -> { + if(it is TextNode) { + val text = it.text().trim() + if(!text.isBlank()) + altTitles.add(text) + } + } + "Artist" -> { + if(it is Element && it.tagName() == "a") { + artist = it.text() + tags.getOrPut("artist", { + ArrayList() + }).add(Tag(it.text().toLowerCase(), false)) + } + } + "Genres" -> { + if(it is Element && it.tagName() == "a") + tags.getOrPut("genre", { + ArrayList() + }).add(Tag(it.text().toLowerCase(), false)) + } + "Type" -> { + if(it is TextNode) { + val text = it.text().trim() + if(!text.isBlank()) + type = text + } + } + "Status" -> { + if(it is TextNode) { + val text = it.text().trim() + if(!text.isBlank()) + status = text + } + } + } + } + } + + rating = document.getElementById("rating-score")?.attr("value")?.toFloat() + + //Save metadata + Timber.d("LNG: " + metadata.lang) + metadataHelper.writeGallery(this, id) + + return SManga.create().apply { + copyTo(this) + } + } + } + + override fun latestUpdatesRequest(page: Int): Request { + val num = if(lang == "en") "0" + else if(lang == "it") "1" + else throw NotImplementedError("Unimplemented language!") + + return GET("$baseUrl/ajax/news/$page/$num/0/") + } + + override fun chapterListSelector() = "#leftContent > table > tbody > tr" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + val linkElement = element.getElementsByClass("chapterLink").first() + + setUrlWithoutDomain(linkElement.attr("href")) + name = "Chapter " + linkElement.getElementsByTag("b").text() + + ChapterRecognition.parseChapterNumber( + this, + SManga.create().apply { + title = "" + }) + + try { + date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim()).time + } catch(ignored: Exception) {} + } + + override fun pageListParse(document: Document) + = document.getElementById("pageSelect").getElementsByTag("option").map { + Page(it.attr("data-page").toInt() - 1, baseUrl + it.attr("value")) + } + + override fun imageUrlParse(document: Document) + = "http:" + document.getElementById("mainImg").attr("src")!! + + override fun getFilterList() = FilterList ( + AuthorFilter(), + ArtistFilter(), + TypeFilterGroup(), + ReleaseYearGroup(), + StatusFilterGroup() + ) + + class StatusFilterGroup : UriGroup("Status", listOf( + StatusFilter("Ongoing", 1), + StatusFilter("Completed", 2), + StatusFilter("Suspended", 0) + )) + + class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter { + override fun addToUri(builder: Uri.Builder) { + if(state) + builder.appendQueryParameter("status", id.toString()) + } + } + + //Explicit type arg for listOf() to workaround this: KT-16570 + class ReleaseYearGroup : UriGroup>("Release Year", listOf>( + ReleaseYearRangeFilter(), + ReleaseYearYearFilter() + )) + + class ReleaseYearRangeFilter : Filter.Select("Range", arrayOf( + "on", + "after", + "before" + )), UriFilter { + override fun addToUri(builder: Uri.Builder) { + builder.appendQueryParameter("releasedType", state.toString()) + } + } + + class ReleaseYearYearFilter : Filter.Text("Year"), UriFilter { + override fun addToUri(builder: Uri.Builder) { + builder.appendQueryParameter("released", state) + } + } + + class AuthorFilter : Filter.Text("Author"), UriFilter { + override fun addToUri(builder: Uri.Builder) { + builder.appendQueryParameter("author", state) + } + } + + class ArtistFilter : Filter.Text("Artist"), UriFilter { + override fun addToUri(builder: Uri.Builder) { + builder.appendQueryParameter("artist", state) + } + } + + class TypeFilterGroup : UriGroup("Type", listOf( + TypeFilter("Japanese Manga", 0), + TypeFilter("Korean Manhwa", 1), + TypeFilter("Chinese Manhua", 2), + TypeFilter("Comic", 3), + TypeFilter("Doujinshi", 4) + )) + + class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter { + override fun addToUri(builder: Uri.Builder) { + if(state) + builder.appendQueryParameter("type", id.toString()) + } + } + + companion object { + val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply { + timeZone = TimeZone.getTimeZone("GMT") + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index c52a5b829..5aad6f4cf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -101,21 +101,10 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) : author != null && author!!.toLowerCase().contains(query) } else { //Use gallery search engine for EH manga - val source = sourceManager.get(manga.source) - source?.let { - val exh: Boolean - if(source is EHentai) - exh = source.exh - else if(source is EHentaiMetadata) - exh = source.exh - else - return@with false - - val metadata = metadataHelper.fetchMetadata(manga.url, exh) - metadata?.let { - searchEngine.matches(metadata, searchEngine.parseQuery(query)) - } ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery - } ?: false + val metadata = metadataHelper.fetchMetadata(manga.url, manga.source) + metadata?.let { + searchEngine.matches(it, searchEngine.parseQuery(query)) + } ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery } } diff --git a/app/src/main/java/exh/EHSourceHelpers.kt b/app/src/main/java/exh/EHSourceHelpers.kt index 4b9f1f3fb..34760c536 100644 --- a/app/src/main/java/exh/EHSourceHelpers.kt +++ b/app/src/main/java/exh/EHSourceHelpers.kt @@ -10,8 +10,16 @@ val EXH_SOURCE_ID = LEWD_SOURCE_SERIES + 2 val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3 val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4 -fun isLewdSource(source: Long) = source >= 6900 - && source <= 6999 +val PERV_EDEN_EN_SOURCE_ID = LEWD_SOURCE_SERIES + 5 +val PERV_EDEN_IT_SOURCE_ID = LEWD_SOURCE_SERIES + 6 + +fun isLewdSource(source: Long) = source in 6900..6999 + +fun isEhSource(source: Long) = source == EH_SOURCE_ID + || source == EH_METADATA_SOURCE_ID fun isExSource(source: Long) = source == EXH_SOURCE_ID || source == EXH_METADATA_SOURCE_ID + +fun isPervEdenSource(source: Long) = source == PERV_EDEN_IT_SOURCE_ID +|| source == PERV_EDEN_EN_SOURCE_ID diff --git a/app/src/main/java/exh/GalleryAdder.kt b/app/src/main/java/exh/GalleryAdder.kt index 2e428a50e..b0e7a9ff9 100644 --- a/app/src/main/java/exh/GalleryAdder.kt +++ b/app/src/main/java/exh/GalleryAdder.kt @@ -44,7 +44,7 @@ class GalleryAdder { manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first()) //Apply metadata - metadataHelper.fetchMetadata(url, isExSource(source))?.copyTo(manga) + metadataHelper.fetchEhMetadata(url, isExSource(source))?.copyTo(manga) if(fav) manga.favorite = true diff --git a/app/src/main/java/exh/metadata/MetadataHelper.kt b/app/src/main/java/exh/metadata/MetadataHelper.kt index b81b7249f..5c147529a 100644 --- a/app/src/main/java/exh/metadata/MetadataHelper.kt +++ b/app/src/main/java/exh/metadata/MetadataHelper.kt @@ -1,20 +1,46 @@ package exh.metadata +import exh.* import exh.metadata.models.ExGalleryMetadata +import exh.metadata.models.PervEdenGalleryMetadata +import exh.metadata.models.SearchableGalleryMetadata import io.paperdb.Paper class MetadataHelper { - fun writeGallery(galleryMetadata: ExGalleryMetadata) - = exGalleryBook().write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!! + fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long) + = (if(isExSource(source) || isEhSource(source)) exGalleryBook() + else if(isPervEdenSource(source)) pervEdenGalleryBook() + else null)?.write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!! - fun fetchMetadata(url: String, exh: Boolean): ExGalleryMetadata? + fun fetchEhMetadata(url: String, exh: Boolean): ExGalleryMetadata? = ExGalleryMetadata().let { it.url = url it.exh = exh return exGalleryBook().read(it.galleryUniqueIdentifier()) } + fun fetchPervEdenMetadata(url: String, source: Long): PervEdenGalleryMetadata? + = PervEdenGalleryMetadata().let { + it.url = url + if(source == PERV_EDEN_EN_SOURCE_ID) + it.lang = "en" + else if(source == PERV_EDEN_IT_SOURCE_ID) + it.lang = "it" + else throw IllegalArgumentException("Invalid source id!") + return pervEdenGalleryBook().read(it.galleryUniqueIdentifier()) + } + + fun fetchMetadata(url: String, source: Long): SearchableGalleryMetadata? { + if(isExSource(source) || isEhSource(source)) { + return fetchEhMetadata(url, isExSource(source)) + } else if(isPervEdenSource(source)) { + return fetchPervEdenMetadata(url, source) + } else { + return null + } + } + fun getAllGalleries() = exGalleryBook().allKeys.map { exGalleryBook().read(it) } @@ -26,5 +52,9 @@ class MetadataHelper { return exGalleryBook().exist(it.galleryUniqueIdentifier()) } + //TODO Problem, our new metadata structures are incompatible. + //TODO We will probably just delete the old metadata structures fun exGalleryBook() = Paper.book("gallery-ex")!! + + fun pervEdenGalleryBook() = Paper.book("gallery-perveden")!! } \ No newline at end of file diff --git a/app/src/main/java/exh/metadata/MetdataCopier.kt b/app/src/main/java/exh/metadata/MetdataCopier.kt index bc079d46b..79cccaec3 100644 --- a/app/src/main/java/exh/metadata/MetdataCopier.kt +++ b/app/src/main/java/exh/metadata/MetdataCopier.kt @@ -3,7 +3,10 @@ package exh.metadata import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.all.PervEden import exh.metadata.models.ExGalleryMetadata +import exh.metadata.models.PervEdenGalleryMetadata +import exh.metadata.models.SearchableGalleryMetadata import exh.metadata.models.Tag import exh.plusAssign import uy.kohesive.injekt.injectLazy @@ -40,7 +43,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) { //No title bug? val titleObj = if(prefs.useJapaneseTitle().getOrDefault()) - altTitle ?: title + altTitles.firstOrNull() ?: title else title titleObj?.let { manga.title = it } @@ -70,7 +73,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) { //Build a nice looking description out of what we know val titleDesc = StringBuilder() title?.let { titleDesc += "Title: $it\n" } - altTitle?.let { titleDesc += "Japanese Title: $it\n" } + altTitles.firstOrNull()?.let { titleDesc += "Alternate Title: $it\n" } val detailsDesc = StringBuilder() uploader?.let { detailsDesc += "Uploader: $it\n" } @@ -90,16 +93,66 @@ fun ExGalleryMetadata.copyTo(manga: SManga) { detailsDesc += "\n" } - val tagsDesc = StringBuilder("Tags:\n") - //BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags' - tags.entries.forEach { namespace, tags -> - if(tags.isNotEmpty()) { - val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" }) - tagsDesc += "▪ $namespace: $joinedTags\n" - } - } - manga.description = listOf(titleDesc, detailsDesc, tagsDesc) - .filter { it.isNotBlank() } + val tagsDesc = buildTagsDescription(this) + + manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) + .filter(String::isNotBlank) .joinToString(separator = "\n") } + +fun PervEdenGalleryMetadata.copyTo(manga: SManga) { + url?.let { manga.url = it } + thumbnailUrl?.let { manga.thumbnail_url = it } + + val titleDesc = StringBuilder() + title?.let { + manga.title = it + titleDesc += "Title: $it\n" + } + if(altTitles.isNotEmpty()) + titleDesc += "Alternate Titles: \n" + altTitles.map { + "▪ $it" + }.joinToString(separator = "\n", postfix = "\n") + + val detailsDesc = StringBuilder() + artist?.let { + manga.artist = it + detailsDesc += "Artist: $it\n" + } + + type?.let { + manga.genre = it + detailsDesc += "Type: $it\n" + } + + status?.let { + manga.status = when(it) { + "Ongoing" -> SManga.ONGOING + "Completed", "Suspended" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + detailsDesc += "Status: $it\n" + } + + rating?.let { + detailsDesc += "Rating: %.2\n".format(it) + } + + val tagsDesc = buildTagsDescription(this) + + manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) + .filter(String::isNotBlank) + .joinToString(separator = "\n") +} + +private fun buildTagsDescription(metadata: SearchableGalleryMetadata) + = StringBuilder("Tags:\n").apply { + //BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags' + metadata.tags.entries.forEach { namespace, tags -> + if (tags.isNotEmpty()) { + val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" }) + this += "▪ $namespace: $joinedTags\n" + } + } + } diff --git a/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt b/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt index ac1de4363..1b076fae8 100644 --- a/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt +++ b/app/src/main/java/exh/metadata/models/ExGalleryMetadata.kt @@ -7,19 +7,15 @@ import java.util.* * Gallery metadata storage model */ -class ExGalleryMetadata { +class ExGalleryMetadata : SearchableGalleryMetadata() { var url: String? = null var exh: Boolean? = null - var title: String? = null - var altTitle: String? = null - var thumbnailUrl: String? = null var genre: String? = null - var uploader: String? = null var datePosted: Long? = null var parent: String? = null var visible: String? = null //Not a boolean @@ -31,8 +27,6 @@ class ExGalleryMetadata { var ratingCount: Int? = null var averageRating: Double? = null - //Being specific about which classes are used in generics to make deserialization easier - var tags: HashMap> = HashMap() private fun splitGalleryUrl() = url?.let { @@ -44,8 +38,10 @@ class ExGalleryMetadata { fun galleryToken() = splitGalleryUrl()?.last() - fun galleryUniqueIdentifier() = exh?.let { exh -> + override fun galleryUniqueIdentifier() = exh?.let { exh -> url?.let { + //Fuck, this should be EXH and EH but it's too late to change it now... + //TODO Change this during migration "${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}" } } diff --git a/app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt b/app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt new file mode 100644 index 000000000..8a90e9ecf --- /dev/null +++ b/app/src/main/java/exh/metadata/models/PervEdenGalleryMetadata.kt @@ -0,0 +1,32 @@ +package exh.metadata.models + +import android.net.Uri +import timber.log.Timber + +//TODO Add artificial artist tag +class PervEdenGalleryMetadata : SearchableGalleryMetadata() { + var url: String? = null + var thumbnailUrl: String? = null + + var artist: String? = null + + var type: String? = null + + var rating: Float? = null + + var status: String? = null + + var lang: String? = null + + private fun splitGalleryUrl() + = url?.let { + Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank) + } + + override fun galleryUniqueIdentifier() = splitGalleryUrl()?.let { + Timber.d( + "PERVEDEN-${lang?.toUpperCase()}-${it.last()}" + ) + "PERVEDEN-${lang?.toUpperCase()}-${it.last()}" + } +} diff --git a/app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt b/app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt new file mode 100644 index 000000000..5b8df99db --- /dev/null +++ b/app/src/main/java/exh/metadata/models/SearchableGalleryMetadata.kt @@ -0,0 +1,19 @@ +package exh.metadata.models + +import java.util.ArrayList +import java.util.HashMap + +/** + * A gallery that can be searched using the EH search engine + */ +abstract class SearchableGalleryMetadata { + var uploader: String? = null + + var title: String? = null + val altTitles: MutableList = mutableListOf() + + //Being specific about which classes are used in generics to make deserialization easier + val tags: HashMap> = HashMap() + + abstract fun galleryUniqueIdentifier(): String? +} \ No newline at end of file diff --git a/app/src/main/java/exh/search/SearchEngine.kt b/app/src/main/java/exh/search/SearchEngine.kt index 56eb16525..cd5054d15 100644 --- a/app/src/main/java/exh/search/SearchEngine.kt +++ b/app/src/main/java/exh/search/SearchEngine.kt @@ -1,13 +1,13 @@ package exh.search -import exh.metadata.models.ExGalleryMetadata +import exh.metadata.models.SearchableGalleryMetadata import exh.metadata.models.Tag class SearchEngine { private val queryCache = mutableMapOf>() - fun matches(metadata: ExGalleryMetadata, query: List): Boolean { + fun matches(metadata: SearchableGalleryMetadata, query: List): Boolean { fun matchTagList(tags: Sequence, component: Text): Boolean { @@ -29,13 +29,13 @@ class SearchEngine { } val cachedLowercaseTitle = metadata.title?.toLowerCase() - val cachedLowercaseAltTitle = metadata.altTitle?.toLowerCase() + val cachedLowercaseAltTitles = metadata.altTitles.map(String::toLowerCase) for(component in query) { if(component is Text) { //Match title if (component.asRegex().test(cachedLowercaseTitle) - || component.asRegex().test(cachedLowercaseAltTitle)) { + || cachedLowercaseAltTitles.find { component.asRegex().test(it) } != null) { continue } //Match tags diff --git a/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt b/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt index 8db9a3221..41d0a0dfd 100644 --- a/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt +++ b/app/src/main/java/exh/ui/migration/MetadataFetchDialog.kt @@ -63,7 +63,7 @@ class MetadataFetchDialog { source?.let { it as EHentai manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first()) - metadataHelper.fetchMetadata(manga.url, it.exh)?.copyTo(manga) + metadataHelper.fetchEhMetadata(manga.url, it.exh)?.copyTo(manga) } } catch(t: Throwable) { Timber.e(t, "Could not migrate manga!") diff --git a/app/src/main/java/exh/ui/migration/UrlMigrator.kt b/app/src/main/java/exh/ui/migration/UrlMigrator.kt index 8665ed84e..2169c555d 100644 --- a/app/src/main/java/exh/ui/migration/UrlMigrator.kt +++ b/app/src/main/java/exh/ui/migration/UrlMigrator.kt @@ -39,33 +39,33 @@ class UrlMigrator { //Sort possible dups so we can use binary search on it possibleDups.sortBy { it.url } - badMangas.forEach { + badMangas.forEach { manga -> //Build fixed URL - val urlWithSlash = "/" + it.url + val urlWithSlash = "/" + manga.url //Fix metadata if required - val metadata = metadataHelper.fetchMetadata(it.url, isExSource(it.source)) + val metadata = metadataHelper.fetchEhMetadata(manga.url, isExSource(manga.source)) metadata?.url?.let { if(it.startsWith("g/")) { //Check if metadata URL has no slash metadata.url = urlWithSlash //Fix it - metadataHelper.writeGallery(metadata) //Write new metadata to disk + metadataHelper.writeGallery(metadata, manga.source) //Write new metadata to disk } } //If we have a dup (with the fixed url), use the dup instead val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url }) if(possibleDup >= 0) { //Make sure it is favorited if we are - if(it.favorite) { + if(manga.favorite) { val dup = possibleDups[possibleDup] dup.favorite = true db.insertManga(dup).executeAsBlocking() //Update DB with changes } //Delete ourself (but the dup is still there) - db.deleteManga(it).executeAsBlocking() + db.deleteManga(manga).executeAsBlocking() return@forEach } //No dup, correct URL and reinsert ourselves - it.url = urlWithSlash - db.insertManga(it).executeAsBlocking() + manga.url = urlWithSlash + db.insertManga(manga).executeAsBlocking() } } } diff --git a/app/src/main/java/exh/util/UriFilter.kt b/app/src/main/java/exh/util/UriFilter.kt new file mode 100644 index 000000000..655b03996 --- /dev/null +++ b/app/src/main/java/exh/util/UriFilter.kt @@ -0,0 +1,10 @@ +package exh.util + +import android.net.Uri + +/** + * Uri filter + */ +interface UriFilter { + fun addToUri(builder: Uri.Builder) +} \ No newline at end of file diff --git a/app/src/main/java/exh/util/UriGroup.kt b/app/src/main/java/exh/util/UriGroup.kt new file mode 100644 index 000000000..cb9225c40 --- /dev/null +++ b/app/src/main/java/exh/util/UriGroup.kt @@ -0,0 +1,15 @@ +package exh.util + +import android.net.Uri +import eu.kanade.tachiyomi.source.model.Filter + +/** + * UriGroup + */ +open class UriGroup(name: String, state: List) : Filter.Group(name, state), UriFilter { + override fun addToUri(builder: Uri.Builder) { + state.forEach { + if(it is UriFilter) it.addToUri(builder) + } + } +} \ No newline at end of file