# Conflicts:
#	README.md
#	app/build.gradle
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/source/SourceManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/backup/BackupPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogFragment.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/res/raw/changelog_release.xml
#	app/src/main/res/values/arrays.xml
#	app/src/main/res/values/strings.xml
This commit is contained in:
NerdNumber9 2017-03-04 22:54:00 -05:00
parent aba8d01818
commit 5f48bb8e7d
14 changed files with 367 additions and 197 deletions

View File

@ -130,6 +130,14 @@
android:host="g.e-hentai.org" android:host="g.e-hentai.org"
android:pathPrefix="/g/" android:pathPrefix="/g/"
android:scheme="https"/> android:scheme="https"/>
<data
android:host="e-hentai.org"
android:pathPrefix="/g/"
android:scheme="http"/>
<data
android:host="e-hentai.org"
android:pathPrefix="/g/"
android:scheme="https"/>
<data <data
android:host="exhentai.org" android:host="exhentai.org"
android:pathPrefix="/g/" android:pathPrefix="/g/"

View File

@ -170,6 +170,8 @@ class PreferencesHelper(val context: Context) {
fun hasPerformedURLMigration() = rxPrefs.getBoolean("performed_url_migration", false) fun hasPerformedURLMigration() = rxPrefs.getBoolean("performed_url_migration", false)
fun hasPerformedSourceMigration() = rxPrefs.getBoolean("performed_source_migration", false)
//EH Cookies //EH Cookies
fun memberIdVal() = rxPrefs.getString("eh_ipb_member_id", null) fun memberIdVal() = rxPrefs.getString("eh_ipb_member_id", null)
fun passHashVal() = rxPrefs.getString("eh_ipb_pass_hash", null) fun passHashVal() = rxPrefs.getString("eh_ipb_pass_hash", null)

View File

@ -7,6 +7,10 @@ import android.content.pm.PackageManager
import android.os.Environment import android.os.Environment
import dalvik.system.PathClassLoader import dalvik.system.PathClassLoader
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
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.HttpSource
import eu.kanade.tachiyomi.source.online.YamlHttpSource import eu.kanade.tachiyomi.source.online.YamlHttpSource
import eu.kanade.tachiyomi.source.online.english.* import eu.kanade.tachiyomi.source.online.english.*
@ -15,16 +19,38 @@ 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.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 timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
open class SourceManager(private val context: Context) { open class SourceManager(private val context: Context) {
private val prefs: PreferencesHelper by injectLazy()
private val sourcesMap = mutableMapOf<Long, Source>() private val sourcesMap = mutableMapOf<Long, Source>()
init { init {
createSources() createSources()
//Rebuild sources when settings change
val action: Action1<Any> = Action1 {
sourcesMap.clear()
createSources()
}
prefs.enableExhentai().asObservable().subscribe(action)
prefs.imageQuality().asObservable().subscribe (action)
prefs.useHentaiAtHome().asObservable().subscribe(action)
prefs.useJapaneseTitle().asObservable().subscribe {
action.call(null)
}
prefs.ehSearchSize().asObservable().subscribe (action)
prefs.thumbnailRows().asObservable().subscribe(action)
} }
open fun get(sourceKey: Long): Source? { open fun get(sourceKey: Long): Source? {
@ -39,6 +65,8 @@ open class SourceManager(private val context: Context) {
createExtensionSources().forEach { registerSource(it) } createExtensionSources().forEach { registerSource(it) }
createYamlSources().forEach { registerSource(it) } createYamlSources().forEach { registerSource(it) }
createInternalSources().forEach { registerSource(it) } createInternalSources().forEach { registerSource(it) }
//EH
createEHSources().forEach { registerSource(it) }
} }
private fun registerSource(source: Source, overwrite: Boolean = false) { private fun registerSource(source: Source, overwrite: Boolean = false) {
@ -61,6 +89,19 @@ open class SourceManager(private val context: Context) {
WieManga() WieManga()
) )
private fun createEHSources(): List<Source> {
//TODO Fix and hook up to createSources...
val exSrcs = mutableListOf(
EHentai(EH_SOURCE_ID, false, context),
EHentaiMetadata(EH_METADATA_SOURCE_ID, false, context)
)
if(prefs.enableExhentai().getOrDefault()) {
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
exSrcs += EHentaiMetadata(EXH_METADATA_SOURCE_ID, true, context)
}
return exSrcs
}
private fun createYamlSources(): List<Source> { private fun createYamlSources(): List<Source> {
val sources = mutableListOf<Source>() val sources = mutableListOf<Source>()

View File

@ -2,20 +2,17 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.network.GET
import eu.kanade.tachiyomi.data.network.asObservableSuccess
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.data.source.model.MangasPage import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.* import exh.metadata.*
import exh.metadata.models.ExGalleryMetadata import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.Tag import exh.metadata.models.Tag
import exh.plusAssign
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
@ -23,10 +20,11 @@ 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 okhttp3.Request
class EHentai(override val id: Int, class EHentai(override val id: Long,
val exh: Boolean, val exh: Boolean,
val context: Context) : OnlineSource() { val context: Context) : HttpSource() {
val schema: String val schema: String
get() = if(prefs.secureEXH().getOrDefault()) get() = if(prefs.secureEXH().getOrDefault())
@ -55,7 +53,7 @@ class EHentai(override val id: Int,
/** /**
* Parse a list of galleries * Parse a list of galleries
*/ */
fun genericMangaParse(response: Response, page: MangasPage? = null) fun genericMangaParse(response: Response)
= with(response.asJsoup()) { = with(response.asJsoup()) {
//Parse mangas //Parse mangas
val parsedMangas = select(".gtr0,.gtr1").map { val parsedMangas = select(".gtr0,.gtr1").map {
@ -81,32 +79,27 @@ class EHentai(override val id: Int,
} }
//Add to page if required //Add to page if required
page?.let { page -> val hasNextPage = select("a[onclick=return false]").last()?.let {
page.mangas += parsedMangas.map { it.manga } it.text() == ">"
select("a[onclick=return false]").last()?.let { } ?: false
if(it.text() == ">") page.nextPageUrl = it.attr("href") MangasPage(parsedMangas.map { it.manga }, hasNextPage)
}
}
//Return parsed mangas anyways
parsedMangas
} }
override fun fetchChapterList(manga: Manga): Observable<List<Chapter>> override fun fetchChapterList(manga: SManga): Observable<List<SChapter>>
= Observable.just(listOf(Chapter.create().apply { = Observable.just(listOf(SChapter.create().apply {
manga_id = manga.id
url = manga.url url = manga.url
name = "Chapter" name = "Chapter"
chapter_number = 1f chapter_number = 1f
})) }))
override fun fetchPageListFromNetwork(chapter: Chapter) override fun fetchPageList(chapter: SChapter)
= fetchChapterPage(chapter, "$baseUrl${chapter.url}").map { = fetchChapterPage(chapter, "$baseUrl/${chapter.url}").map {
it.mapIndexed { i, s -> it.mapIndexed { i, s ->
Page(i, s) Page(i, s)
} }
}!! }!!
private fun fetchChapterPage(chapter: Chapter, np: String, private fun fetchChapterPage(chapter: SChapter, np: String,
pastUrls: List<String> = emptyList()): Observable<List<String>> { pastUrls: List<String> = emptyList()): Observable<List<String>> {
val urls = ArrayList(pastUrls) val urls = ArrayList(pastUrls)
return chapterPageCall(np).flatMap { return chapterPageCall(np).flatMap {
@ -134,60 +127,46 @@ class EHentai(override val id: Int,
return if (it.text() == ">") it.attr("href") else null return if (it.text() == ">") it.attr("href") else null
} }
private fun buildGenreString(filters: List<OnlineSource.Filter>): String { override fun popularMangaRequest(page: Int) = if(exh)
val genreString = StringBuilder() latestUpdatesRequest(page)
for (genre in GENRE_LIST) {
genreString += "&f_"
genreString += genre
genreString += "="
genreString += if (filters.isEmpty()
|| !filters
.map { it.id }
.find { it == genre }
.isNullOrEmpty())
"1"
else
"0"
}
return genreString.toString()
}
override fun popularMangaInitialUrl() = if(exh)
latestUpdatesInitialUrl()
else else
"$baseUrl/toplist.php?tl=15" exGet("$baseUrl/toplist.php?tl=15", page)
override fun popularMangaParse(response: Response, page: MangasPage) { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
genericMangaParse(response, page) val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
uri.appendQueryParameter("f_search", query)
filters.forEach {
if(it is UriFilter) it.addToUri(uri)
}
return exGet(uri.toString(), page)
} }
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page)
= "$baseUrl$QUERY_PREFIX${buildGenreString(filters)}&f_search=${URLEncoder.encode(query, "UTF-8")}"
override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) { override fun popularMangaParse(response: Response) = genericMangaParse(response)
genericMangaParse(response, page) override fun searchMangaParse(response: Response) = genericMangaParse(response)
} override fun latestUpdatesParse(response: Response) = genericMangaParse(response)
override fun latestUpdatesInitialUrl() = baseUrl fun exGet(url: String, page: Int? = null)
= GET(page?.let {
override fun latestUpdatesParse(response: Response, page: MangasPage) { addParam(url, "page", Integer.toString(page - 1))
genericMangaParse(response, page) } ?: url, headers)
}
/** /**
* Parse gallery page to metadata model * Parse gallery page to metadata model
*/ */
override fun mangaDetailsParse(response: Response, manga: Manga) = with(response.asJsoup()) { override fun mangaDetailsParse(response: Response) = with(response.asJsoup()) {
val metdata = ExGalleryMetadata() val metdata = ExGalleryMetadata()
with(metdata) { with(metdata) {
url = manga.url val manga = SManga.create()
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() altTitle = select("#gj").text().nullIfBlank()?.trim()
thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()?.trim() thumbnailUrl = select("#gd1 img").attr("src").nullIfBlank()?.trim()
genre = select(".ic").attr("alt").nullIfBlank()?.trim() genre = select(".ic").parents().attr("href").nullIfBlank()?.trim()?.substringAfterLast('/')
uploader = select("#gdn").text().nullIfBlank()?.trim() uploader = select("#gdn").text().nullIfBlank()?.trim()
@ -252,41 +231,25 @@ class EHentai(override val id: Int,
//Copy metadata to manga //Copy metadata to manga
copyTo(manga) copyTo(manga)
manga
} }
} }
override fun chapterListParse(response: Response, chapters: MutableList<Chapter>) { override fun chapterListParse(response: Response)
throw UnsupportedOperationException() = throw UnsupportedOperationException("Unused method was called somehow!")
}
override fun pageListParse(response: Response, pages: MutableList<Page>) { override fun pageListParse(response: Response)
throw UnsupportedOperationException() = throw UnsupportedOperationException("Unused method was called somehow!")
}
override fun imageUrlParse(response: Response): String { override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException()
}
//Copy and paste from OnlineSource as we need the page argument
override public fun fetchImageUrl(page: Page): Observable<Page> {
page.status = Page.LOAD_PAGE
return client
.newCall(imageUrlRequest(page))
.asObservableSuccess()
.map { imageUrlParse(it, page) }
.doOnError { page.status = Page.ERROR }
.onErrorReturn { null }
.doOnNext { page.imageUrl = it }
.map { page }
}
fun imageUrlParse(response: Response, page: Page): String {
with(response.asJsoup()) { with(response.asJsoup()) {
val currentImage = select("img[onerror]").attr("src") val currentImage = select("img[onerror]").attr("src")
//TODO This doesn't work currently. Find a better way to do this
//Each press of the retry button will choose another server //Each press of the retry button will choose another server
select("#loadfail").attr("onclick").nullIfBlank()?.let { // select("#loadfail").attr("onclick").nullIfBlank()?.let {
page.url = addParam(page.url, "nl", it.substring(it.indexOf('\'') + 1 .. it.lastIndexOf('\'') - 1)) // page.url = addParam(page.url, "nl", it.substring(it.indexOf('\'') + 1 .. it.lastIndexOf('\'') - 1))
} // }
return currentImage return currentImage
} }
} }
@ -366,8 +329,70 @@ class EHentai(override val id: Int,
}.build()!! }.build()!!
//Filters //Filters
val generatedFilters = GENRE_LIST.map { Filter(it, it) } override fun getFilterList() = FilterList(
override fun getFilterList() = generatedFilters 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) {
builder.appendQueryParameter("f_" + genreId, if(state) "1" else "0")
}
}
class GenreGroup : UriGroup<GenreOption>("Genres", listOf(
GenreOption("Dōjinshi", "doujinshi"),
GenreOption("Manga", "manga"),
GenreOption("Artist CG", "artistcg"),
GenreOption("Game CG", "gamecg"),
GenreOption("Western", "western"),
GenreOption("Non-H", "non-h"),
GenreOption("Image Set", "imageset"),
GenreOption("Cosplay", "cosplay"),
GenreOption("Asian Porn", "asianporn"),
GenreOption("Misc", "misc")
))
class AdvancedOption(name: String, val param: String, defValue: Boolean = false): Filter.CheckBox(name, defValue), UriFilter {
override fun addToUri(builder: Uri.Builder) {
if(state)
builder.appendQueryParameter(param, "on")
}
}
class RatingOption : Filter.Select<String>("Minimum Rating", arrayOf(
"Any",
"2 stars",
"3 stars",
"4 stars",
"5 stars"
)), UriFilter {
override fun addToUri(builder: Uri.Builder) {
if(state > 0) builder.appendQueryParameter("f_srdd", Integer.toString(state + 1))
}
}
//Explicit type arg for listOf() to workaround this: KT-16570
class AdvancedGroup : UriGroup<Filter<*>>("Advanced Options", listOf<Filter<*>>(
AdvancedOption("Search Gallery Name", "f_sname", true),
AdvancedOption("Search Gallery Tags", "f_stags", true),
AdvancedOption("Search Gallery Description", "f_sdesc"),
AdvancedOption("Search Torrent Filenames", "f_storr"),
AdvancedOption("Only Show Galleries With Torrents", "f_sto"),
AdvancedOption("Search Low-Power Tags", "f_sdt1"),
AdvancedOption("Search Downvoted Tags", "f_sdt2"),
AdvancedOption("Show Expunged Galleries", "f_sh"),
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"
@ -376,7 +401,6 @@ class EHentai(override val id: Int,
companion object { companion object {
val QUERY_PREFIX = "?f_apply=Apply+Filter" val QUERY_PREFIX = "?f_apply=Apply+Filter"
val GENRE_LIST = arrayOf("doujinshi", "manga", "artistcg", "gamecg", "western", "non-h", "imageset", "cosplay", "asianporn", "misc")
val TR_SUFFIX = "TR" val TR_SUFFIX = "TR"
} }
} }

View File

@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.data.source.online.OnlineSource
import exh.metadata.MetadataHelper import exh.metadata.MetadataHelper
import exh.metadata.copyTo import exh.metadata.copyTo
import exh.metadata.models.ExGalleryMetadata import exh.metadata.models.ExGalleryMetadata
@ -15,11 +14,35 @@ import rx.Observable
/** /**
* Offline metadata store source * Offline metadata store source
*
* TODO This no longer fakes an online source because of technical reasons.
* If we still want offline search, we must find out a way to rearchitecture the source system so it supports
* online source faking again.
*/ */
class EHentaiMetadata(override val id: Int, class EHentaiMetadata(override val id: Long,
val exh: Boolean, val exh: Boolean,
val context: Context) : OnlineSource() { val context: Context) : HttpSource() {
override fun popularMangaRequest(page: Int)
= throw UnsupportedOperationException("Unused method called!")
override fun popularMangaParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
override fun searchMangaRequest(page: Int, query: String, filters: FilterList)
= throw UnsupportedOperationException("Unused method called!")
override fun searchMangaParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
override fun latestUpdatesRequest(page: Int)
= throw UnsupportedOperationException("Unused method called!")
override fun latestUpdatesParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
override fun mangaDetailsParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
override fun chapterListParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
override fun pageListParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
override fun imageUrlParse(response: Response)
= throw UnsupportedOperationException("Unused method called!")
val metadataHelper = MetadataHelper() val metadataHelper = MetadataHelper()
@ -34,55 +57,14 @@ class EHentaiMetadata(override val id: Int,
override val supportsLatest: Boolean override val supportsLatest: Boolean
get() = true get() = true
override fun popularMangaInitialUrl(): String { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>>
throw UnsupportedOperationException()
}
override fun popularMangaParse(response: Response, page: MangasPage) {
throw UnsupportedOperationException()
}
override fun searchMangaInitialUrl(query: String, filters: List<Filter>): String {
throw UnsupportedOperationException()
}
override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List<Filter>) {
throw UnsupportedOperationException()
}
override fun latestUpdatesInitialUrl(): String {
throw UnsupportedOperationException()
}
override fun latestUpdatesParse(response: Response, page: MangasPage) {
throw UnsupportedOperationException()
}
override fun mangaDetailsParse(response: Response, manga: Manga) {
throw UnsupportedOperationException()
}
override fun chapterListParse(response: Response, chapters: MutableList<Chapter>) {
throw UnsupportedOperationException()
}
override fun pageListParse(response: Response, pages: MutableList<Page>) {
throw UnsupportedOperationException()
}
override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException()
}
override fun fetchChapterList(manga: Manga): Observable<List<Chapter>>
= Observable.just(listOf(Chapter.create().apply { = Observable.just(listOf(Chapter.create().apply {
manga_id = manga.id
url = manga.url url = manga.url
name = "ONLINE - Chapter" name = "ONLINE - Chapter"
chapter_number = 1f chapter_number = 1f
})) }))
override fun fetchPageListFromNetwork(chapter: Chapter) = internalEx.fetchPageListFromNetwork(chapter) override fun fetchPageList(chapter: SChapter) = internalEx.fetchPageList(chapter)
override fun fetchImageUrl(page: Page) = internalEx.fetchImageUrl(page) override fun fetchImageUrl(page: Page) = internalEx.fetchImageUrl(page)
@ -98,38 +80,42 @@ class EHentaiMetadata(override val id: Int,
it.datePosted ?: 0 it.datePosted ?: 0
} }
override fun fetchPopularManga(page: MangasPage) override fun fetchPopularManga(page: Int)
= Observable.fromCallable { = Observable.fromCallable {
page.mangas.addAll(metadataHelper.getAllGalleries().sortedByDescending { MangasPage(metadataHelper.getAllGalleries().sortedByDescending {
it.ratingCount ?: 0 it.ratingCount ?: 0
}.mapToManga()) }.mapToManga(), false)
page
}!! }!!
override fun fetchSearchManga(page: MangasPage, query: String, filters: List<Filter>) override fun fetchSearchManga(page: Int, query: String, filters: FilterList)
= Observable.fromCallable { = Observable.fromCallable {
val genreGroup = filters.find {
it is EHentai.GenreGroup
}!! as EHentai.GenreGroup
val disableGenreFilter = genreGroup.state.find(EHentai.GenreOption::state) == null
val parsed = searchEngine.parseQuery(query) val parsed = searchEngine.parseQuery(query)
page.mangas.addAll(sortedByTimeGalleries().filter { manga -> MangasPage(sortedByTimeGalleries().filter { manga ->
filters.isEmpty() || filters.filter { it.id == manga.genre }.isNotEmpty() disableGenreFilter || genreGroup.state.find {
it.state && it.genreId == manga.genre
} != null
}.filter { }.filter {
searchEngine.matches(it, parsed) searchEngine.matches(it, parsed)
}.mapToManga()) }.mapToManga(), false)
page
}!! }!!
override fun fetchLatestUpdates(page: MangasPage) override fun fetchLatestUpdates(page: Int)
= Observable.fromCallable { = Observable.fromCallable {
page.mangas.addAll(sortedByTimeGalleries().mapToManga()) MangasPage(sortedByTimeGalleries().mapToManga(), false)
page
}!! }!!
override fun fetchMangaDetails(manga: Manga) = 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.fetchMetadata(manga.url, exh)?.copyTo(manga)
manga manga
}!! }!!
override fun getFilterList() = internalEx.getFilterList() override fun getFilterList() = FilterList(EHentai.GenreGroup())
override val name: String override val name: String
get() = if(exh) { get() = if(exh) {

View File

@ -9,17 +9,17 @@ import android.widget.FrameLayout
import eu.davidea.flexibleadapter4.FlexibleAdapter import eu.davidea.flexibleadapter4.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.data.source.online.all.EHentaiMetadata import eu.kanade.tachiyomi.source.online.all.EHentaiMetadata
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import exh.isLewdSource
import exh.metadata.MetadataHelper import exh.metadata.MetadataHelper
import exh.search.SearchEngine import exh.search.SearchEngine
import kotlinx.android.synthetic.main.item_catalogue_grid.view.* import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.* import java.util.*
import kotlin.concurrent.thread
/** /**
* Adapter storing a list of manga in a certain category. * Adapter storing a list of manga in a certain category.
@ -95,7 +95,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryView) :
* @return true if the manga should be included, false otherwise. * @return true if the manga should be included, false otherwise.
*/ */
override fun filterObject(manga: Manga, query: String): Boolean = with(manga) { override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
if(manga.source > 100) { if(!isLewdSource(manga.source)) {
//Regular searching for normal manga //Regular searching for normal manga
title.toLowerCase().contains(query) || title.toLowerCase().contains(query) ||
author != null && author!!.toLowerCase().contains(query) author != null && author!!.toLowerCase().contains(query)

View File

@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadFragment
import eu.kanade.tachiyomi.ui.setting.SettingsActivity import eu.kanade.tachiyomi.ui.setting.SettingsActivity
import exh.ui.batchadd.BatchAddFragment import exh.ui.batchadd.BatchAddFragment
import exh.ui.migration.LibraryMigrationManager import exh.ui.migration.LibraryMigrationManager
import exh.ui.migration.SourceMigrator
import exh.ui.migration.UrlMigrator import exh.ui.migration.UrlMigrator
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.toolbar.* import kotlinx.android.synthetic.main.toolbar.*
@ -89,25 +90,31 @@ class MainActivity : BaseActivity() {
} }
if (savedState == null) { if (savedState == null) {
// Set start screen //Perform source migration
setSelectedDrawerItem(startScreenId) SourceMigrator().tryMigrationWithDialog(this, {
// Show changelog if needed // Set start screen
ChangelogDialogFragment.show(this, preferences, supportFragmentManager) try {
setSelectedDrawerItem(startScreenId)
} catch(e: Exception) {}
// Migrate library if needed // Show changelog if needed
LibraryMigrationManager(this, dismissQueue).askMigrationIfNecessary() ChangelogDialogFragment.show(this, preferences, supportFragmentManager)
//Last part of migration requires finishing this activity // Migrate library if needed
finishSubscription?.unsubscribe() LibraryMigrationManager(this, dismissQueue).askMigrationIfNecessary()
preferences.finishMainActivity().set(false)
finishSubscription = preferences.finishMainActivity().asObservable().subscribe {
if(it)
finish()
}
//Migrate URLs if necessary //Last part of migration requires finishing this activity
UrlMigrator().tryMigration() finishSubscription?.unsubscribe()
preferences.finishMainActivity().set(false)
finishSubscription = preferences.finishMainActivity().asObservable().subscribe {
if (it)
finish()
}
//Migrate URLs if necessary
UrlMigrator().tryMigration()
})
} }
} }

View File

@ -1,5 +1,17 @@
package exh package exh
/** /**
* Created by nulldev on 2/28/17. * Source helpers
*/ */
val LEWD_SOURCE_SERIES = 6900L
val EH_SOURCE_ID = LEWD_SOURCE_SERIES + 1
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
fun isExSource(source: Long) = source == EXH_SOURCE_ID
|| source == EXH_METADATA_SOURCE_ID

View File

@ -2,14 +2,15 @@ package exh
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.UrlUtil
import eu.kanade.tachiyomi.util.syncChaptersWithSource import eu.kanade.tachiyomi.util.syncChaptersWithSource
import exh.metadata.MetadataHelper import exh.metadata.MetadataHelper
import exh.metadata.copyTo import exh.metadata.copyTo
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URI
import java.net.URISyntaxException
import java.net.URL import java.net.URL
class GalleryAdder { class GalleryAdder {
@ -22,19 +23,20 @@ class GalleryAdder {
fun addGallery(url: String, fav: Boolean = false): Manga { fun addGallery(url: String, fav: Boolean = false): Manga {
val source = when(URL(url).host) { val source = when(URL(url).host) {
"g.e-hentai.org", "e-hentai.org" -> 1 "g.e-hentai.org", "e-hentai.org" -> EH_SOURCE_ID
"exhentai.org" -> 2 "exhentai.org" -> EXH_SOURCE_ID
else -> throw MalformedURLException("Not a valid gallery URL!") else -> throw MalformedURLException("Not a valid gallery URL!")
} }
val sourceObj = sourceManager.get(source) val sourceObj = sourceManager.get(source)
?: throw IllegalStateException("Could not find EH source!") ?: throw IllegalStateException("Could not find EH source!")
val pathOnlyUrl = UrlUtil.getPath(url) val pathOnlyUrl = getUrlWithoutDomain(url)
//Use manga in DB if possible, otherwise, make a new manga //Use manga in DB if possible, otherwise, make a new manga
val manga = db.getManga(pathOnlyUrl, source).executeAsBlocking() val manga = db.getManga(pathOnlyUrl, source).executeAsBlocking()
?: Manga.create(pathOnlyUrl, source).apply { ?: Manga.create(source).apply {
this.url = pathOnlyUrl
title = url title = url
} }
@ -42,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, source == 2)?.copyTo(manga) metadataHelper.fetchMetadata(url, isExSource(source))?.copyTo(manga)
if(fav) manga.favorite = true if(fav) manga.favorite = true
@ -61,4 +63,18 @@ class GalleryAdder {
return manga return manga
} }
private fun getUrlWithoutDomain(orig: String): String {
try {
val uri = URI(orig)
var out = uri.path
if (uri.query != null)
out += "?" + uri.query
if (uri.fragment != null)
out += "#" + uri.fragment
return out
} catch (e: URISyntaxException) {
return orig
}
}
} }

View File

@ -1,9 +1,8 @@
package exh.metadata package exh.metadata
import eu.kanade.tachiyomi.data.database.models.Manga
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.util.UrlUtil import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.models.ExGalleryMetadata import exh.metadata.models.ExGalleryMetadata
import exh.metadata.models.Tag import exh.metadata.models.Tag
import exh.plusAssign import exh.plusAssign
@ -28,16 +27,18 @@ val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
private val prefs: PreferencesHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy()
fun ExGalleryMetadata.copyTo(manga: Manga) { fun ExGalleryMetadata.copyTo(manga: SManga) {
exh?.let { //TODO Find some way to do this with SManga
/*exh?.let {
manga.source = if(it) manga.source = if(it)
2 2
else else
1 1
} }*/
url?.let { manga.url = it } url?.let { manga.url = it }
thumbnailUrl?.let { manga.thumbnail_url = it } thumbnailUrl?.let { manga.thumbnail_url = it }
//No title bug?
val titleObj = if(prefs.useJapaneseTitle().getOrDefault()) val titleObj = if(prefs.useJapaneseTitle().getOrDefault())
altTitle ?: title altTitle ?: title
else else
@ -57,12 +58,12 @@ fun ExGalleryMetadata.copyTo(manga: Manga) {
//Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes //Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes
//We default to completed //We default to completed
manga.status = Manga.COMPLETED manga.status = SManga.COMPLETED
title?.let { t -> title?.let { t ->
ONGOING_SUFFIX.find { ONGOING_SUFFIX.find {
t.endsWith(it, ignoreCase = true) t.endsWith(it, ignoreCase = true)
}?.let { }?.let {
manga.status = Manga.ONGOING manga.status = SManga.ONGOING
} }
} }

View File

@ -7,8 +7,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
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.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.isExSource
import exh.metadata.MetadataHelper import exh.metadata.MetadataHelper
import exh.metadata.copyTo import exh.metadata.copyTo
import timber.log.Timber import timber.log.Timber
@ -44,7 +45,7 @@ class MetadataFetchDialog {
.executeAsBlocking() .executeAsBlocking()
.filter { .filter {
it.source <= 2 it.source <= 2
&& !metadataHelper.hasMetadata(it.url, it.source == 2) && !metadataHelper.hasMetadata(it.url, isExSource(it.source))
} }
context.runOnUiThread { context.runOnUiThread {
@ -91,7 +92,7 @@ class MetadataFetchDialog {
db.getLibraryMangas().asRxSingle().subscribe { db.getLibraryMangas().asRxSingle().subscribe {
//Not logged in but have ExHentai galleries //Not logged in but have ExHentai galleries
if(!preferenceHelper.enableExhentai().getOrDefault()) { if(!preferenceHelper.enableExhentai().getOrDefault()) {
it.find { it.source == 2 }?.let { it.find { isExSource(it.source) }?.let {
extra = "<b><font color='red'>If you use ExHentai, please log in first before fetching your library metadata!</font></b><br><br>" extra = "<b><font color='red'>If you use ExHentai, please log in first before fetching your library metadata!</font></b><br><br>"
} }
} }

View File

@ -56,6 +56,9 @@ class MigrationCompletionActivity : BaseActivity() {
//Migrate urls //Migrate urls
UrlMigrator().perform() UrlMigrator().perform()
//Migrate source IDs
SourceMigrator().perform()
//Go back to MainActivity //Go back to MainActivity
//Set final steps //Set final steps
preferenceManager.migrationStatus().set(MigrationStatus.FINALIZE_MIGRATION) preferenceManager.migrationStatus().set(MigrationStatus.FINALIZE_MIGRATION)

View File

@ -1,5 +1,74 @@
package exh.ui.migration package exh.ui.migration
/** import android.app.Activity
* Created by nulldev on 2/28/17. import com.afollestad.materialdialogs.MaterialDialog
*/ import eu.kanade.tachiyomi.data.backup.BackupManager
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import exh.LEWD_SOURCE_SERIES
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.io.File
import kotlin.concurrent.thread
class SourceMigrator {
val db: DatabaseHelper by injectLazy()
val prefs: PreferencesHelper by injectLazy()
val backupManager by lazy {
BackupManager(db)
}
fun perform() {
db.insertMangas(db.getMangas().executeAsBlocking().map {
if(it.source < 100) {
if(it.url.trim('/').startsWith("g/")) {
//EH source, move ID
it.source += LEWD_SOURCE_SERIES
}
} else if(it.source < 200) {
//Regular source, move ID down
it.source -= 100
}
it
}).executeAsBlocking()
}
fun tryMigrationWithDialog(context: Activity, callback: () -> Unit) {
if(!prefs.hasPerformedSourceMigration().getOrDefault()) {
val dialog = MaterialDialog.Builder(context)
.title("Migrating galleries")
.progress(true, 0)
.cancelable(false)
.show()
thread {
try {
context.runOnUiThread {
dialog.setContent("Backing up library...")
}
backupManager.backupToFile(File(context.filesDir, "teh-source-migration-bck.json"))
context.runOnUiThread {
dialog.setContent("Performing migration...")
}
perform()
context.runOnUiThread {
dialog.setContent("Completing migration...")
}
prefs.hasPerformedSourceMigration().set(true)
dialog.dismiss()
} catch(e: Exception) {
Timber.e(e, "Error migrating source IDs!")
}
context.runOnUiThread {
callback()
}
}
} else {
callback()
}
}
}

View File

@ -4,6 +4,8 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
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 exh.isExSource
import exh.isLewdSource
import exh.metadata.MetadataHelper import exh.metadata.MetadataHelper
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -21,8 +23,7 @@ class UrlMigrator {
//Find all EX mangas //Find all EX mangas
val qualifyingMangas = dbMangas.asSequence().filter { val qualifyingMangas = dbMangas.asSequence().filter {
it.source > 0 isLewdSource(it.source)
&& it.source <= 4
} }
val possibleDups = mutableListOf<Manga>() val possibleDups = mutableListOf<Manga>()
@ -42,8 +43,7 @@ class UrlMigrator {
//Build fixed URL //Build fixed URL
val urlWithSlash = "/" + it.url val urlWithSlash = "/" + it.url
//Fix metadata if required //Fix metadata if required
val metadata = metadataHelper.fetchMetadata(it.url, it.source == 2 val metadata = metadataHelper.fetchMetadata(it.url, isExSource(it.source))
|| it.source == 4)
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