Implement Perv Eden source.

This commit is contained in:
NerdNumber9 2017-03-07 22:02:16 -05:00
parent 957c50088d
commit c42f011a05
17 changed files with 505 additions and 76 deletions

View File

@ -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.all.EHentaiMetadata
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.YamlHttpSource 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.english.*
import eu.kanade.tachiyomi.source.online.german.WieManga import eu.kanade.tachiyomi.source.online.german.WieManga
import eu.kanade.tachiyomi.source.online.russian.Mangachan import eu.kanade.tachiyomi.source.online.russian.Mangachan
import eu.kanade.tachiyomi.source.online.russian.Mintmanga import eu.kanade.tachiyomi.source.online.russian.Mintmanga
import eu.kanade.tachiyomi.source.online.russian.Readmanga import eu.kanade.tachiyomi.source.online.russian.Readmanga
import eu.kanade.tachiyomi.util.hasPermission import eu.kanade.tachiyomi.util.hasPermission
import exh.EH_METADATA_SOURCE_ID import exh.*
import exh.EH_SOURCE_ID
import exh.EXH_METADATA_SOURCE_ID
import exh.EXH_SOURCE_ID
import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.Yaml
import rx.functions.Action1 import rx.functions.Action1
import timber.log.Timber import timber.log.Timber
@ -99,6 +97,8 @@ open class SourceManager(private val context: Context) {
exSrcs += EHentai(EXH_SOURCE_ID, true, context) exSrcs += EHentai(EXH_SOURCE_ID, true, context)
exSrcs += EHentaiMetadata(EXH_METADATA_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 return exSrcs
} }

View File

@ -20,6 +20,8 @@ import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder import java.net.URLEncoder
import java.util.* import java.util.*
import exh.ui.login.LoginActivity import exh.ui.login.LoginActivity
import exh.util.UriFilter
import exh.util.UriGroup
import okhttp3.Request import okhttp3.Request
class EHentai(override val id: Long, class EHentai(override val id: Long,
@ -158,11 +160,14 @@ class EHentai(override val id: Long,
override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) { override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) {
val metdata = ExGalleryMetadata() val metdata = ExGalleryMetadata()
with(metdata) { with(metdata) {
val manga = SManga.create()
url = response.request().url().toString() url = response.request().url().toString()
exh = this@EHentai.exh exh = this@EHentai.exh
title = select("#gn").text().nullIfBlank()?.trim() 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() thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()?.trim()
@ -227,12 +232,13 @@ class EHentai(override val id: Long,
} }
//Save metadata //Save metadata
metadataHelper.writeGallery(this) metadataHelper.writeGallery(this, id)
//Copy metadata to manga //Copy metadata to manga
copyTo(manga) SManga.create().let {
copyTo(it)
manga it
}
} }
} }
@ -333,9 +339,6 @@ class EHentai(override val id: Long,
GenreGroup(), GenreGroup(),
AdvancedGroup() AdvancedGroup()
) )
private interface UriFilter {
fun addToUri(builder: Uri.Builder)
}
class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter { class GenreOption(name: String, val genreId: String): Filter.CheckBox(name, false), UriFilter {
override fun addToUri(builder: Uri.Builder) { override fun addToUri(builder: Uri.Builder) {
@ -386,14 +389,6 @@ class EHentai(override val id: Long,
RatingOption() RatingOption()
)) ))
open class UriGroup<V>(name: String, state: List<V>) : Filter.Group<V>(name, state), UriFilter {
override fun addToUri(builder: Uri.Builder) {
state.forEach {
if(it is UriFilter) it.addToUri(builder)
}
}
}
override val name = if(exh) override val name = if(exh)
"ExHentai" "ExHentai"
else else

View File

@ -111,7 +111,7 @@ class EHentaiMetadata(override val id: Long,
override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable { override fun fetchMangaDetails(manga: SManga) = Observable.fromCallable {
//Hack to convert the gallery into an online gallery when favoriting it or reading it //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 manga
}!! }!!

View File

@ -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<StatusFilter>("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<Filter<*>>("Release Year", listOf<Filter<*>>(
ReleaseYearRangeFilter(),
ReleaseYearYearFilter()
))
class ReleaseYearRangeFilter : Filter.Select<String>("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<TypeFilter>("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")
}
}
}

View File

@ -101,21 +101,10 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
author != null && author!!.toLowerCase().contains(query) author != null && author!!.toLowerCase().contains(query)
} else { } else {
//Use gallery search engine for EH manga //Use gallery search engine for EH manga
val source = sourceManager.get(manga.source) val metadata = metadataHelper.fetchMetadata(manga.url, manga.source)
source?.let { metadata?.let {
val exh: Boolean searchEngine.matches(it, searchEngine.parseQuery(query))
if(source is EHentai) } ?: title.contains(query, ignoreCase = true) //Use regular searching when the metadata is not set up for this gallery
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
} }
} }

View File

