Linting Fixes AZ

This commit is contained in:
Jobobby04
2020-05-02 00:46:24 -04:00
parent 03e5c5ca10
commit 7e99a9f789
108 changed files with 2962 additions and 2412 deletions
@@ -83,10 +83,12 @@ class ChapterCache(private val context: Context) {
// --> EH
// Cache size is in MB
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
return DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT,
cacheSize * 1024 * 1024)
return DiskLruCache.open(
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT,
cacheSize * 1024 * 1024
)
}
// <-- EH
@@ -19,18 +19,22 @@ interface ChapterQueries : DbProvider {
fun getChaptersByMangaId(mangaId: Long?) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(Query.builder()
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build())
.build()
)
.prepare()
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(RawQuery.builder()
.withQuery(
RawQuery.builder()
.query(getMergedChaptersQuery(mangaId))
.build())
.build()
)
.prepare()
fun getRecentChapters(date: Date) = db.get()
@@ -80,11 +84,13 @@ interface ChapterQueries : DbProvider {
fun getChapters(url: String) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(Query.builder()
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build())
.build()
)
.prepare()
@@ -10,7 +10,8 @@ import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged
/**
* Query to get the manga merged into a merged manga
*/
fun getMergedMangaQuery(id: Long) = """
fun getMergedMangaQuery(id: Long) =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
@@ -22,7 +23,8 @@ fun getMergedMangaQuery(id: Long) = """
/**
* Query to get the chapters of all manga in a merged manga
*/
fun getMergedChaptersQuery(id: Long) = """
fun getMergedChaptersQuery(id: Long) =
"""
SELECT ${Chapter.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
@@ -21,10 +21,10 @@ class MangaUrlPutResolver : PutResolver<Manga>() {
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_URL, manga.url)
@@ -9,7 +9,8 @@ object MergedTable {
const val COL_MANGA_ID = "mangaID"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_MERGE_ID INTEGER NOT NULL,
$COL_MANGA_ID INTEGER NOT NULL
)"""
@@ -147,7 +147,7 @@ class ExtensionManager(
fun Extension.isBlacklisted(
blacklistEnabled: Boolean =
preferences.eh_enableSourceBlacklist().get()
preferences.eh_enableSourceBlacklist().get()
): Boolean {
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
}
@@ -37,7 +37,7 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
private fun newMetaInstance() = metaClass.constructors.find {
it.parameters.isEmpty()
}?.call()
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
/**
* Parses metadata from the input and then copies it into the manga
@@ -19,10 +19,12 @@ interface UrlImportableSource : Source {
return try {
val uri = URI(url)
var out = uri.path
if (uri.query != null)
if (uri.query != null) {
out += "?" + uri.query
if (uri.fragment != null)
}
if (uri.fragment != null) {
out += "#" + uri.fragment
}
out
} catch (e: URISyntaxException) {
url
@@ -73,16 +73,18 @@ class EHentai(
override val metaClass = EHentaiSearchMetadata::class
val schema: String
get() = if (prefs.secureEXH().getOrDefault())
get() = if (prefs.secureEXH().getOrDefault()) {
"https"
else
} else {
"http"
}
val domain: String
get() = if (exh)
get() = if (exh) {
"exhentai.org"
else
} else {
"e-hentai.org"
}
override val baseUrl: String
get() = "$schema://$domain"
@@ -111,25 +113,27 @@ class EHentai(
val favElement = column2.children().find { it.attr("style").startsWith("border-color") }
ParsedManga(
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
favElement?.attr("style")?.substring(14, 17)
),
manga = Manga.create(id).apply {
// Get title
title = thumbnailElement.attr("title")
url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href"))
// Get image
thumbnail_url = thumbnailElement.attr("src")
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
favElement?.attr("style")?.substring(14, 17)
),
manga = Manga.create(id).apply {
// Get title
title = thumbnailElement.attr("title")
url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href"))
// Get image
thumbnail_url = thumbnailElement.attr("src")
// TODO Parse genre + uploader + tags
})
// TODO Parse genre + uploader + tags
}
)
}
val parsedLocation = doc.location().toHttpUrlOrNull()
// Add to page if required
val hasNextPage = if (parsedLocation == null ||
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) {
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)
) {
select("a[onclick=return false]").last()?.let {
it.text() == ">"
} ?: false
@@ -160,7 +164,7 @@ class EHentai(
while (true) {
val gid = EHentaiSearchMetadata.galleryId(url).toInt()
val cachedParent = updateHelper.parentLookupTable.get(
gid
gid
)
if (cachedParent == null) {
throttleFunc()
@@ -175,19 +179,19 @@ class EHentai(
if (parentLink != null) {
updateHelper.parentLookupTable.put(
gid,
GalleryEntry(
EHentaiSearchMetadata.galleryId(parentLink),
EHentaiSearchMetadata.galleryToken(parentLink)
)
gid,
GalleryEntry(
EHentaiSearchMetadata.galleryId(parentLink),
EHentaiSearchMetadata.galleryToken(parentLink)
)
)
url = EHentaiSearchMetadata.normalizeUrl(parentLink)
} else break
} else {
XLog.d("Parent cache hit: %s!", gid)
url = EHentaiSearchMetadata.idAndTokenToUrl(
cachedParent.gId,
cachedParent.gToken
cachedParent.gId,
cachedParent.gToken
)
}
}
@@ -201,9 +205,11 @@ class EHentai(
url = EHentaiSearchMetadata.normalizeUrl(d.location())
name = "v1: " + d.selectFirst("#gn").text()
chapter_number = 1f
date_upload = EX_DATE_FORMAT.parse(d.select("#gdd .gdt1").find { el ->
el.text().toLowerCase() == "posted:"
}!!.nextElementSibling().text()).time
date_upload = EX_DATE_FORMAT.parse(
d.select("#gdd .gdt1").find { el ->
el.text().toLowerCase() == "posted:"
}!!.nextElementSibling().text()
).time
}
// Build and append the rest of the galleries
if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self)
@@ -253,27 +259,32 @@ class EHentai(
}.sortedBy(Pair<Int, String>::first).map { it.second }
}
private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
private fun chapterPageRequest(np: String) = exGet(np, null, headers)
private fun chapterPageCall(np: String): Observable<Response> {
return client.newCall(chapterPageRequest(np)).asObservableSuccess()
}
private fun chapterPageRequest(np: String): Request {
return exGet(np, null, headers)
}
private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let {
return if (it.text() == ">") it.attr("href") else null
}
override fun popularMangaRequest(page: Int) = if (exh)
override fun popularMangaRequest(page: Int) = if (exh) {
latestUpdatesRequest(page)
else
} else {
exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists
}
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(query) {
searchMangaRequestObservable(page, query, filters).flatMap {
client.newCall(it).asObservableSuccess()
}.map { response ->
searchMangaParse(response)
}
urlImportFetchSearchManga(query) {
searchMangaRequestObservable(page, query, filters).flatMap {
client.newCall(it).asObservableSuccess()
}.map { response ->
searchMangaParse(response)
}
}
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
@@ -287,20 +298,20 @@ class EHentai(
// Reverse search results on filter
if (filters.any { it is ReverseFilter && it.state }) {
return client.newCall(request)
.asObservableSuccess()
.map {
val doc = it.asJsoup()
.asObservableSuccess()
.map {
val doc = it.asJsoup()
val elements = doc.select(".ptt > tbody > tr > td")
val elements = doc.select(".ptt > tbody > tr > td")
val totalElement = elements[elements.size - 2]
val totalElement = elements[elements.size - 2]
val thisPage = totalElement.text().toInt() - (page - 1)
val thisPage = totalElement.text().toInt() - (page - 1)
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
exGet(uri.toString(), thisPage)
}
exGet(uri.toString(), thisPage)
}
} else {
return Observable.just(request)
}
@@ -314,22 +325,28 @@ class EHentai(
override fun searchMangaParse(response: Response) = genericMangaParse(response)
override fun latestUpdatesParse(response: Response) = genericMangaParse(response)
fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true) = GET(page?.let {
addParam(url, "page", Integer.toString(page - 1))
} ?: url, additionalHeaders?.let {
val headers = headers.newBuilder()
it.toMultimap().forEach { (t, u) ->
u.forEach {
headers.add(t, it)
fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true): Request {
return GET(
page?.let {
addParam(url, "page", Integer.toString(page - 1))
} ?: url,
additionalHeaders?.let {
val headers = headers.newBuilder()
it.toMultimap().forEach { (t, u) ->
u.forEach {
headers.add(t, it)
}
}
headers.build()
} ?: headers
).let {
if (!cache) {
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
} else {
it
}
}
headers.build()
} ?: headers).let {
if (!cache)
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
else
it
}!!
}
/**
* Returns an observable with the updated details for a manga. Normally it's not needed to
@@ -339,33 +356,37 @@ class EHentai(
*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableWithAsyncStacktrace()
.flatMap { (stacktrace, response) ->
if (response.isSuccessful) {
// Pull to most recent
val doc = response.asJsoup()
val newerGallery = doc.select("#gnd a").lastOrNull()
val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) {
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess().map { it.asJsoup() }
} else Observable.just(doc)
.asObservableWithAsyncStacktrace()
.flatMap { (stacktrace, response) ->
if (response.isSuccessful) {
// Pull to most recent
val doc = response.asJsoup()
val newerGallery = doc.select("#gnd a").lastOrNull()
val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) {
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess().map { it.asJsoup() }
} else Observable.just(doc)
pre.flatMap {
parseToManga(manga, it).andThen(Observable.just(manga.apply {
initialized = true
}))
}
pre.flatMap {
parseToManga(manga, it).andThen(
Observable.just(
manga.apply {
initialized = true
}
)
)
}
} else {
response.close()
if (response.code == 404) {
throw GalleryNotFoundException(stacktrace)
} else {
response.close()
if (response.code == 404) {
throw GalleryNotFoundException(stacktrace)
} else {
throw Exception("HTTP error ${response.code}", stacktrace)
}
throw Exception("HTTP error ${response.code}", stacktrace)
}
}
}
}
/**
@@ -389,11 +410,11 @@ class EHentai(
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
}
genre = select(".cs")
.attr("onclick")
.nullIfBlank()
?.trim()
?.substringAfterLast('/')
?.removeSuffix("'")
.attr("onclick")
.nullIfBlank()
?.trim()
?.substringAfterLast('/')
?.removeSuffix("'")
uploader = select("#gdn").text().nullIfBlank()?.trim()
@@ -404,8 +425,10 @@ class EHentai(
val right = rightElement.text().nullIfBlank()?.trim()
if (left != null && right != null) {
ignore {
when (left.removeSuffix(":")
.toLowerCase()) {
when (
left.removeSuffix(":")
.toLowerCase()
) {
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
@@ -428,7 +451,8 @@ class EHentai(
lastUpdateCheck = System.currentTimeMillis()
if (datePosted != null &&
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) {
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME
) {
aged = true
XLog.d("aged %s - too old", title)
}
@@ -436,32 +460,35 @@ class EHentai(
// Parse ratings
ignore {
averageRating = select("#rating_label")
.text()
.removePrefix("Average:")
.trim()
.nullIfBlank()
?.toDouble()
.text()
.removePrefix("Average:")
.trim()
.nullIfBlank()
?.toDouble()
ratingCount = select("#rating_count")
.text()
.trim()
.nullIfBlank()
?.toInt()
.text()
.trim()
.nullIfBlank()
?.toInt()
}
// Parse tags
tags.clear()
select("#taglist tr").forEach {
val namespace = it.select(".tc").text().removeSuffix(":")
tags.addAll(it.select("div").map { element ->
RaisedTag(
tags.addAll(
it.select("div").map { element ->
RaisedTag(
namespace,
element.text().trim(),
if (element.hasClass("gtl"))
if (element.hasClass("gtl")) {
TAG_TYPE_LIGHT
else
} else {
TAG_TYPE_NORMAL
)
})
}
)
}
)
}
// Add genre as virtual tag
@@ -478,8 +505,8 @@ class EHentai(
override fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page))
.asObservableSuccess()
.map { realImageUrlParse(it, page) }
.asObservableSuccess()
.map { realImageUrlParse(it, page) }
}
fun realImageUrlParse(response: Response, page: Page): String {
@@ -505,9 +532,13 @@ class EHentai(
var favNames: List<String>? = null
do {
val response2 = client.newCall(exGet(favoriteUrl,
val response2 = client.newCall(
exGet(
favoriteUrl,
page = page,
cache = false)).execute()
cache = false
)
).execute()
val doc = response2.asJsoup()
// Parse favorites
@@ -515,22 +546,24 @@ class EHentai(
result += parsed.first
// Parse fav names
if (favNames == null)
if (favNames == null) {
favNames = doc.select(".fp:not(.fps)").mapNotNull {
it.child(2).text()
}
}
// Next page
page++
} while (parsed.second)
return Pair(result as List<ParsedManga>, favNames!!)
}
fun spPref() = if (exh)
fun spPref() = if (exh) {
prefs.eh_exhSettingsProfile()
else
} else {
prefs.eh_ehSettingsProfile()
}
fun rawCookies(sp: Int): Map<String, String> {
val cookies: MutableMap<String, String> = mutableMapOf()
@@ -541,16 +574,19 @@ class EHentai(
cookies["sp"] = sp.toString()
val sessionKey = prefs.eh_settingsKey().getOrDefault()
if (sessionKey != null)
if (sessionKey != null) {
cookies["sk"] = sessionKey
}
val sessionCookie = prefs.eh_sessionCookie().getOrDefault()
if (sessionCookie != null)
if (sessionCookie != null) {
cookies["s"] = sessionCookie
}
val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault()
if (hathPerksCookie != null)
if (hathPerksCookie != null) {
cookies["hath_perks"] = hathPerksCookie
}
}
// Session-less extended display mode (for users without ExHentai)
@@ -568,51 +604,57 @@ class EHentai(
override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!!
fun addParam(url: String, param: String, value: String) = Uri.parse(url)
.buildUpon()
.appendQueryParameter(param, value)
.toString()
.buildUpon()
.appendQueryParameter(param, value)
.toString()
override val client = network.client.newBuilder()
.cookieJar(CookieJar.NO_COOKIES)
.addInterceptor { chain ->
val newReq = chain
.request()
.newBuilder()
.removeHeader("Cookie")
.addHeader("Cookie", cookiesHeader())
.build()
.cookieJar(CookieJar.NO_COOKIES)
.addInterceptor { chain ->
val newReq = chain
.request()
.newBuilder()
.removeHeader("Cookie")
.addHeader("Cookie", cookiesHeader())
.build()
chain.proceed(newReq)
}.build()!!
chain.proceed(newReq)
}.build()!!
// Filters
override fun getFilterList() = FilterList(
Watched(),
GenreGroup(),
AdvancedGroup(),
ReverseFilter()
Watched(),
GenreGroup(),
AdvancedGroup(),
ReverseFilter()
)
class Watched : Filter.CheckBox("Watched List"), UriFilter {
override fun addToUri(builder: Uri.Builder) {
if (state)
if (state) {
builder.appendPath("watched")
}
}
}
class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false)
class GenreGroup : Filter.Group<GenreOption>("Genres", listOf(
GenreOption("Dōjinshi", 2),
GenreOption("Manga", 4),
GenreOption("Artist CG", 8),
GenreOption("Game CG", 16),
GenreOption("Western", 512),
GenreOption("Non-H", 256),
GenreOption("Image Set", 32),
GenreOption("Cosplay", 64),
GenreOption("Asian Porn", 128),
GenreOption("Misc", 1)
)), UriFilter {
class GenreGroup :
Filter.Group<GenreOption>(
"Genres",
listOf(
GenreOption("Dōjinshi", 2),
GenreOption("Manga", 4),
GenreOption("Artist CG", 8),
GenreOption("Game CG", 16),
GenreOption("Western", 512),
GenreOption("Non-H", 256),
GenreOption("Image Set", 32),
GenreOption("Cosplay", 64),
GenreOption("Asian Porn", 128),
GenreOption("Misc", 1)
)
),
UriFilter {
override fun addToUri(builder: Uri.Builder) {
val bits = state.fold(0) { acc, genre ->
if (!genre.state) acc + genre.genreId else acc
@@ -623,8 +665,9 @@ class EHentai(
class AdvancedOption(name: String, val param: String, defValue: Boolean = false) : Filter.CheckBox(name, defValue), UriFilter {
override fun addToUri(builder: Uri.Builder) {
if (state)
if (state) {
builder.appendQueryParameter(param, "on")
}
}
}
@@ -643,13 +686,18 @@ class EHentai(
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
class RatingOption : Filter.Select<String>("Minimum Rating", arrayOf(
"Any",
"2 stars",
"3 stars",
"4 stars",
"5 stars"
)), UriFilter {
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))
@@ -658,7 +706,9 @@ class EHentai(
}
}
class AdvancedGroup : UriGroup<Filter<*>>("Advanced Options", listOf(
class AdvancedGroup : UriGroup<Filter<*>>(
"Advanced Options",
listOf(
AdvancedOption("Search Gallery Name", "f_sname", true),
AdvancedOption("Search Gallery Tags", "f_stags", true),
AdvancedOption("Search Gallery Description", "f_sdesc"),
@@ -670,24 +720,26 @@ class EHentai(
RatingOption(),
MinPagesOption(),
MaxPagesOption()
))
)
)
class ReverseFilter : Filter.CheckBox("Reverse search results")
override val name = if (exh)
override val name = if (exh) {
"ExHentai"
else
} else {
"E-Hentai"
}
class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause)
// === URL IMPORT STUFF
override val matchingHosts: List<String> = if (exh) listOf(
"exhentai.org"
"exhentai.org"
) else listOf(
"g.e-hentai.org",
"e-hentai.org"
"g.e-hentai.org",
"e-hentai.org"
)
override fun mapUrlToMangaUrl(uri: Uri): String? {
@@ -717,17 +769,23 @@ class EHentai(
val json = JsonObject()
json["method"] = "gtoken"
json["pagelist"] = JsonArray().apply {
add(JsonArray().apply {
add(gallery.toInt())
add(pageToken)
add(pageNum.toInt())
})
add(
JsonArray().apply {
add(gallery.toInt())
add(pageToken)
add(pageNum.toInt())
}
)
}
val outJson = JsonParser.parseString(client.newCall(Request.Builder()
.url(EH_API_BASE)
.post(RequestBody.create(JSON, json.toString()))
.build()).execute().body!!.string()).obj
val outJson = JsonParser.parseString(
client.newCall(
Request.Builder()
.url(EH_API_BASE)
.post(RequestBody.create(JSON, json.toString()))
.build()
).execute().body!!.string()
).obj
val obj = outJson["tokenlist"].array.first()
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
@@ -742,16 +800,16 @@ class EHentai(
private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!
private val FAVORITES_BORDER_HEX_COLORS = listOf(
"000",
"f00",
"fa0",
"dd0",
"080",
"9f4",
"4bf",
"00f",
"508",
"e8e"
"000",
"f00",
"fa0",
"dd0",
"080",
"9f4",
"4bf",
"00f",
"508",
"e8e"
)
fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") {
@@ -65,7 +65,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
private fun tagIndexVersion(): Single<Long> {
val sCachedTagIndexVersion = cachedTagIndexVersion
return if (sCachedTagIndexVersion == null ||
tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) {
tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()
) {
HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext {
cachedTagIndexVersion = it
tagIndexVersionCacheTime = System.currentTimeMillis()
@@ -80,7 +81,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
private fun galleryIndexVersion(): Single<Long> {
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
return if (sCachedGalleryIndexVersion == null ||
galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) {
galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()
) {
HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext {
cachedGalleryIndexVersion = it
galleryIndexVersionCacheTime = System.currentTimeMillis()
@@ -162,9 +164,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
* @param page the page number to retrieve.
*/
override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet(
"$LTN_BASE_URL/popular-all.nozomi",
100L * (page - 1),
99L + 100 * (page - 1)
"$LTN_BASE_URL/popular-all.nozomi",
100L * (page - 1),
99L + 100 * (page - 1)
)
/**
@@ -192,7 +194,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
// TODO Cache the results coming out of HitomiNozomi
val hn = Single.zip(tagIndexVersion(), galleryIndexVersion()) { tv, gv -> tv to gv }
.map { HitomiNozomi(client, it.first, it.second) }
.map { HitomiNozomi(client, it.first, it.second) }
var base = if (positive.isEmpty()) {
hn.flatMap { n -> n.getGalleryIdsFromNozomi(null, "index", "all").map { n to it.toSet() } }
@@ -240,9 +242,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
* @param page the page number to retrieve.
*/
override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet(
"$LTN_BASE_URL/index-all.nozomi",
100L * (page - 1),
99L + 100 * (page - 1)
"$LTN_BASE_URL/index-all.nozomi",
100L * (page - 1),
99L + 100 * (page - 1)
)
/**
@@ -254,14 +256,14 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page))
.asObservableSuccess()
.flatMap { responseToMangas(it) }
.asObservableSuccess()
.flatMap { responseToMangas(it) }
}
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess()
.flatMap { responseToMangas(it) }
.asObservableSuccess()
.flatMap { responseToMangas(it) }
}
fun responseToMangas(response: Response): Observable<MangasPage> {
@@ -270,9 +272,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
val end = range.substringBefore('/').substringAfter('-').toLong()
val body = response.body!!
return parseNozomiPage(body.bytes())
.map {
MangasPage(it, end < total - 1)
}
.map {
MangasPage(it, end < total - 1)
}
}
private fun parseNozomiPage(array: ByteArray): Observable<List<SManga>> {
@@ -285,13 +287,15 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
}
private fun nozomiIdsToMangas(ids: List<Int>): Single<List<SManga>> {
return Single.zip(ids.map {
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
return Single.zip(
ids.map {
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
.asObservableSuccess()
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
.map { parseGalleryBlock(it) }
.toSingle()
}) { it.map { m -> m as SManga } }
}
) { it.map { m -> m as SManga } }
}
private fun parseGalleryBlock(response: Response): SManga {
@@ -318,23 +322,27 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
initialized = true
}))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(
Observable.just(
manga.apply {
initialized = true
}
)
)
}
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return Observable.just(
listOf(
SChapter.create().apply {
url = manga.url
name = "Chapter"
chapter_number = 0.0f
}
)
listOf(
SChapter.create().apply {
url = manga.url
name = "Chapter"
chapter_number = 0.0f
}
)
)
}
@@ -372,9 +380,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
val hashPath1 = hash.takeLast(1)
val hashPath2 = hash.takeLast(3).take(2)
Page(
index,
"",
"https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext"
index,
"",
"https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext"
)
}
}
@@ -396,19 +404,20 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
it[it.lastIndex - 1]
}
return request.newBuilder()
.header("Referer", "$BASE_URL/reader/$hlId.html")
.build()
.header("Referer", "$BASE_URL/reader/$hlId.html")
.build()
}
override val matchingHosts = listOf(
"hitomi.la"
"hitomi.la"
)
override fun mapUrlToMangaUrl(uri: Uri): String? {
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader")
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader") {
return null
}
return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html"
}
@@ -419,10 +428,11 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
private val NUMBER_OF_FRONTENDS = 2
private val DATE_FORMAT by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.US)
else
} else {
SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US)
}
}
}
}
@@ -80,7 +80,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
}
val sortFilter = filters.filterIsInstance<SortFilter>().firstOrNull()?.state
?: defaultSortFilterSelection()
?: defaultSortFilterSelection()
if (sortFilter.index == 1) {
if (query.isBlank()) error("You must specify a search query if you wish to sort by popularity!")
@@ -89,22 +89,22 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
if (sortFilter.ascending) {
return client.newCall(nhGet(uri.toString()))
.asObservableSuccess()
.map {
val doc = it.asJsoup()
.asObservableSuccess()
.map {
val doc = it.asJsoup()
val lastPage = doc.selectFirst(".last")
?.attr("href")
?.substringAfterLast('=')
?.toIntOrNull() ?: 1
val lastPage = doc.selectFirst(".last")
?.attr("href")
?.substringAfterLast('=')
?.toIntOrNull() ?: 1
val thisPage = lastPage - (page - 1)
val thisPage = lastPage - (page - 1)
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
uri.appendQueryParameter("page", thisPage.toString())
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
uri.appendQueryParameter("page", thisPage.toString())
nhGet(uri.toString(), page)
}
nhGet(uri.toString(), page)
}
}
uri.appendQueryParameter("page", page.toString())
@@ -134,12 +134,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it).andThen(Observable.just(manga.apply {
initialized = true
}))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it).andThen(
Observable.just(
manga.apply {
initialized = true
}
)
)
}
}
override fun mangaDetailsRequest(manga: SManga) = nhGet(baseUrl + manga.url)
@@ -208,31 +212,38 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
}?.apply {
tags.clear()
}?.forEach {
if (it.first != null && it.second != null)
if (it.first != null && it.second != null) {
tags.add(RaisedTag(it.first!!, it.second!!, TAG_TYPE_DEFAULT))
}
}
}
}
fun getOrLoadMetadata(mangaId: Long?, nhId: Long) = getOrLoadMetadata(mangaId) {
client.newCall(nhGet(baseUrl + NHentaiSearchMetadata.nhIdToPath(nhId)))
.asObservableSuccess()
.toSingle()
.asObservableSuccess()
.toSingle()
}
override fun fetchChapterList(manga: SManga) = Observable.just(listOf(SChapter.create().apply {
url = manga.url
name = "Chapter"
chapter_number = 1f
}))
override fun fetchChapterList(manga: SManga) = Observable.just(
listOf(
SChapter.create().apply {
url = manga.url
name = "Chapter"
chapter_number = 1f
}
)
)
override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
if (metadata.mediaId == null) emptyList()
else
if (metadata.mediaId == null) {
emptyList()
} else {
metadata.pageImageTypes.mapIndexed { index, s ->
val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
Page(index, imageUrl!!, imageUrl)
}
}
}.toObservable()
override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!!
@@ -259,9 +270,9 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
private class filterLang : Filter.Select<String>("Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray())
class SortFilter : Filter.Sort(
"Sort",
arrayOf("Date", "Popular"),
defaultSortFilterSelection()
"Sort",
arrayOf("Date", "Popular"),
defaultSortFilterSelection()
)
val appName by lazy {
@@ -269,14 +280,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
}
fun nhGet(url: String, tag: Any? = null) = GET(url)
.newBuilder()
.header("User-Agent",
"Mozilla/5.0 (X11; Linux x86_64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/56.0.2924.87 " +
"Safari/537.36 " +
"$appName/${BuildConfig.VERSION_CODE}")
.tag(tag).build()
.newBuilder()
.header(
"User-Agent",
"Mozilla/5.0 (X11; Linux x86_64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/56.0.2924.87 " +
"Safari/537.36 " +
"$appName/${BuildConfig.VERSION_CODE}"
)
.tag(tag).build()
override val id = NHENTAI_SOURCE_ID
@@ -291,12 +304,13 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
// === URL IMPORT STUFF
override val matchingHosts = listOf(
"nhentai.net"
"nhentai.net"
)
override fun mapUrlToMangaUrl(uri: Uri): String? {
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g")
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g") {
return null
}
return "$baseUrl/g/${uri.pathSegments[1]}/"
}
@@ -308,10 +322,10 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
private fun defaultSortFilterSelection() = Filter.Sort.Selection(0, false)
private val SOURCE_LANG_LIST = listOf(
Pair("All", ""),
Pair("English", " english"),
Pair("Japanese", " japanese"),
Pair("Chinese", " chinese")
Pair("All", ""),
Pair("English", " english"),
Pair("Japanese", " japanese"),
Pair("Chinese", " chinese")
)
}
}
@@ -33,8 +33,10 @@ import org.jsoup.nodes.TextNode
import rx.Observable
// TODO Transform into delegated source
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
LewdSource<PervEdenSearchMetadata, Document>, UrlImportableSource {
class PervEden(override val id: Long, val pvLang: PervEdenLang) :
ParsedHttpSource(),
LewdSource<PervEdenSearchMetadata, Document>,
UrlImportableSource {
/**
* The class of the metadata used by this source
*/
@@ -62,9 +64,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(query) {
super.fetchSearchManga(page, query, filters)
}
urlImportFetchSearchManga(query) {
super.fetchSearchManga(page, query, filters)
}
override fun searchMangaSelector() = "#mangaList > tbody > tr"
@@ -79,9 +81,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
override fun searchMangaNextPageSelector() = ".next"
override fun popularMangaRequest(page: Int): Request {
val urlLang = if (lang == "en")
val urlLang = if (lang == "en") {
"eng"
else "it"
} else {
"it"
}
return GET("$baseUrl/$urlLang/")
}
@@ -129,12 +133,16 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
initialized = true
}))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(
Observable.just(
manga.apply {
initialized = true
}
)
)
}
}
/**
@@ -165,8 +173,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
"Alternative name(s)" -> {
if (it is TextNode) {
val text = it.text().trim()
if (!text.isBlank())
if (!text.isBlank()) {
newAltTitles += text
}
}
}
"Artist" -> {
@@ -176,21 +185,24 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
}
}
"Genres" -> {
if (it is Element && it.tagName() == "a")
if (it is Element && it.tagName() == "a") {
tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT)
}
}
"Type" -> {
if (it is TextNode) {
val text = it.text().trim()
if (!text.isBlank())
if (!text.isBlank()) {
type = text
}
}
}
"Status" -> {
if (it is TextNode) {
val text = it.text().trim()
if (!text.isBlank())
if (!text.isBlank()) {
status = text
}
}
}
}
@@ -224,10 +236,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
name = "Chapter " + linkElement.getElementsByTag("b").text()
ChapterRecognition.parseChapterNumber(
this,
SManga.create().apply {
title = ""
})
this,
SManga.create().apply {
title = ""
}
)
try {
date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim())!!.time
@@ -242,37 +255,49 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
override fun imageUrlParse(document: Document) = "http:" + document.getElementById("mainImg").attr("src")!!
override fun getFilterList() = FilterList(
AuthorFilter(),
ArtistFilter(),
TypeFilterGroup(),
ReleaseYearGroup(),
StatusFilterGroup()
AuthorFilter(),
ArtistFilter(),
TypeFilterGroup(),
ReleaseYearGroup(),
StatusFilterGroup()
)
class StatusFilterGroup : UriGroup<StatusFilter>("Status", listOf(
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)
if (state) {
builder.appendQueryParameter("status", id.toString())
}
}
}
// Explicit type arg for listOf() to workaround this: KT-16570
class ReleaseYearGroup : UriGroup<Filter<*>>("Release Year", listOf(
class ReleaseYearGroup : UriGroup<Filter<*>>(
"Release Year",
listOf(
ReleaseYearRangeFilter(),
ReleaseYearYearFilter()
))
)
)
class ReleaseYearRangeFilter : Filter.Select<String>("Range", arrayOf(
"on",
"after",
"before"
)), UriFilter {
class ReleaseYearRangeFilter :
Filter.Select<String>(
"Range",
arrayOf(
"on",
"after",
"before"
)
),
UriFilter {
override fun addToUri(builder: Uri.Builder) {
builder.appendQueryParameter("releasedType", state.toString())
}
@@ -296,18 +321,22 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
}
}
class TypeFilterGroup : UriGroup<TypeFilter>("Type", listOf(
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)
if (state) {
builder.appendQueryParameter("type", id.toString())
}
}
}
@@ -41,9 +41,10 @@ import rx.schedulers.Schedulers
typealias SiteMap = NakedTrie<Unit>
class EightMuses : HttpSource(),
LewdSource<EightMusesSearchMetadata, Document>,
UrlImportableSource {
class EightMuses :
HttpSource(),
LewdSource<EightMusesSearchMetadata, Document>,
UrlImportableSource {
override val id = EIGHTMUSES_SOURCE_ID
/**
@@ -74,10 +75,10 @@ class EightMuses : HttpSource(),
private suspend fun obtainSiteMap() = siteMapCache.obtain {
withContext(Dispatchers.IO) {
val result = client.newCall(eightMusesGet("$baseUrl/sitemap/1.xml"))
.asObservableSuccess()
.toSingle()
.await(Schedulers.io())
.body!!.string()
.asObservableSuccess()
.toSingle()
.await(Schedulers.io())
.body!!.string()
val parsed = Jsoup.parse(result)
@@ -93,10 +94,10 @@ class EightMuses : HttpSource(),
override fun headersBuilder(): Headers.Builder {
return Headers.Builder()
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;")
.add("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8")
.add("Referer", "https://www.8muses.com")
.add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36")
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;")
.add("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8")
.add("Referer", "https://www.8muses.com")
.add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36")
}
private fun eightMusesGet(url: String): Request {
@@ -129,11 +130,11 @@ class EightMuses : HttpSource(),
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val urlBuilder = if (!query.isBlank()) {
"$baseUrl/search".toHttpUrlOrNull()!!
.newBuilder()
.addQueryParameter("q", query)
.newBuilder()
.addQueryParameter("q", query)
} else {
"$baseUrl/comics".toHttpUrlOrNull()!!
.newBuilder()
.newBuilder()
}
urlBuilder.addQueryParameter("page", page.toString())
@@ -182,12 +183,14 @@ class EightMuses : HttpSource(),
private fun fetchListing(request: Request, dig: Boolean): Observable<MangasPage> {
return client.newCall(request)
.asObservableSuccess()
.flatMapSingle { response ->
RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
.asObservableSuccess()
.flatMapSingle { response ->
RxJavaInterop.toV1Single(
GlobalScope.async(Dispatchers.IO) {
parseResultsPage(response, dig)
}.asSingle(GlobalScope.coroutineContext))
}
}.asSingle(GlobalScope.coroutineContext)
)
}
}
private suspend fun parseResultsPage(response: Response, dig: Boolean): MangasPage {
@@ -197,32 +200,32 @@ class EightMuses : HttpSource(),
val onLastPage = doc.selectFirst(".current:nth-last-child(2)") != null
return MangasPage(
if (dig) {
contents.albums.flatMap {
val href = it.attr("href")
val splitHref = href.split('/')
obtainSiteMap().subMap(href).filter {
it.key.split('/').size - splitHref.size == 1
}.map { (key, _) ->
SManga.create().apply {
url = key
title = key.substringAfterLast('/').replace('-', ' ')
}
}
}
} else {
contents.albums.map {
if (dig) {
contents.albums.flatMap {
val href = it.attr("href")
val splitHref = href.split('/')
obtainSiteMap().subMap(href).filter {
it.key.split('/').size - splitHref.size == 1
}.map { (key, _) ->
SManga.create().apply {
url = it.attr("href")
url = key
title = it.select(".title-text").text()
thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src")
title = key.substringAfterLast('/').replace('-', ' ')
}
}
},
!onLastPage
}
} else {
contents.albums.map {
SManga.create().apply {
url = it.attr("href")
title = it.select(".title-text").text()
thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src")
}
}
},
!onLastPage
)
}
@@ -243,10 +246,10 @@ class EightMuses : HttpSource(),
*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
}
}
/**
@@ -259,9 +262,11 @@ class EightMuses : HttpSource(),
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
fetchAndParseChapterList("", manga.url)
}.asSingle(GlobalScope.coroutineContext)).toObservable()
return RxJavaInterop.toV1Single(
GlobalScope.async(Dispatchers.IO) {
fetchAndParseChapterList("", manga.url)
}.asSingle(GlobalScope.coroutineContext)
).toObservable()
}
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
@@ -309,9 +314,9 @@ class EightMuses : HttpSource(),
val contents = parseSelf(response.asJsoup())
return contents.images.mapIndexed { index, element ->
Page(
index,
element.attr("href"),
"$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9)
index,
element.attr("href"),
"$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9)
)
}
}
@@ -325,30 +330,30 @@ class EightMuses : HttpSource(),
title = breadcrumbs.selectFirst("li:nth-last-child(1) > a").text()
thumbnailUrl = parseSelf(input).let { it.albums + it.images }.firstOrNull()
?.selectFirst(".lazyload")
?.attr("data-src")?.let {
baseUrl + it
}
?.selectFirst(".lazyload")
?.attr("data-src")?.let {
baseUrl + it
}
tags.clear()
tags += RaisedTag(
EightMusesSearchMetadata.ARTIST_NAMESPACE,
breadcrumbs.selectFirst("li:nth-child(2) > a").text(),
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
EightMusesSearchMetadata.ARTIST_NAMESPACE,
breadcrumbs.selectFirst("li:nth-child(2) > a").text(),
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
)
tags += input.select(".album-tags a").map {
RaisedTag(
EightMusesSearchMetadata.TAGS_NAMESPACE,
it.text(),
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
EightMusesSearchMetadata.TAGS_NAMESPACE,
it.text(),
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
)
}
}
}
class SortFilter : Filter.Select<String>(
"Sort",
SORT_OPTIONS.map { it.second }.toTypedArray()
"Sort",
SORT_OPTIONS.map { it.second }.toTypedArray()
) {
fun addToUri(url: HttpUrl.Builder) {
url.addQueryParameter("sort", SORT_OPTIONS[state].first)
@@ -357,16 +362,16 @@ class EightMuses : HttpSource(),
companion object {
// <Internal, Display>
private val SORT_OPTIONS = listOf(
"" to "Views",
"like" to "Likes",
"date" to "Date",
"az" to "A-Z"
"" to "Views",
"like" to "Likes",
"date" to "Date",
"az" to "A-Z"
)
}
}
override fun getFilterList() = FilterList(
SortFilter()
SortFilter()
)
/**
@@ -379,8 +384,8 @@ class EightMuses : HttpSource(),
}
override val matchingHosts = listOf(
"www.8muses.com",
"8muses.com"
"www.8muses.com",
"8muses.com"
)
override fun mapUrlToMangaUrl(uri: Uri): String? {
File diff suppressed because it is too large Load Diff
@@ -19,8 +19,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.nodes.Document
import rx.Observable
class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
LewdSource<HentaiCafeSearchMetadata, Document>, UrlImportableSource {
class HentaiCafe(delegate: HttpSource) :
DelegatedHttpSource(delegate),
LewdSource<HentaiCafeSearchMetadata, Document>,
UrlImportableSource {
/**
* An ISO 639-1 compliant language code (two letters in lower case).
*/
@@ -32,18 +34,22 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(query) {
super.fetchSearchManga(page, query, filters)
}
urlImportFetchSearchManga(query) {
super.fetchSearchManga(page, query, filters)
}
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
initialized = true
}))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(
Observable.just(
manga.apply {
initialized = true
}
)
)
}
}
/**
@@ -57,8 +63,8 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
thumbnailUrl = contentElement.child(0).child(0).attr("src")
fun filterableTagsOfType(type: String) = contentElement.select("a")
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
.map { it.text() }
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
.map { it.text() }
tags.clear()
tags += filterableTagsOfType("tag").map {
@@ -78,29 +84,30 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
override fun fetchChapterList(manga: SManga) = getOrLoadMetadata(manga.id) {
client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.map { it.asJsoup() }
.toSingle()
.asObservableSuccess()
.map { it.asJsoup() }
.toSingle()
}.map {
listOf(
SChapter.create().apply {
setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/")
name = "Chapter"
chapter_number = 0.0f
}
SChapter.create().apply {
setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/")
name = "Chapter"
chapter_number = 0.0f
}
)
}.toObservable()
override val matchingHosts = listOf(
"hentai.cafe"
"hentai.cafe"
)
override fun mapUrlToMangaUrl(uri: Uri): String? {
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
return if (lcFirstPathSegment == "manga")
return if (lcFirstPathSegment == "manga") {
"https://hentai.cafe/${uri.pathSegments[2]}"
else
} else {
"https://hentai.cafe/$lcFirstPathSegment"
}
}
}
@@ -18,8 +18,10 @@ import exh.util.urlImportFetchSearchManga
import org.jsoup.nodes.Document
import rx.Observable
class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
LewdSource<PururinSearchMetadata, Document>, UrlImportableSource {
class Pururin(delegate: HttpSource) :
DelegatedHttpSource(delegate),
LewdSource<PururinSearchMetadata, Document>,
UrlImportableSource {
/**
* An ISO 639-1 compliant language code (two letters in lower case).
*/
@@ -43,11 +45,11 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup())
.andThen(Observable.just(manga))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup())
.andThen(Observable.just(manga))
}
}
override fun parseIntoMetadata(metadata: PururinSearchMetadata, input: Document) {
@@ -87,9 +89,9 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
value.select("a").forEach { link ->
val searchUrl = Uri.parse(link.attr("href"))
tags += RaisedTag(
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
searchUrl.lastPathSegment!!.substringBefore("."),
PururinSearchMetadata.TAG_TYPE_DEFAULT
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
searchUrl.lastPathSegment!!.substringBefore("."),
PururinSearchMetadata.TAG_TYPE_DEFAULT
)
}
}
@@ -99,8 +101,8 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
}
override val matchingHosts = listOf(
"pururin.io",
"www.pururin.io"
"pururin.io",
"www.pururin.io"
)
override fun mapUrlToMangaUrl(uri: Uri): String? {
@@ -19,30 +19,33 @@ import java.util.Locale
import org.jsoup.nodes.Document
import rx.Observable
class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
LewdSource<TsuminoSearchMetadata, Document>, UrlImportableSource {
class Tsumino(delegate: HttpSource) :
DelegatedHttpSource(delegate),
LewdSource<TsuminoSearchMetadata, Document>,
UrlImportableSource {
override val metaClass = TsuminoSearchMetadata::class
override val lang = "en"
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(query) {
super.fetchSearchManga(page, query, filters)
}
urlImportFetchSearchManga(query) {
super.fetchSearchManga(page, query, filters)
}
override fun mapUrlToMangaUrl(uri: Uri): String? {
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry")
if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry") {
return null
}
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
}
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
}
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
}
}
override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) {
@@ -106,16 +109,18 @@ class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
character = newCharacter
input.getElementById("Tag")?.children()?.let {
tags.addAll(it.map {
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
})
tags.addAll(
it.map {
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
}
)
}
}
}
override val matchingHosts = listOf(
"www.tsumino.com",
"tsumino.com"
"www.tsumino.com",
"tsumino.com"
)
companion object {
@@ -131,10 +131,14 @@ class SourceController :
// Open the catalogue view.
openCatalogue(source, BrowseSourceController(source))
}
Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply {
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
}).withFadeTransaction())
Mode.SMART_SEARCH -> router.pushController(
SmartSearchController(
Bundle().apply {
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
}
).withFadeTransaction()
)
}
return false
}
@@ -413,9 +413,9 @@ open class BrowseSourcePresenter(
}
val newSerialized = searches.map {
"${source.id}:" + jsonObject(
"name" to it.name,
"query" to it.query,
"filters" to filterSerializer.serialize(it.filterList)
"name" to it.name,
"query" to it.query,
"filters" to filterSerializer.serialize(it.filterList)
).toString()
}
prefs.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
@@ -430,9 +430,11 @@ open class BrowseSourcePresenter(
val content = JsonParser.parseString(it.substringAfter(':')).obj
val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content["filters"].array)
EXHSavedSearch(content["name"].string,
content["query"].string,
originalFilters)
EXHSavedSearch(
content["name"].string,
content["query"].string,
originalFilters
)
} catch (t: RuntimeException) {
// Load failed
Timber.e(t, "Failed to load saved search!")
@@ -393,7 +393,6 @@ class LibraryPresenter(
manga: Manga,
replace: Boolean
) {
val flags = preferences.migrateFlags().get()
val migrateChapters = MigrationFlags.hasChapters(flags)
val migrateCategories = MigrationFlags.hasCategories(flags)
@@ -181,29 +181,34 @@ class MangaInfoPresenter(
suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga {
val originalManga = db.getManga(originalMangaId).await()
?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId")
?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId")
val toInsert = if (originalManga.source == MERGED_SOURCE_ID) {
originalManga.apply {
val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children
if (originalChildren.any { it.source == manga.source && it.url == manga.url })
if (originalChildren.any { it.source == manga.source && it.url == manga.url }) {
throw IllegalArgumentException("This manga is already merged with the current manga!")
}
url = MergedSource.MangaConfig(originalChildren + MergedSource.MangaSource(
url = MergedSource.MangaConfig(
originalChildren + MergedSource.MangaSource(
manga.source,
manga.url
)).writeAsUrl(gson)
)
).writeAsUrl(gson)
}
} else {
val newMangaConfig = MergedSource.MangaConfig(listOf(
val newMangaConfig = MergedSource.MangaConfig(
listOf(
MergedSource.MangaSource(
originalManga.source,
originalManga.url
originalManga.source,
originalManga.url
),
MergedSource.MangaSource(
manga.source,
manga.url
manga.source,
manga.url
)
))
)
)
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
copyFrom(originalManga)
favorite = true
@@ -23,17 +23,22 @@ class MigrationMangaDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
val confirmString = applicationContext?.resources?.getQuantityString(confirmRes, mangaSet,
mangaSet, (
val confirmString = applicationContext?.resources?.getQuantityString(
confirmRes, mangaSet,
mangaSet,
(
if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
else "")) ?: ""
else ""
)
) ?: ""
return MaterialDialog(activity!!)
.message(text = confirmString)
.positiveButton(if (copy) R.string.copy else R.string.migrate) {
if (copy)
if (copy) {
(targetController as? MigrationListController)?.copyMangas()
else
} else {
(targetController as? MigrationListController)?.migrateMangas()
}
}
.negativeButton(android.R.string.no)
}
@@ -31,10 +31,12 @@ class MigrationBottomSheetDialog(
activity: Activity,
theme: Int,
private val listener:
StartMigrationListener
StartMigrationListener
) :
BottomSheetDialog(activity,
theme) {
BottomSheetDialog(
activity,
theme
) {
/**
* Preferences helper.
*/
@@ -47,8 +49,9 @@ class MigrationBottomSheetDialog(
// scroll.addView(view)
setContentView(view)
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE)
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) {
sourceGroup.orientation = LinearLayout.HORIZONTAL
}
window?.setBackgroundDrawable(null)
}
@@ -63,8 +66,10 @@ class MigrationBottomSheetDialog(
fab.setOnClickListener {
preferences.skipPreMigration().set(skip_step.isChecked)
listener.startMigration(
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank())
extra_search_param_text.text.toString() else null)
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank()) {
extra_search_param_text.text.toString()
} else null
)
dismiss()
}
}
@@ -96,9 +101,12 @@ class MigrationBottomSheetDialog(
skip_step.isChecked = preferences.skipPreMigration().get()
skip_step.setOnCheckedChangeListener { _, isChecked ->
if (isChecked)
(listener as? Controller)?.activity?.toast(R.string.pre_migration_skip_toast,
Toast.LENGTH_LONG)
if (isChecked) {
(listener as? Controller)?.activity?.toast(
R.string.pre_migration_skip_toast,
Toast.LENGTH_LONG
)
}
}
}
@@ -9,16 +9,21 @@ class MigrationSourceAdapter(
var items: List<MigrationSourceItem>,
val controllerPre: PreMigrationController
) : FlexibleAdapter<MigrationSourceItem>(
items,
controllerPre,
true
items,
controllerPre,
true
) {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelableArrayList(SELECTED_SOURCES_KEY, ArrayList(currentItems.map {
it.asParcelable()
}))
outState.putParcelableArrayList(
SELECTED_SOURCES_KEY,
ArrayList(
currentItems.map {
it.asParcelable()
}
)
)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
@@ -66,8 +66,8 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) :
val source = sourceManager.get(si.sourceId) as? HttpSource ?: return null
return MigrationSourceItem(
source,
si.sourceEnabled
source,
si.sourceEnabled
)
}
}
@@ -27,8 +27,11 @@ import exh.util.updateLayoutParams
import exh.util.updatePaddingRelative
import uy.kohesive.injekt.injectLazy
class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrationControllerBinding>(bundle), FlexibleAdapter
.OnItemClickListener, StartMigrationListener {
class PreMigrationController(bundle: Bundle? = null) :
BaseController<PreMigrationControllerBinding>(bundle),
FlexibleAdapter
.OnItemClickListener,
StartMigrationListener {
private val sourceManager: SourceManager by injectLazy()
private val prefs: PreferencesHelper by injectLazy()
@@ -69,8 +72,10 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
}
// offset the recycler by the fab's inset + some inset on top
v.updatePaddingRelative(bottom = padding.bottom + (binding.fab.marginBottom) +
fabBaseMarginBottom + (binding.fab.height))
v.updatePaddingRelative(
bottom = padding.bottom + (binding.fab.marginBottom) +
fabBaseMarginBottom + (binding.fab.height)
)
}
binding.fab.setOnClickListener {
@@ -101,7 +106,8 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
config.toList(),
extraSearchParams = extraParam
)
).withFadeTransaction().tag(MigrationListController.TAG))
).withFadeTransaction().tag(MigrationListController.TAG)
)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -136,10 +142,13 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
.filter { it.lang in languages }
.sortedBy { "(${it.lang}) ${it.name}" }
sources =
sources.filter { isEnabled(it.id.toString()) }.sortedBy { sourcesSaved.indexOf(it.id
.toString())
} +
sources.filterNot { isEnabled(it.id.toString()) }
sources.filter { isEnabled(it.id.toString()) }.sortedBy {
sourcesSaved.indexOf(
it.id
.toString()
)
} +
sources.filterNot { isEnabled(it.id.toString()) }
return sources
}
@@ -167,9 +176,11 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
}
fun create(mangaIds: List<Long>): PreMigrationController {
return PreMigrationController(Bundle().apply {
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
})
return PreMigrationController(
Bundle().apply {
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
}
)
}
}
}
@@ -56,8 +56,10 @@ import rx.schedulers.Schedulers
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
class MigrationListController(bundle: Bundle? = null) : BaseController<MigrationListControllerBinding>(bundle),
MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope {
class MigrationListController(bundle: Bundle? = null) :
BaseController<MigrationListControllerBinding>(bundle),
MigrationProcessAdapter.MigrationProcessInterface,
CoroutineScope {
init {
setHasOptionsMenu(true)
@@ -93,7 +95,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
setTitle()
@@ -217,7 +218,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
val chapters = try {
source.fetchChapterList(localManga)
.toSingle().await(Schedulers.io()) } catch (e: java.lang.Exception) {
.toSingle().await(Schedulers.io())
} catch (e: java.lang.Exception) {
Timber.e(e)
emptyList<SChapter>()
} ?: emptyList()
@@ -313,7 +315,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
}
override fun onMenuItemClick(position: Int, item: MenuItem) {
when (item.itemId) {
R.id.action_search_manually -> {
launchUI {
@@ -488,9 +489,11 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
const val TAG = "migration_list"
fun create(config: MigrationProcedureConfig): MigrationListController {
return MigrationListController(Bundle().apply {
putParcelable(CONFIG_EXTRA, config)
})
return MigrationListController(
Bundle().apply {
putParcelable(CONFIG_EXTRA, config)
}
)
}
}
}
@@ -42,8 +42,12 @@ class MigrationProcessAdapter(
if (allMangasDone()) menuItemListener.enableButtons()
}
fun allMangasDone() = (items.all { it.manga.migrationStatus != MigrationStatus
.RUNNUNG } && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND })
fun allMangasDone() = (
items.all {
it.manga.migrationStatus != MigrationStatus
.RUNNUNG
} && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND }
)
fun mangasSkipped() = (items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND })
@@ -59,7 +63,8 @@ class MigrationProcessAdapter(
migrateMangaInternal(
manga.manga() ?: return@forEach,
toMangaObj,
!copy)
!copy
)
}
}
}
@@ -48,10 +48,18 @@ class MigrationProcessHolder(
val manga = item.manga.manga()
val source = item.manga.mangaSource()
migration_menu.setVectorCompat(R.drawable.ic_more_vert_24dp, view.context
.getResourceColor(R.attr.colorOnPrimary))
skip_manga.setVectorCompat(R.drawable.ic_close_24dp, view.context.getResourceColor(R
.attr.colorOnPrimary))
migration_menu.setVectorCompat(
R.drawable.ic_more_vert_24dp,
view.context
.getResourceColor(R.attr.colorOnPrimary)
)
skip_manga.setVectorCompat(
R.drawable.ic_close_24dp,
view.context.getResourceColor(
R
.attr.colorOnPrimary
)
)
migration_menu.invisible()
skip_manga.visible()
migration_manga_card_to.resetManga()
@@ -87,7 +95,8 @@ class MigrationProcessHolder(
}
withContext(Dispatchers.Main) {
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
item.manga.migrationStatus == MigrationStatus.RUNNUNG) {
item.manga.migrationStatus == MigrationStatus.RUNNUNG
) {
return@withContext
}
if (searchResult != null && resultSource != null) {
@@ -152,11 +161,15 @@ class MigrationProcessHolder(
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
if (latestChapter > 0f) {
manga_last_chapter_label.text = context.getString(R.string.latest_,
DecimalFormat("#.#").format(latestChapter))
manga_last_chapter_label.text = context.getString(
R.string.latest_,
DecimalFormat("#.#").format(latestChapter)
)
} else {
manga_last_chapter_label.text = context.getString(R.string.latest_,
context.getString(R.string.unknown))
manga_last_chapter_label.text = context.getString(
R.string.latest_,
context.getString(R.string.unknown)
)
}
}
@@ -63,13 +63,13 @@ class SettingsEhController : SettingsController() {
private fun Preference<*>.reconfigure(): Boolean {
// Listen for change commit
asObservable()
.skip(1) // Skip first as it is emitted immediately
.take(1) // Only listen for first commit
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
// Only listen for first change commit
WarnConfigureDialogController.uploadSettings(router)
}
.skip(1) // Skip first as it is emitted immediately
.take(1) // Only listen for first commit
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
// Only listen for first change commit
WarnConfigureDialogController.uploadSettings(router)
}
// Always return true to save changes
return true
@@ -85,12 +85,12 @@ class SettingsEhController : SettingsController() {
isPersistent = false
defaultValue = false
preferences.enableExhentai()
.asObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
isChecked = it
}
.asObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
isChecked = it
}
onChange { newVal ->
newVal as Boolean
@@ -98,9 +98,11 @@ class SettingsEhController : SettingsController() {
preferences.enableExhentai().set(false)
true
} else {
router.pushController(RouterTransaction.with(LoginController())
router.pushController(
RouterTransaction.with(LoginController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler()))
.popChangeHandler(FadeChangeHandler())
)
false
}
}
@@ -148,20 +150,20 @@ class SettingsEhController : SettingsController() {
summary = "The quality of the downloaded images"
title = "Image quality"
entries = arrayOf(
"Auto",
"2400x",
"1600x",
"1280x",
"980x",
"780x"
"Auto",
"2400x",
"1600x",
"1280x",
"980x",
"780x"
)
entryValues = arrayOf(
"auto",
"ovrs_2400",
"ovrs_1600",
"high",
"med",
"low"
"auto",
"ovrs_2400",
"ovrs_1600",
"high",
"med",
"low"
)
onChange { preferences.imageQuality().reconfigure() }
@@ -202,21 +204,21 @@ class SettingsEhController : SettingsController() {
onClick {
activity?.let { activity ->
MaterialDialog(activity)
.title(R.string.eh_force_sync_reset_title)
.message(R.string.eh_force_sync_reset_message)
.positiveButton(android.R.string.yes) {
LocalFavoritesStorage().apply {
getRealm().use {
it.trans {
clearSnapshots(it)
}
.title(R.string.eh_force_sync_reset_title)
.message(R.string.eh_force_sync_reset_message)
.positiveButton(android.R.string.yes) {
LocalFavoritesStorage().apply {
getRealm().use {
it.trans {
clearSnapshots(it)
}
}
activity.toast("Sync state reset", Toast.LENGTH_LONG)
}
.negativeButton(android.R.string.no)
.cancelable(false)
.show()
activity.toast("Sync state reset", Toast.LENGTH_LONG)
}
.negativeButton(android.R.string.no)
.cancelable(false)
.show()
}
}
}
@@ -311,18 +313,18 @@ class SettingsEhController : SettingsController() {
}
"""
$statsText
$statsText
Galleries that were checked in the last:
- hour: ${metaInRelativeDuration(1.hours)}
- 6 hours: ${metaInRelativeDuration(6.hours)}
- 12 hours: ${metaInRelativeDuration(12.hours)}
- day: ${metaInRelativeDuration(1.days)}
- 2 days: ${metaInRelativeDuration(2.days)}
- week: ${metaInRelativeDuration(7.days)}
- month: ${metaInRelativeDuration(30.days)}
- year: ${metaInRelativeDuration(365.days)}
""".trimIndent()
Galleries that were checked in the last:
- hour: ${metaInRelativeDuration(1.hours)}
- 6 hours: ${metaInRelativeDuration(6.hours)}
- 12 hours: ${metaInRelativeDuration(12.hours)}
- day: ${metaInRelativeDuration(1.days)}
- 2 days: ${metaInRelativeDuration(2.days)}
- week: ${metaInRelativeDuration(7.days)}
- month: ${metaInRelativeDuration(30.days)}
- year: ${metaInRelativeDuration(365.days)}
""".trimIndent()
} finally {
progress.dismiss()
}
@@ -75,10 +75,12 @@ class SettingsLibraryController : SettingsController() {
intListPreference {
key = Keys.eh_library_rounded_corners
title = "Rounded Corner Radius"
entriesRes = arrayOf(R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1,
entriesRes = arrayOf(
R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1,
R.string.eh_rounded_corner_2, R.string.eh_rounded_corner_3, R.string.eh_rounded_corner_4,
R.string.eh_rounded_corner_5, R.string.eh_rounded_corner_6, R.string.eh_rounded_corner_7,
R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10)
R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10
)
entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
defaultValue = "4"
summaryRes = R.string.eh_rounded_corners_desc
@@ -211,7 +213,8 @@ class SettingsLibraryController : SettingsController() {
}
}
if (preferences.skipPreMigration().get() || preferences.migrationSources()
.getOrDefault().isNotEmpty()) {
.getOrDefault().isNotEmpty()
) {
switchPreference {
key = Keys.skipPreMigration
titleRes = R.string.pref_skip_pre_migration