@ -10,8 +10,16 @@ val EXH_SOURCE_ID = LEWD_SOURCE_SERIES + 2
val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3 val EH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 3
val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4 val EXH_METADATA_SOURCE_ID = LEWD_SOURCE_SERIES + 4
fun isLewdSource(source: Long) = source >= 6900 val PERV_EDEN_EN_SOURCE_ID = LEWD_SOURCE_SERIES + 5
&& source <= 6999 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 fun isExSource(source: Long) = source == EXH_SOURCE_ID
|| source == EXH_METADATA_SOURCE_ID || source == EXH_METADATA_SOURCE_ID
fun isPervEdenSource(source: Long) = source == PERV_EDEN_IT_SOURCE_ID
|| source == PERV_EDEN_EN_SOURCE_ID

View File

@ -44,7 +44,7 @@ class GalleryAdder {
manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first()) manga.copyFrom(sourceObj.fetchMangaDetails(manga).toBlocking().first())
//Apply metadata //Apply metadata
metadataHelper.fetchMetadata(url, isExSource(source))?.copyTo(manga) metadataHelper.fetchEhMetadata(url, isExSource(source))?.copyTo(manga)
if(fav) manga.favorite = true if(fav) manga.favorite = true

View File

@ -1,20 +1,46 @@
package exh.metadata package exh.metadata
import exh.*
import exh.metadata.models.ExGalleryMetadata import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.PervEdenGalleryMetadata
import exh.metadata.models.SearchableGalleryMetadata
import io.paperdb.Paper import io.paperdb.Paper
class MetadataHelper { class MetadataHelper {
fun writeGallery(galleryMetadata: ExGalleryMetadata) fun writeGallery(galleryMetadata: SearchableGalleryMetadata, source: Long)
= exGalleryBook().write(galleryMetadata.galleryUniqueIdentifier(), galleryMetadata)!! = (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 { = ExGalleryMetadata().let {
it.url = url it.url = url
it.exh = exh it.exh = exh
return exGalleryBook().read<ExGalleryMetadata>(it.galleryUniqueIdentifier()) return exGalleryBook().read<ExGalleryMetadata>(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<PervEdenGalleryMetadata>(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 { fun getAllGalleries() = exGalleryBook().allKeys.map {
exGalleryBook().read<ExGalleryMetadata>(it) exGalleryBook().read<ExGalleryMetadata>(it)
} }
@ -26,5 +52,9 @@ class MetadataHelper {
return exGalleryBook().exist(it.galleryUniqueIdentifier()) 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 exGalleryBook() = Paper.book("gallery-ex")!!
fun pervEdenGalleryBook() = Paper.book("gallery-perveden")!!
} }

View File

@ -3,7 +3,10 @@ package exh.metadata
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.PervEden
import exh.metadata.models.ExGalleryMetadata import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.PervEdenGalleryMetadata
import exh.metadata.models.SearchableGalleryMetadata
import exh.metadata.models.Tag import exh.metadata.models.Tag
import exh.plusAssign import exh.plusAssign
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -40,7 +43,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
//No title bug? //No title bug?
val titleObj = if(prefs.useJapaneseTitle().getOrDefault()) val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
altTitle ?: title altTitles.firstOrNull() ?: title
else else
title title
titleObj?.let { manga.title = it } titleObj?.let { manga.title = it }
@ -70,7 +73,7 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
//Build a nice looking description out of what we know //Build a nice looking description out of what we know
val titleDesc = StringBuilder() val titleDesc = StringBuilder()
title?.let { titleDesc += "Title: $it\n" } title?.let { titleDesc += "Title: $it\n" }
altTitle?.let { titleDesc += "Japanese Title: $it\n" } altTitles.firstOrNull()?.let { titleDesc += "Alternate Title: $it\n" }
val detailsDesc = StringBuilder() val detailsDesc = StringBuilder()
uploader?.let { detailsDesc += "Uploader: $it\n" } uploader?.let { detailsDesc += "Uploader: $it\n" }
@ -90,16 +93,66 @@ fun ExGalleryMetadata.copyTo(manga: SManga) {
detailsDesc += "\n" 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) val tagsDesc = buildTagsDescription(this)
.filter { it.isNotBlank() }
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n") .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"
}
}
}

View File

@ -7,19 +7,15 @@ import java.util.*
* Gallery metadata storage model * Gallery metadata storage model
*/ */
class ExGalleryMetadata { class ExGalleryMetadata : SearchableGalleryMetadata() {
var url: String? = null var url: String? = null
var exh: Boolean? = null var exh: Boolean? = null
var title: String? = null
var altTitle: String? = null
var thumbnailUrl: String? = null var thumbnailUrl: String? = null
var genre: String? = null var genre: String? = null
var uploader: String? = null
var datePosted: Long? = null var datePosted: Long? = null
var parent: String? = null var parent: String? = null
var visible: String? = null //Not a boolean var visible: String? = null //Not a boolean
@ -31,8 +27,6 @@ class ExGalleryMetadata {
var ratingCount: Int? = null var ratingCount: Int? = null
var averageRating: Double? = null var averageRating: Double? = null
//Being specific about which classes are used in generics to make deserialization easier
var tags: HashMap<String, ArrayList<Tag>> = HashMap()
private fun splitGalleryUrl() private fun splitGalleryUrl()
= url?.let { = url?.let {
@ -44,8 +38,10 @@ class ExGalleryMetadata {
fun galleryToken() = fun galleryToken() =
splitGalleryUrl()?.last() splitGalleryUrl()?.last()
fun galleryUniqueIdentifier() = exh?.let { exh -> override fun galleryUniqueIdentifier() = exh?.let { exh ->
url?.let { 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()}" "${if(exh) "EXH" else "EX"}-${galleryId()}-${galleryToken()}"
} }
} }

View File

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

View File

@ -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<String> = mutableListOf()
//Being specific about which classes are used in generics to make deserialization easier
val tags: HashMap<String, ArrayList<Tag>> = HashMap()
abstract fun galleryUniqueIdentifier(): String?
}

View File

@ -1,13 +1,13 @@
package exh.search package exh.search
import exh.metadata.models.ExGalleryMetadata import exh.metadata.models.SearchableGalleryMetadata
import exh.metadata.models.Tag import exh.metadata.models.Tag
class SearchEngine { class SearchEngine {
private val queryCache = mutableMapOf<String, List<QueryComponent>>() private val queryCache = mutableMapOf<String, List<QueryComponent>>()
fun matches(metadata: ExGalleryMetadata, query: List<QueryComponent>): Boolean { fun matches(metadata: SearchableGalleryMetadata, query: List<QueryComponent>): Boolean {
fun matchTagList(tags: Sequence<Tag>, fun matchTagList(tags: Sequence<Tag>,
component: Text): Boolean { component: Text): Boolean {
@ -29,13 +29,13 @@ class SearchEngine {
} }
val cachedLowercaseTitle = metadata.title?.toLowerCase() val cachedLowercaseTitle = metadata.title?.toLowerCase()
val cachedLowercaseAltTitle = metadata.altTitle?.toLowerCase() val cachedLowercaseAltTitles = metadata.altTitles.map(String::toLowerCase)
for(component in query) { for(component in query) {
if(component is Text) { if(component is Text) {
//Match title //Match title
if (component.asRegex().test(cachedLowercaseTitle) if (component.asRegex().test(cachedLowercaseTitle)
|| component.asRegex().test(cachedLowercaseAltTitle)) { || cachedLowercaseAltTitles.find { component.asRegex().test(it) } != null) {
continue continue
} }
//Match tags //Match tags

View File

@ -63,7 +63,7 @@ class MetadataFetchDialog {
source?.let { source?.let {
it as EHentai it as EHentai
manga.copyFrom(it.fetchMangaDetails(manga).toBlocking().first()) 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) { } catch(t: Throwable) {
Timber.e(t, "Could not migrate manga!") Timber.e(t, "Could not migrate manga!")

View File

@ -39,33 +39,33 @@ class UrlMigrator {
//Sort possible dups so we can use binary search on it //Sort possible dups so we can use binary search on it
possibleDups.sortBy { it.url } possibleDups.sortBy { it.url }
badMangas.forEach { badMangas.forEach { manga ->
//Build fixed URL //Build fixed URL
val urlWithSlash = "/" + it.url val urlWithSlash = "/" + manga.url
//Fix metadata if required //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 { metadata?.url?.let {
if(it.startsWith("g/")) { //Check if metadata URL has no slash if(it.startsWith("g/")) { //Check if metadata URL has no slash
metadata.url = urlWithSlash //Fix it 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 //If we have a dup (with the fixed url), use the dup instead
val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url }) val possibleDup = possibleDups.binarySearchBy(urlWithSlash, selector = { it.url })
if(possibleDup >= 0) { if(possibleDup >= 0) {
//Make sure it is favorited if we are //Make sure it is favorited if we are
if(it.favorite) { if(manga.favorite) {
val dup = possibleDups[possibleDup] val dup = possibleDups[possibleDup]
dup.favorite = true dup.favorite = true
db.insertManga(dup).executeAsBlocking() //Update DB with changes db.insertManga(dup).executeAsBlocking() //Update DB with changes
} }
//Delete ourself (but the dup is still there) //Delete ourself (but the dup is still there)
db.deleteManga(it).executeAsBlocking() db.deleteManga(manga).executeAsBlocking()
return@forEach return@forEach
} }
//No dup, correct URL and reinsert ourselves //No dup, correct URL and reinsert ourselves
it.url = urlWithSlash manga.url = urlWithSlash
db.insertManga(it).executeAsBlocking() db.insertManga(manga).executeAsBlocking()
} }
} }
} }

View File

@ -0,0 +1,10 @@
package exh.util
import android.net.Uri
/**
* Uri filter
*/
interface UriFilter {
fun addToUri(builder: Uri.Builder)
}

View File

@ -0,0 +1,15 @@
package exh.util
import android.net.Uri
import eu.kanade.tachiyomi.source.model.Filter
/**
* UriGroup
*/
open class UriGroup<V>(name: String, state: List<V>) : Filter.Group<V>(name, state), UriFilter {
override fun addToUri(builder: Uri.Builder) {
state.forEach {
if(it is UriFilter) it.addToUri(builder)
}
}
}