Linting Fixes AZ
This commit is contained in:
parent
03e5c5ca10
commit
7e99a9f789
@ -83,10 +83,12 @@ class ChapterCache(private val context: Context) {
|
|||||||
// --> EH
|
// --> EH
|
||||||
// Cache size is in MB
|
// Cache size is in MB
|
||||||
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
||||||
return DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
return DiskLruCache.open(
|
||||||
|
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||||
PARAMETER_APP_VERSION,
|
PARAMETER_APP_VERSION,
|
||||||
PARAMETER_VALUE_COUNT,
|
PARAMETER_VALUE_COUNT,
|
||||||
cacheSize * 1024 * 1024)
|
cacheSize * 1024 * 1024
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// <-- EH
|
// <-- EH
|
||||||
|
|
||||||
|
@ -19,18 +19,22 @@ interface ChapterQueries : DbProvider {
|
|||||||
|
|
||||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
.query(getMergedChaptersQuery(mangaId))
|
.query(getMergedChaptersQuery(mangaId))
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getRecentChapters(date: Date) = db.get()
|
fun getRecentChapters(date: Date) = db.get()
|
||||||
@ -80,11 +84,13 @@ interface ChapterQueries : DbProvider {
|
|||||||
|
|
||||||
fun getChapters(url: String) = db.get()
|
fun getChapters(url: String) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
.whereArgs(url)
|
.whereArgs(url)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.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
|
* Query to get the manga merged into a merged manga
|
||||||
*/
|
*/
|
||||||
fun getMergedMangaQuery(id: Long) = """
|
fun getMergedMangaQuery(id: Long) =
|
||||||
|
"""
|
||||||
SELECT ${Manga.TABLE}.*
|
SELECT ${Manga.TABLE}.*
|
||||||
FROM (
|
FROM (
|
||||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
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
|
* Query to get the chapters of all manga in a merged manga
|
||||||
*/
|
*/
|
||||||
fun getMergedChaptersQuery(id: Long) = """
|
fun getMergedChaptersQuery(id: Long) =
|
||||||
|
"""
|
||||||
SELECT ${Chapter.TABLE}.*
|
SELECT ${Chapter.TABLE}.*
|
||||||
FROM (
|
FROM (
|
||||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||||
|
@ -9,7 +9,8 @@ object MergedTable {
|
|||||||
const val COL_MANGA_ID = "mangaID"
|
const val COL_MANGA_ID = "mangaID"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_MERGE_ID INTEGER NOT NULL,
|
$COL_MERGE_ID INTEGER NOT NULL,
|
||||||
$COL_MANGA_ID INTEGER NOT NULL
|
$COL_MANGA_ID INTEGER NOT NULL
|
||||||
)"""
|
)"""
|
||||||
|
@ -19,10 +19,12 @@ interface UrlImportableSource : Source {
|
|||||||
return try {
|
return try {
|
||||||
val uri = URI(url)
|
val uri = URI(url)
|
||||||
var out = uri.path
|
var out = uri.path
|
||||||
if (uri.query != null)
|
if (uri.query != null) {
|
||||||
out += "?" + uri.query
|
out += "?" + uri.query
|
||||||
if (uri.fragment != null)
|
}
|
||||||
|
if (uri.fragment != null) {
|
||||||
out += "#" + uri.fragment
|
out += "#" + uri.fragment
|
||||||
|
}
|
||||||
out
|
out
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
url
|
url
|
||||||
|
@ -73,16 +73,18 @@ class EHentai(
|
|||||||
override val metaClass = EHentaiSearchMetadata::class
|
override val metaClass = EHentaiSearchMetadata::class
|
||||||
|
|
||||||
val schema: String
|
val schema: String
|
||||||
get() = if (prefs.secureEXH().getOrDefault())
|
get() = if (prefs.secureEXH().getOrDefault()) {
|
||||||
"https"
|
"https"
|
||||||
else
|
} else {
|
||||||
"http"
|
"http"
|
||||||
|
}
|
||||||
|
|
||||||
val domain: String
|
val domain: String
|
||||||
get() = if (exh)
|
get() = if (exh) {
|
||||||
"exhentai.org"
|
"exhentai.org"
|
||||||
else
|
} else {
|
||||||
"e-hentai.org"
|
"e-hentai.org"
|
||||||
|
}
|
||||||
|
|
||||||
override val baseUrl: String
|
override val baseUrl: String
|
||||||
get() = "$schema://$domain"
|
get() = "$schema://$domain"
|
||||||
@ -122,14 +124,16 @@ class EHentai(
|
|||||||
thumbnail_url = thumbnailElement.attr("src")
|
thumbnail_url = thumbnailElement.attr("src")
|
||||||
|
|
||||||
// TODO Parse genre + uploader + tags
|
// TODO Parse genre + uploader + tags
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parsedLocation = doc.location().toHttpUrlOrNull()
|
val parsedLocation = doc.location().toHttpUrlOrNull()
|
||||||
|
|
||||||
// Add to page if required
|
// Add to page if required
|
||||||
val hasNextPage = if (parsedLocation == null ||
|
val hasNextPage = if (parsedLocation == null ||
|
||||||
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) {
|
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)
|
||||||
|
) {
|
||||||
select("a[onclick=return false]").last()?.let {
|
select("a[onclick=return false]").last()?.let {
|
||||||
it.text() == ">"
|
it.text() == ">"
|
||||||
} ?: false
|
} ?: false
|
||||||
@ -201,9 +205,11 @@ class EHentai(
|
|||||||
url = EHentaiSearchMetadata.normalizeUrl(d.location())
|
url = EHentaiSearchMetadata.normalizeUrl(d.location())
|
||||||
name = "v1: " + d.selectFirst("#gn").text()
|
name = "v1: " + d.selectFirst("#gn").text()
|
||||||
chapter_number = 1f
|
chapter_number = 1f
|
||||||
date_upload = EX_DATE_FORMAT.parse(d.select("#gdd .gdt1").find { el ->
|
date_upload = EX_DATE_FORMAT.parse(
|
||||||
|
d.select("#gdd .gdt1").find { el ->
|
||||||
el.text().toLowerCase() == "posted:"
|
el.text().toLowerCase() == "posted:"
|
||||||
}!!.nextElementSibling().text()).time
|
}!!.nextElementSibling().text()
|
||||||
|
).time
|
||||||
}
|
}
|
||||||
// Build and append the rest of the galleries
|
// Build and append the rest of the galleries
|
||||||
if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self)
|
if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self)
|
||||||
@ -253,17 +259,22 @@ class EHentai(
|
|||||||
}.sortedBy(Pair<Int, String>::first).map { it.second }
|
}.sortedBy(Pair<Int, String>::first).map { it.second }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
|
private fun chapterPageCall(np: String): Observable<Response> {
|
||||||
private fun chapterPageRequest(np: String) = exGet(np, null, headers)
|
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 {
|
private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let {
|
||||||
return if (it.text() == ">") it.attr("href") else null
|
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)
|
latestUpdatesRequest(page)
|
||||||
else
|
} else {
|
||||||
exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists
|
exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists
|
||||||
|
}
|
||||||
|
|
||||||
// Support direct URL importing
|
// Support direct URL importing
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
@ -314,9 +325,12 @@ class EHentai(
|
|||||||
override fun searchMangaParse(response: Response) = genericMangaParse(response)
|
override fun searchMangaParse(response: Response) = genericMangaParse(response)
|
||||||
override fun latestUpdatesParse(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 {
|
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))
|
addParam(url, "page", Integer.toString(page - 1))
|
||||||
} ?: url, additionalHeaders?.let {
|
} ?: url,
|
||||||
|
additionalHeaders?.let {
|
||||||
val headers = headers.newBuilder()
|
val headers = headers.newBuilder()
|
||||||
it.toMultimap().forEach { (t, u) ->
|
it.toMultimap().forEach { (t, u) ->
|
||||||
u.forEach {
|
u.forEach {
|
||||||
@ -324,12 +338,15 @@ class EHentai(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers.build()
|
headers.build()
|
||||||
} ?: headers).let {
|
} ?: headers
|
||||||
if (!cache)
|
).let {
|
||||||
|
if (!cache) {
|
||||||
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
|
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
|
||||||
else
|
} else {
|
||||||
it
|
it
|
||||||
}!!
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
||||||
@ -352,9 +369,13 @@ class EHentai(
|
|||||||
} else Observable.just(doc)
|
} else Observable.just(doc)
|
||||||
|
|
||||||
pre.flatMap {
|
pre.flatMap {
|
||||||
parseToManga(manga, it).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it).andThen(
|
||||||
|
Observable.just(
|
||||||
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response.close()
|
response.close()
|
||||||
@ -404,8 +425,10 @@ class EHentai(
|
|||||||
val right = rightElement.text().nullIfBlank()?.trim()
|
val right = rightElement.text().nullIfBlank()?.trim()
|
||||||
if (left != null && right != null) {
|
if (left != null && right != null) {
|
||||||
ignore {
|
ignore {
|
||||||
when (left.removeSuffix(":")
|
when (
|
||||||
.toLowerCase()) {
|
left.removeSuffix(":")
|
||||||
|
.toLowerCase()
|
||||||
|
) {
|
||||||
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||||
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
|
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
|
||||||
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
|
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
|
||||||
@ -428,7 +451,8 @@ class EHentai(
|
|||||||
|
|
||||||
lastUpdateCheck = System.currentTimeMillis()
|
lastUpdateCheck = System.currentTimeMillis()
|
||||||
if (datePosted != null &&
|
if (datePosted != null &&
|
||||||
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) {
|
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME
|
||||||
|
) {
|
||||||
aged = true
|
aged = true
|
||||||
XLog.d("aged %s - too old", title)
|
XLog.d("aged %s - too old", title)
|
||||||
}
|
}
|
||||||
@ -452,16 +476,19 @@ class EHentai(
|
|||||||
tags.clear()
|
tags.clear()
|
||||||
select("#taglist tr").forEach {
|
select("#taglist tr").forEach {
|
||||||
val namespace = it.select(".tc").text().removeSuffix(":")
|
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||||
tags.addAll(it.select("div").map { element ->
|
tags.addAll(
|
||||||
|
it.select("div").map { element ->
|
||||||
RaisedTag(
|
RaisedTag(
|
||||||
namespace,
|
namespace,
|
||||||
element.text().trim(),
|
element.text().trim(),
|
||||||
if (element.hasClass("gtl"))
|
if (element.hasClass("gtl")) {
|
||||||
TAG_TYPE_LIGHT
|
TAG_TYPE_LIGHT
|
||||||
else
|
} else {
|
||||||
TAG_TYPE_NORMAL
|
TAG_TYPE_NORMAL
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add genre as virtual tag
|
// Add genre as virtual tag
|
||||||
@ -505,9 +532,13 @@ class EHentai(
|
|||||||
var favNames: List<String>? = null
|
var favNames: List<String>? = null
|
||||||
|
|
||||||
do {
|
do {
|
||||||
val response2 = client.newCall(exGet(favoriteUrl,
|
val response2 = client.newCall(
|
||||||
|
exGet(
|
||||||
|
favoriteUrl,
|
||||||
page = page,
|
page = page,
|
||||||
cache = false)).execute()
|
cache = false
|
||||||
|
)
|
||||||
|
).execute()
|
||||||
val doc = response2.asJsoup()
|
val doc = response2.asJsoup()
|
||||||
|
|
||||||
// Parse favorites
|
// Parse favorites
|
||||||
@ -515,22 +546,24 @@ class EHentai(
|
|||||||
result += parsed.first
|
result += parsed.first
|
||||||
|
|
||||||
// Parse fav names
|
// Parse fav names
|
||||||
if (favNames == null)
|
if (favNames == null) {
|
||||||
favNames = doc.select(".fp:not(.fps)").mapNotNull {
|
favNames = doc.select(".fp:not(.fps)").mapNotNull {
|
||||||
it.child(2).text()
|
it.child(2).text()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Next page
|
// Next page
|
||||||
|
|
||||||
page++
|
page++
|
||||||
} while (parsed.second)
|
} while (parsed.second)
|
||||||
|
|
||||||
return Pair(result as List<ParsedManga>, favNames!!)
|
return Pair(result as List<ParsedManga>, favNames!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun spPref() = if (exh)
|
fun spPref() = if (exh) {
|
||||||
prefs.eh_exhSettingsProfile()
|
prefs.eh_exhSettingsProfile()
|
||||||
else
|
} else {
|
||||||
prefs.eh_ehSettingsProfile()
|
prefs.eh_ehSettingsProfile()
|
||||||
|
}
|
||||||
|
|
||||||
fun rawCookies(sp: Int): Map<String, String> {
|
fun rawCookies(sp: Int): Map<String, String> {
|
||||||
val cookies: MutableMap<String, String> = mutableMapOf()
|
val cookies: MutableMap<String, String> = mutableMapOf()
|
||||||
@ -541,17 +574,20 @@ class EHentai(
|
|||||||
cookies["sp"] = sp.toString()
|
cookies["sp"] = sp.toString()
|
||||||
|
|
||||||
val sessionKey = prefs.eh_settingsKey().getOrDefault()
|
val sessionKey = prefs.eh_settingsKey().getOrDefault()
|
||||||
if (sessionKey != null)
|
if (sessionKey != null) {
|
||||||
cookies["sk"] = sessionKey
|
cookies["sk"] = sessionKey
|
||||||
|
}
|
||||||
|
|
||||||
val sessionCookie = prefs.eh_sessionCookie().getOrDefault()
|
val sessionCookie = prefs.eh_sessionCookie().getOrDefault()
|
||||||
if (sessionCookie != null)
|
if (sessionCookie != null) {
|
||||||
cookies["s"] = sessionCookie
|
cookies["s"] = sessionCookie
|
||||||
|
}
|
||||||
|
|
||||||
val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault()
|
val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault()
|
||||||
if (hathPerksCookie != null)
|
if (hathPerksCookie != null) {
|
||||||
cookies["hath_perks"] = hathPerksCookie
|
cookies["hath_perks"] = hathPerksCookie
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Session-less extended display mode (for users without ExHentai)
|
// Session-less extended display mode (for users without ExHentai)
|
||||||
cookies["sl"] = "dm_2"
|
cookies["sl"] = "dm_2"
|
||||||
@ -595,13 +631,17 @@ class EHentai(
|
|||||||
|
|
||||||
class Watched : Filter.CheckBox("Watched List"), UriFilter {
|
class Watched : Filter.CheckBox("Watched List"), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendPath("watched")
|
builder.appendPath("watched")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false)
|
class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false)
|
||||||
class GenreGroup : Filter.Group<GenreOption>("Genres", listOf(
|
class GenreGroup :
|
||||||
|
Filter.Group<GenreOption>(
|
||||||
|
"Genres",
|
||||||
|
listOf(
|
||||||
GenreOption("Dōjinshi", 2),
|
GenreOption("Dōjinshi", 2),
|
||||||
GenreOption("Manga", 4),
|
GenreOption("Manga", 4),
|
||||||
GenreOption("Artist CG", 8),
|
GenreOption("Artist CG", 8),
|
||||||
@ -612,7 +652,9 @@ class EHentai(
|
|||||||
GenreOption("Cosplay", 64),
|
GenreOption("Cosplay", 64),
|
||||||
GenreOption("Asian Porn", 128),
|
GenreOption("Asian Porn", 128),
|
||||||
GenreOption("Misc", 1)
|
GenreOption("Misc", 1)
|
||||||
)), UriFilter {
|
)
|
||||||
|
),
|
||||||
|
UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
val bits = state.fold(0) { acc, genre ->
|
val bits = state.fold(0) { acc, genre ->
|
||||||
if (!genre.state) acc + genre.genreId else acc
|
if (!genre.state) acc + genre.genreId else acc
|
||||||
@ -623,10 +665,11 @@ class EHentai(
|
|||||||
|
|
||||||
class AdvancedOption(name: String, val param: String, defValue: Boolean = false) : Filter.CheckBox(name, defValue), UriFilter {
|
class AdvancedOption(name: String, val param: String, defValue: Boolean = false) : Filter.CheckBox(name, defValue), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendQueryParameter(param, "on")
|
builder.appendQueryParameter(param, "on")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open class PageOption(name: String, private val queryKey: String) : Filter.Text(name), UriFilter {
|
open class PageOption(name: String, private val queryKey: String) : Filter.Text(name), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
@ -643,13 +686,18 @@ class EHentai(
|
|||||||
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
||||||
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
||||||
|
|
||||||
class RatingOption : Filter.Select<String>("Minimum Rating", arrayOf(
|
class RatingOption :
|
||||||
|
Filter.Select<String>(
|
||||||
|
"Minimum Rating",
|
||||||
|
arrayOf(
|
||||||
"Any",
|
"Any",
|
||||||
"2 stars",
|
"2 stars",
|
||||||
"3 stars",
|
"3 stars",
|
||||||
"4 stars",
|
"4 stars",
|
||||||
"5 stars"
|
"5 stars"
|
||||||
)), UriFilter {
|
)
|
||||||
|
),
|
||||||
|
UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state > 0) {
|
if (state > 0) {
|
||||||
builder.appendQueryParameter("f_srdd", Integer.toString(state + 1))
|
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 Name", "f_sname", true),
|
||||||
AdvancedOption("Search Gallery Tags", "f_stags", true),
|
AdvancedOption("Search Gallery Tags", "f_stags", true),
|
||||||
AdvancedOption("Search Gallery Description", "f_sdesc"),
|
AdvancedOption("Search Gallery Description", "f_sdesc"),
|
||||||
@ -670,14 +720,16 @@ class EHentai(
|
|||||||
RatingOption(),
|
RatingOption(),
|
||||||
MinPagesOption(),
|
MinPagesOption(),
|
||||||
MaxPagesOption()
|
MaxPagesOption()
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class ReverseFilter : Filter.CheckBox("Reverse search results")
|
class ReverseFilter : Filter.CheckBox("Reverse search results")
|
||||||
|
|
||||||
override val name = if (exh)
|
override val name = if (exh) {
|
||||||
"ExHentai"
|
"ExHentai"
|
||||||
else
|
} else {
|
||||||
"E-Hentai"
|
"E-Hentai"
|
||||||
|
}
|
||||||
|
|
||||||
class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause)
|
class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause)
|
||||||
|
|
||||||
@ -717,17 +769,23 @@ class EHentai(
|
|||||||
val json = JsonObject()
|
val json = JsonObject()
|
||||||
json["method"] = "gtoken"
|
json["method"] = "gtoken"
|
||||||
json["pagelist"] = JsonArray().apply {
|
json["pagelist"] = JsonArray().apply {
|
||||||
add(JsonArray().apply {
|
add(
|
||||||
|
JsonArray().apply {
|
||||||
add(gallery.toInt())
|
add(gallery.toInt())
|
||||||
add(pageToken)
|
add(pageToken)
|
||||||
add(pageNum.toInt())
|
add(pageNum.toInt())
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val outJson = JsonParser.parseString(client.newCall(Request.Builder()
|
val outJson = JsonParser.parseString(
|
||||||
|
client.newCall(
|
||||||
|
Request.Builder()
|
||||||
.url(EH_API_BASE)
|
.url(EH_API_BASE)
|
||||||
.post(RequestBody.create(JSON, json.toString()))
|
.post(RequestBody.create(JSON, json.toString()))
|
||||||
.build()).execute().body!!.string()).obj
|
.build()
|
||||||
|
).execute().body!!.string()
|
||||||
|
).obj
|
||||||
|
|
||||||
val obj = outJson["tokenlist"].array.first()
|
val obj = outJson["tokenlist"].array.first()
|
||||||
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
|
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
|
||||||
|
@ -65,7 +65,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
private fun tagIndexVersion(): Single<Long> {
|
private fun tagIndexVersion(): Single<Long> {
|
||||||
val sCachedTagIndexVersion = cachedTagIndexVersion
|
val sCachedTagIndexVersion = cachedTagIndexVersion
|
||||||
return if (sCachedTagIndexVersion == null ||
|
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 {
|
HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||||
cachedTagIndexVersion = it
|
cachedTagIndexVersion = it
|
||||||
tagIndexVersionCacheTime = System.currentTimeMillis()
|
tagIndexVersionCacheTime = System.currentTimeMillis()
|
||||||
@ -80,7 +81,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
private fun galleryIndexVersion(): Single<Long> {
|
private fun galleryIndexVersion(): Single<Long> {
|
||||||
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
|
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
|
||||||
return if (sCachedGalleryIndexVersion == null ||
|
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 {
|
HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||||
cachedGalleryIndexVersion = it
|
cachedGalleryIndexVersion = it
|
||||||
galleryIndexVersionCacheTime = System.currentTimeMillis()
|
galleryIndexVersionCacheTime = System.currentTimeMillis()
|
||||||
@ -285,13 +287,15 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun nozomiIdsToMangas(ids: List<Int>): Single<List<SManga>> {
|
private fun nozomiIdsToMangas(ids: List<Int>): Single<List<SManga>> {
|
||||||
return Single.zip(ids.map {
|
return Single.zip(
|
||||||
|
ids.map {
|
||||||
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
|
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
|
||||||
.map { parseGalleryBlock(it) }
|
.map { parseGalleryBlock(it) }
|
||||||
.toSingle()
|
.toSingle()
|
||||||
}) { it.map { m -> m as SManga } }
|
}
|
||||||
|
) { it.map { m -> m as SManga } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseGalleryBlock(response: Response): SManga {
|
private fun parseGalleryBlock(response: Response): SManga {
|
||||||
@ -320,9 +324,13 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it.asJsoup()).andThen(
|
||||||
|
Observable.just(
|
||||||
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,8 +415,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader")
|
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader") {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html"
|
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 NUMBER_OF_FRONTENDS = 2
|
||||||
|
|
||||||
private val DATE_FORMAT by lazy {
|
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)
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.US)
|
||||||
else
|
} else {
|
||||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US)
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -136,9 +136,13 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it).andThen(
|
||||||
|
Observable.just(
|
||||||
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,11 +212,12 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
}?.apply {
|
}?.apply {
|
||||||
tags.clear()
|
tags.clear()
|
||||||
}?.forEach {
|
}?.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))
|
tags.add(RaisedTag(it.first!!, it.second!!, TAG_TYPE_DEFAULT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getOrLoadMetadata(mangaId: Long?, nhId: Long) = getOrLoadMetadata(mangaId) {
|
fun getOrLoadMetadata(mangaId: Long?, nhId: Long) = getOrLoadMetadata(mangaId) {
|
||||||
client.newCall(nhGet(baseUrl + NHentaiSearchMetadata.nhIdToPath(nhId)))
|
client.newCall(nhGet(baseUrl + NHentaiSearchMetadata.nhIdToPath(nhId)))
|
||||||
@ -220,19 +225,25 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
.toSingle()
|
.toSingle()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga) = Observable.just(listOf(SChapter.create().apply {
|
override fun fetchChapterList(manga: SManga) = Observable.just(
|
||||||
|
listOf(
|
||||||
|
SChapter.create().apply {
|
||||||
url = manga.url
|
url = manga.url
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
chapter_number = 1f
|
chapter_number = 1f
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
|
override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
|
||||||
if (metadata.mediaId == null) emptyList()
|
if (metadata.mediaId == null) {
|
||||||
else
|
emptyList()
|
||||||
|
} else {
|
||||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
metadata.pageImageTypes.mapIndexed { index, s ->
|
||||||
val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
|
val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
|
||||||
Page(index, imageUrl!!, imageUrl)
|
Page(index, imageUrl!!, imageUrl)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}.toObservable()
|
}.toObservable()
|
||||||
|
|
||||||
override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!!
|
override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!!
|
||||||
@ -270,12 +281,14 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
|
|
||||||
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.header("User-Agent",
|
.header(
|
||||||
|
"User-Agent",
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) " +
|
"Mozilla/5.0 (X11; Linux x86_64) " +
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) " +
|
"AppleWebKit/537.36 (KHTML, like Gecko) " +
|
||||||
"Chrome/56.0.2924.87 " +
|
"Chrome/56.0.2924.87 " +
|
||||||
"Safari/537.36 " +
|
"Safari/537.36 " +
|
||||||
"$appName/${BuildConfig.VERSION_CODE}")
|
"$appName/${BuildConfig.VERSION_CODE}"
|
||||||
|
)
|
||||||
.tag(tag).build()
|
.tag(tag).build()
|
||||||
|
|
||||||
override val id = NHENTAI_SOURCE_ID
|
override val id = NHENTAI_SOURCE_ID
|
||||||
@ -295,8 +308,9 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g")
|
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g") {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return "$baseUrl/g/${uri.pathSegments[1]}/"
|
return "$baseUrl/g/${uri.pathSegments[1]}/"
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,10 @@ import org.jsoup.nodes.TextNode
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
// TODO Transform into delegated source
|
// TODO Transform into delegated source
|
||||||
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
|
class PervEden(override val id: Long, val pvLang: PervEdenLang) :
|
||||||
LewdSource<PervEdenSearchMetadata, Document>, UrlImportableSource {
|
ParsedHttpSource(),
|
||||||
|
LewdSource<PervEdenSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* The class of the metadata used by this source
|
* The class of the metadata used by this source
|
||||||
*/
|
*/
|
||||||
@ -79,9 +81,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
override fun searchMangaNextPageSelector() = ".next"
|
override fun searchMangaNextPageSelector() = ".next"
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
val urlLang = if (lang == "en")
|
val urlLang = if (lang == "en") {
|
||||||
"eng"
|
"eng"
|
||||||
else "it"
|
} else {
|
||||||
|
"it"
|
||||||
|
}
|
||||||
return GET("$baseUrl/$urlLang/")
|
return GET("$baseUrl/$urlLang/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,9 +135,13 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it.asJsoup()).andThen(
|
||||||
|
Observable.just(
|
||||||
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,10 +173,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
"Alternative name(s)" -> {
|
"Alternative name(s)" -> {
|
||||||
if (it is TextNode) {
|
if (it is TextNode) {
|
||||||
val text = it.text().trim()
|
val text = it.text().trim()
|
||||||
if (!text.isBlank())
|
if (!text.isBlank()) {
|
||||||
newAltTitles += text
|
newAltTitles += text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"Artist" -> {
|
"Artist" -> {
|
||||||
if (it is Element && it.tagName() == "a") {
|
if (it is Element && it.tagName() == "a") {
|
||||||
artist = it.text()
|
artist = it.text()
|
||||||
@ -176,26 +185,29 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Genres" -> {
|
"Genres" -> {
|
||||||
if (it is Element && it.tagName() == "a")
|
if (it is Element && it.tagName() == "a") {
|
||||||
tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT)
|
tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"Type" -> {
|
"Type" -> {
|
||||||
if (it is TextNode) {
|
if (it is TextNode) {
|
||||||
val text = it.text().trim()
|
val text = it.text().trim()
|
||||||
if (!text.isBlank())
|
if (!text.isBlank()) {
|
||||||
type = text
|
type = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"Status" -> {
|
"Status" -> {
|
||||||
if (it is TextNode) {
|
if (it is TextNode) {
|
||||||
val text = it.text().trim()
|
val text = it.text().trim()
|
||||||
if (!text.isBlank())
|
if (!text.isBlank()) {
|
||||||
status = text
|
status = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
altTitles = newAltTitles
|
altTitles = newAltTitles
|
||||||
|
|
||||||
@ -227,7 +239,8 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
this,
|
this,
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
title = ""
|
title = ""
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim())!!.time
|
date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim())!!.time
|
||||||
@ -249,30 +262,42 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
StatusFilterGroup()
|
StatusFilterGroup()
|
||||||
)
|
)
|
||||||
|
|
||||||
class StatusFilterGroup : UriGroup<StatusFilter>("Status", listOf(
|
class StatusFilterGroup : UriGroup<StatusFilter>(
|
||||||
|
"Status",
|
||||||
|
listOf(
|
||||||
StatusFilter("Ongoing", 1),
|
StatusFilter("Ongoing", 1),
|
||||||
StatusFilter("Completed", 2),
|
StatusFilter("Completed", 2),
|
||||||
StatusFilter("Suspended", 0)
|
StatusFilter("Suspended", 0)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendQueryParameter("status", id.toString())
|
builder.appendQueryParameter("status", id.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Explicit type arg for listOf() to workaround this: KT-16570
|
// 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(),
|
ReleaseYearRangeFilter(),
|
||||||
ReleaseYearYearFilter()
|
ReleaseYearYearFilter()
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class ReleaseYearRangeFilter : Filter.Select<String>("Range", arrayOf(
|
class ReleaseYearRangeFilter :
|
||||||
|
Filter.Select<String>(
|
||||||
|
"Range",
|
||||||
|
arrayOf(
|
||||||
"on",
|
"on",
|
||||||
"after",
|
"after",
|
||||||
"before"
|
"before"
|
||||||
)), UriFilter {
|
)
|
||||||
|
),
|
||||||
|
UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
builder.appendQueryParameter("releasedType", state.toString())
|
builder.appendQueryParameter("releasedType", state.toString())
|
||||||
}
|
}
|
||||||
@ -296,20 +321,24 @@ 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("Japanese Manga", 0),
|
||||||
TypeFilter("Korean Manhwa", 1),
|
TypeFilter("Korean Manhwa", 1),
|
||||||
TypeFilter("Chinese Manhua", 2),
|
TypeFilter("Chinese Manhua", 2),
|
||||||
TypeFilter("Comic", 3),
|
TypeFilter("Comic", 3),
|
||||||
TypeFilter("Doujinshi", 4)
|
TypeFilter("Doujinshi", 4)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendQueryParameter("type", id.toString())
|
builder.appendQueryParameter("type", id.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val matchingHosts = listOf("www.perveden.com")
|
override val matchingHosts = listOf("www.perveden.com")
|
||||||
|
|
||||||
|
@ -41,7 +41,8 @@ import rx.schedulers.Schedulers
|
|||||||
|
|
||||||
typealias SiteMap = NakedTrie<Unit>
|
typealias SiteMap = NakedTrie<Unit>
|
||||||
|
|
||||||
class EightMuses : HttpSource(),
|
class EightMuses :
|
||||||
|
HttpSource(),
|
||||||
LewdSource<EightMusesSearchMetadata, Document>,
|
LewdSource<EightMusesSearchMetadata, Document>,
|
||||||
UrlImportableSource {
|
UrlImportableSource {
|
||||||
override val id = EIGHTMUSES_SOURCE_ID
|
override val id = EIGHTMUSES_SOURCE_ID
|
||||||
@ -184,9 +185,11 @@ class EightMuses : HttpSource(),
|
|||||||
return client.newCall(request)
|
return client.newCall(request)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMapSingle { response ->
|
.flatMapSingle { response ->
|
||||||
RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
RxJavaInterop.toV1Single(
|
||||||
|
GlobalScope.async(Dispatchers.IO) {
|
||||||
parseResultsPage(response, dig)
|
parseResultsPage(response, dig)
|
||||||
}.asSingle(GlobalScope.coroutineContext))
|
}.asSingle(GlobalScope.coroutineContext)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,9 +262,11 @@ class EightMuses : HttpSource(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
return RxJavaInterop.toV1Single(
|
||||||
|
GlobalScope.async(Dispatchers.IO) {
|
||||||
fetchAndParseChapterList("", manga.url)
|
fetchAndParseChapterList("", manga.url)
|
||||||
}.asSingle(GlobalScope.coroutineContext)).toObservable()
|
}.asSingle(GlobalScope.coroutineContext)
|
||||||
|
).toObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
|
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
|
||||||
|
@ -132,7 +132,8 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
|
|||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Should not be called!")
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException("Should not be called!")
|
||||||
|
|
||||||
private fun fetchSearchMangaInternal(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
private fun fetchSearchMangaInternal(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
return RxJavaInterop.toV1Single(
|
||||||
|
GlobalScope.async(Dispatchers.IO) {
|
||||||
val modeFilter = filters.filterIsInstance<ModeFilter>().firstOrNull()
|
val modeFilter = filters.filterIsInstance<ModeFilter>().firstOrNull()
|
||||||
val sortFilter = filters.filterIsInstance<SortFilter>().firstOrNull()
|
val sortFilter = filters.filterIsInstance<SortFilter>().firstOrNull()
|
||||||
|
|
||||||
@ -196,10 +197,12 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
|
|||||||
base += "/$page"
|
base += "/$page"
|
||||||
|
|
||||||
if (isSortFilter) {
|
if (isSortFilter) {
|
||||||
parseListing(client.newCall(GET(baseUrl + base, headers))
|
parseListing(
|
||||||
|
client.newCall(GET(baseUrl + base, headers))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.toSingle()
|
.toSingle()
|
||||||
.await(Schedulers.io()))
|
.await(Schedulers.io())
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val body = if (tagQuery != null) {
|
val body = if (tagQuery != null) {
|
||||||
FormBody.Builder()
|
FormBody.Builder()
|
||||||
@ -224,17 +227,22 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
|
|||||||
.toSingle()
|
.toSingle()
|
||||||
.await(Schedulers.io())
|
.await(Schedulers.io())
|
||||||
|
|
||||||
if (!processResponse.isRedirect)
|
if (!processResponse.isRedirect) {
|
||||||
throw IllegalStateException("Unexpected process response code!")
|
throw IllegalStateException("Unexpected process response code!")
|
||||||
|
}
|
||||||
|
|
||||||
val sessId = processResponse.headers("Set-Cookie").find {
|
val sessId = processResponse.headers("Set-Cookie").find {
|
||||||
it.startsWith("PHPSESSID")
|
it.startsWith("PHPSESSID")
|
||||||
} ?: throw IllegalStateException("Missing server session cookie!")
|
} ?: throw IllegalStateException("Missing server session cookie!")
|
||||||
|
|
||||||
val response = clientWithoutCookies.newCall(GET(baseUrl + base,
|
val response = clientWithoutCookies.newCall(
|
||||||
|
GET(
|
||||||
|
baseUrl + base,
|
||||||
headersBuilder()
|
headersBuilder()
|
||||||
.set("Cookie", BASE_COOKIES + " " + sessId.substringBefore(';'))
|
.set("Cookie", BASE_COOKIES + " " + sessId.substringBefore(';'))
|
||||||
.build()))
|
.build()
|
||||||
|
)
|
||||||
|
)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.toSingle()
|
.toSingle()
|
||||||
.await(Schedulers.io())
|
.await(Schedulers.io())
|
||||||
@ -254,7 +262,8 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
|
|||||||
hasNextPage
|
hasNextPage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.asSingle(GlobalScope.coroutineContext)).toObservable()
|
}.asSingle(GlobalScope.coroutineContext)
|
||||||
|
).toObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collection must be sorted and cannot be sorted
|
// Collection must be sorted and cannot be sorted
|
||||||
@ -397,7 +406,10 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
class HelpFilter : Filter.HelpDialog("Usage instructions", markdown = """
|
class HelpFilter : Filter.HelpDialog(
|
||||||
|
"Usage instructions",
|
||||||
|
markdown =
|
||||||
|
"""
|
||||||
### Modes
|
### Modes
|
||||||
There are three available filter modes:
|
There are three available filter modes:
|
||||||
- Text search
|
- Text search
|
||||||
@ -416,12 +428,16 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
|
|||||||
View a list of all galleries sorted by a specific parameter. Exit sorting mode by resetting the filters using the reset button near the bottom of the screen.
|
View a list of all galleries sorted by a specific parameter. Exit sorting mode by resetting the filters using the reset button near the bottom of the screen.
|
||||||
|
|
||||||
### Tag list
|
### Tag list
|
||||||
""".trimIndent() + "\n$TAGS_AS_MARKDOWN")
|
""".trimIndent() + "\n$TAGS_AS_MARKDOWN"
|
||||||
|
)
|
||||||
|
|
||||||
class ModeFilter : Filter.Select<String>("Mode", arrayOf(
|
class ModeFilter : Filter.Select<String>(
|
||||||
|
"Mode",
|
||||||
|
arrayOf(
|
||||||
"Text search",
|
"Text search",
|
||||||
"Tag search"
|
"Tag search"
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class SortFilter : Filter.Sort("Sort", SORT_OPTIONS.map { it.second }.toTypedArray()) {
|
class SortFilter : Filter.Sort("Sort", SORT_OPTIONS.map { it.second }.toTypedArray()) {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -19,8 +19,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class HentaiCafe(delegate: HttpSource) :
|
||||||
LewdSource<HentaiCafeSearchMetadata, Document>, UrlImportableSource {
|
DelegatedHttpSource(delegate),
|
||||||
|
LewdSource<HentaiCafeSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||||
*/
|
*/
|
||||||
@ -40,9 +42,13 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it.asJsoup()).andThen(
|
||||||
|
Observable.just(
|
||||||
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,9 +104,10 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
return if (lcFirstPathSegment == "manga")
|
return if (lcFirstPathSegment == "manga") {
|
||||||
"https://hentai.cafe/${uri.pathSegments[2]}"
|
"https://hentai.cafe/${uri.pathSegments[2]}"
|
||||||
else
|
} else {
|
||||||
"https://hentai.cafe/$lcFirstPathSegment"
|
"https://hentai.cafe/$lcFirstPathSegment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -18,8 +18,10 @@ import exh.util.urlImportFetchSearchManga
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class Pururin(delegate: HttpSource) :
|
||||||
LewdSource<PururinSearchMetadata, Document>, UrlImportableSource {
|
DelegatedHttpSource(delegate),
|
||||||
|
LewdSource<PururinSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||||
*/
|
*/
|
||||||
|
@ -19,8 +19,10 @@ import java.util.Locale
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class Tsumino(delegate: HttpSource) :
|
||||||
LewdSource<TsuminoSearchMetadata, Document>, UrlImportableSource {
|
DelegatedHttpSource(delegate),
|
||||||
|
LewdSource<TsuminoSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
override val metaClass = TsuminoSearchMetadata::class
|
override val metaClass = TsuminoSearchMetadata::class
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
@ -32,8 +34,9 @@ class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
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 null
|
||||||
|
}
|
||||||
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
|
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,9 +109,11 @@ class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
character = newCharacter
|
character = newCharacter
|
||||||
|
|
||||||
input.getElementById("Tag")?.children()?.let {
|
input.getElementById("Tag")?.children()?.let {
|
||||||
tags.addAll(it.map {
|
tags.addAll(
|
||||||
|
it.map {
|
||||||
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
|
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,10 +131,14 @@ class SourceController :
|
|||||||
// Open the catalogue view.
|
// Open the catalogue view.
|
||||||
openCatalogue(source, BrowseSourceController(source))
|
openCatalogue(source, BrowseSourceController(source))
|
||||||
}
|
}
|
||||||
Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply {
|
Mode.SMART_SEARCH -> router.pushController(
|
||||||
|
SmartSearchController(
|
||||||
|
Bundle().apply {
|
||||||
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
|
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
|
||||||
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
|
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
|
||||||
}).withFadeTransaction())
|
}
|
||||||
|
).withFadeTransaction()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -430,9 +430,11 @@ open class BrowseSourcePresenter(
|
|||||||
val content = JsonParser.parseString(it.substringAfter(':')).obj
|
val content = JsonParser.parseString(it.substringAfter(':')).obj
|
||||||
val originalFilters = source.getFilterList()
|
val originalFilters = source.getFilterList()
|
||||||
filterSerializer.deserialize(originalFilters, content["filters"].array)
|
filterSerializer.deserialize(originalFilters, content["filters"].array)
|
||||||
EXHSavedSearch(content["name"].string,
|
EXHSavedSearch(
|
||||||
|
content["name"].string,
|
||||||
content["query"].string,
|
content["query"].string,
|
||||||
originalFilters)
|
originalFilters
|
||||||
|
)
|
||||||
} catch (t: RuntimeException) {
|
} catch (t: RuntimeException) {
|
||||||
// Load failed
|
// Load failed
|
||||||
Timber.e(t, "Failed to load saved search!")
|
Timber.e(t, "Failed to load saved search!")
|
||||||
|
@ -393,7 +393,6 @@ class LibraryPresenter(
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
replace: Boolean
|
replace: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val flags = preferences.migrateFlags().get()
|
val flags = preferences.migrateFlags().get()
|
||||||
val migrateChapters = MigrationFlags.hasChapters(flags)
|
val migrateChapters = MigrationFlags.hasChapters(flags)
|
||||||
val migrateCategories = MigrationFlags.hasCategories(flags)
|
val migrateCategories = MigrationFlags.hasCategories(flags)
|
||||||
|
@ -185,16 +185,20 @@ class MangaInfoPresenter(
|
|||||||
val toInsert = if (originalManga.source == MERGED_SOURCE_ID) {
|
val toInsert = if (originalManga.source == MERGED_SOURCE_ID) {
|
||||||
originalManga.apply {
|
originalManga.apply {
|
||||||
val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children
|
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!")
|
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.source,
|
||||||
manga.url
|
manga.url
|
||||||
)).writeAsUrl(gson)
|
)
|
||||||
|
).writeAsUrl(gson)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val newMangaConfig = MergedSource.MangaConfig(listOf(
|
val newMangaConfig = MergedSource.MangaConfig(
|
||||||
|
listOf(
|
||||||
MergedSource.MangaSource(
|
MergedSource.MangaSource(
|
||||||
originalManga.source,
|
originalManga.source,
|
||||||
originalManga.url
|
originalManga.url
|
||||||
@ -203,7 +207,8 @@ class MangaInfoPresenter(
|
|||||||
manga.source,
|
manga.source,
|
||||||
manga.url
|
manga.url
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
|
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
|
||||||
copyFrom(originalManga)
|
copyFrom(originalManga)
|
||||||
favorite = true
|
favorite = true
|
||||||
|
@ -23,18 +23,23 @@ class MigrationMangaDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
|
val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
|
||||||
val confirmString = applicationContext?.resources?.getQuantityString(confirmRes, mangaSet,
|
val confirmString = applicationContext?.resources?.getQuantityString(
|
||||||
mangaSet, (
|
confirmRes, mangaSet,
|
||||||
|
mangaSet,
|
||||||
|
(
|
||||||
if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
|
if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
|
||||||
else "")) ?: ""
|
else ""
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.message(text = confirmString)
|
.message(text = confirmString)
|
||||||
.positiveButton(if (copy) R.string.copy else R.string.migrate) {
|
.positiveButton(if (copy) R.string.copy else R.string.migrate) {
|
||||||
if (copy)
|
if (copy) {
|
||||||
(targetController as? MigrationListController)?.copyMangas()
|
(targetController as? MigrationListController)?.copyMangas()
|
||||||
else
|
} else {
|
||||||
(targetController as? MigrationListController)?.migrateMangas()
|
(targetController as? MigrationListController)?.migrateMangas()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.negativeButton(android.R.string.no)
|
.negativeButton(android.R.string.no)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,10 @@ class MigrationBottomSheetDialog(
|
|||||||
private val listener:
|
private val listener:
|
||||||
StartMigrationListener
|
StartMigrationListener
|
||||||
) :
|
) :
|
||||||
BottomSheetDialog(activity,
|
BottomSheetDialog(
|
||||||
theme) {
|
activity,
|
||||||
|
theme
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* Preferences helper.
|
* Preferences helper.
|
||||||
*/
|
*/
|
||||||
@ -47,8 +49,9 @@ class MigrationBottomSheetDialog(
|
|||||||
// scroll.addView(view)
|
// scroll.addView(view)
|
||||||
|
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
sourceGroup.orientation = LinearLayout.HORIZONTAL
|
sourceGroup.orientation = LinearLayout.HORIZONTAL
|
||||||
|
}
|
||||||
window?.setBackgroundDrawable(null)
|
window?.setBackgroundDrawable(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +66,10 @@ class MigrationBottomSheetDialog(
|
|||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
preferences.skipPreMigration().set(skip_step.isChecked)
|
preferences.skipPreMigration().set(skip_step.isChecked)
|
||||||
listener.startMigration(
|
listener.startMigration(
|
||||||
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank())
|
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank()) {
|
||||||
extra_search_param_text.text.toString() else null)
|
extra_search_param_text.text.toString()
|
||||||
|
} else null
|
||||||
|
)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,9 +101,12 @@ class MigrationBottomSheetDialog(
|
|||||||
|
|
||||||
skip_step.isChecked = preferences.skipPreMigration().get()
|
skip_step.isChecked = preferences.skipPreMigration().get()
|
||||||
skip_step.setOnCheckedChangeListener { _, isChecked ->
|
skip_step.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if (isChecked)
|
if (isChecked) {
|
||||||
(listener as? Controller)?.activity?.toast(R.string.pre_migration_skip_toast,
|
(listener as? Controller)?.activity?.toast(
|
||||||
Toast.LENGTH_LONG)
|
R.string.pre_migration_skip_toast,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,9 +16,14 @@ class MigrationSourceAdapter(
|
|||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
outState.putParcelableArrayList(SELECTED_SOURCES_KEY, ArrayList(currentItems.map {
|
outState.putParcelableArrayList(
|
||||||
|
SELECTED_SOURCES_KEY,
|
||||||
|
ArrayList(
|
||||||
|
currentItems.map {
|
||||||
it.asParcelable()
|
it.asParcelable()
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
|
@ -27,8 +27,11 @@ import exh.util.updateLayoutParams
|
|||||||
import exh.util.updatePaddingRelative
|
import exh.util.updatePaddingRelative
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrationControllerBinding>(bundle), FlexibleAdapter
|
class PreMigrationController(bundle: Bundle? = null) :
|
||||||
.OnItemClickListener, StartMigrationListener {
|
BaseController<PreMigrationControllerBinding>(bundle),
|
||||||
|
FlexibleAdapter
|
||||||
|
.OnItemClickListener,
|
||||||
|
StartMigrationListener {
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
@ -69,8 +72,10 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
|
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
|
||||||
}
|
}
|
||||||
// offset the recycler by the fab's inset + some inset on top
|
// offset the recycler by the fab's inset + some inset on top
|
||||||
v.updatePaddingRelative(bottom = padding.bottom + (binding.fab.marginBottom) +
|
v.updatePaddingRelative(
|
||||||
fabBaseMarginBottom + (binding.fab.height))
|
bottom = padding.bottom + (binding.fab.marginBottom) +
|
||||||
|
fabBaseMarginBottom + (binding.fab.height)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.fab.setOnClickListener {
|
binding.fab.setOnClickListener {
|
||||||
@ -101,7 +106,8 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
config.toList(),
|
config.toList(),
|
||||||
extraSearchParams = extraParam
|
extraSearchParams = extraParam
|
||||||
)
|
)
|
||||||
).withFadeTransaction().tag(MigrationListController.TAG))
|
).withFadeTransaction().tag(MigrationListController.TAG)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@ -136,8 +142,11 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
.filter { it.lang in languages }
|
.filter { it.lang in languages }
|
||||||
.sortedBy { "(${it.lang}) ${it.name}" }
|
.sortedBy { "(${it.lang}) ${it.name}" }
|
||||||
sources =
|
sources =
|
||||||
sources.filter { isEnabled(it.id.toString()) }.sortedBy { sourcesSaved.indexOf(it.id
|
sources.filter { isEnabled(it.id.toString()) }.sortedBy {
|
||||||
.toString())
|
sourcesSaved.indexOf(
|
||||||
|
it.id
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
} +
|
} +
|
||||||
sources.filterNot { isEnabled(it.id.toString()) }
|
sources.filterNot { isEnabled(it.id.toString()) }
|
||||||
|
|
||||||
@ -167,9 +176,11 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun create(mangaIds: List<Long>): PreMigrationController {
|
fun create(mangaIds: List<Long>): PreMigrationController {
|
||||||
return PreMigrationController(Bundle().apply {
|
return PreMigrationController(
|
||||||
|
Bundle().apply {
|
||||||
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
|
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,10 @@ import rx.schedulers.Schedulers
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MigrationListController(bundle: Bundle? = null) : BaseController<MigrationListControllerBinding>(bundle),
|
class MigrationListController(bundle: Bundle? = null) :
|
||||||
MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope {
|
BaseController<MigrationListControllerBinding>(bundle),
|
||||||
|
MigrationProcessAdapter.MigrationProcessInterface,
|
||||||
|
CoroutineScope {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
@ -93,7 +95,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
|
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
view.applyWindowInsetsForController()
|
view.applyWindowInsetsForController()
|
||||||
setTitle()
|
setTitle()
|
||||||
@ -217,7 +218,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
|
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
|
||||||
val chapters = try {
|
val chapters = try {
|
||||||
source.fetchChapterList(localManga)
|
source.fetchChapterList(localManga)
|
||||||
.toSingle().await(Schedulers.io()) } catch (e: java.lang.Exception) {
|
.toSingle().await(Schedulers.io())
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
emptyList<SChapter>()
|
emptyList<SChapter>()
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
@ -313,7 +315,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemClick(position: Int, item: MenuItem) {
|
override fun onMenuItemClick(position: Int, item: MenuItem) {
|
||||||
|
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_search_manually -> {
|
R.id.action_search_manually -> {
|
||||||
launchUI {
|
launchUI {
|
||||||
@ -488,9 +489,11 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
const val TAG = "migration_list"
|
const val TAG = "migration_list"
|
||||||
|
|
||||||
fun create(config: MigrationProcedureConfig): MigrationListController {
|
fun create(config: MigrationProcedureConfig): MigrationListController {
|
||||||
return MigrationListController(Bundle().apply {
|
return MigrationListController(
|
||||||
|
Bundle().apply {
|
||||||
putParcelable(CONFIG_EXTRA, config)
|
putParcelable(CONFIG_EXTRA, config)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,12 @@ class MigrationProcessAdapter(
|
|||||||
if (allMangasDone()) menuItemListener.enableButtons()
|
if (allMangasDone()) menuItemListener.enableButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allMangasDone() = (items.all { it.manga.migrationStatus != MigrationStatus
|
fun allMangasDone() = (
|
||||||
.RUNNUNG } && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND })
|
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 })
|
fun mangasSkipped() = (items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND })
|
||||||
|
|
||||||
@ -59,7 +63,8 @@ class MigrationProcessAdapter(
|
|||||||
migrateMangaInternal(
|
migrateMangaInternal(
|
||||||
manga.manga() ?: return@forEach,
|
manga.manga() ?: return@forEach,
|
||||||
toMangaObj,
|
toMangaObj,
|
||||||
!copy)
|
!copy
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,18 @@ class MigrationProcessHolder(
|
|||||||
val manga = item.manga.manga()
|
val manga = item.manga.manga()
|
||||||
val source = item.manga.mangaSource()
|
val source = item.manga.mangaSource()
|
||||||
|
|
||||||
migration_menu.setVectorCompat(R.drawable.ic_more_vert_24dp, view.context
|
migration_menu.setVectorCompat(
|
||||||
.getResourceColor(R.attr.colorOnPrimary))
|
R.drawable.ic_more_vert_24dp,
|
||||||
skip_manga.setVectorCompat(R.drawable.ic_close_24dp, view.context.getResourceColor(R
|
view.context
|
||||||
.attr.colorOnPrimary))
|
.getResourceColor(R.attr.colorOnPrimary)
|
||||||
|
)
|
||||||
|
skip_manga.setVectorCompat(
|
||||||
|
R.drawable.ic_close_24dp,
|
||||||
|
view.context.getResourceColor(
|
||||||
|
R
|
||||||
|
.attr.colorOnPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
migration_menu.invisible()
|
migration_menu.invisible()
|
||||||
skip_manga.visible()
|
skip_manga.visible()
|
||||||
migration_manga_card_to.resetManga()
|
migration_manga_card_to.resetManga()
|
||||||
@ -87,7 +95,8 @@ class MigrationProcessHolder(
|
|||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
|
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
|
||||||
item.manga.migrationStatus == MigrationStatus.RUNNUNG) {
|
item.manga.migrationStatus == MigrationStatus.RUNNUNG
|
||||||
|
) {
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
if (searchResult != null && resultSource != null) {
|
if (searchResult != null && resultSource != null) {
|
||||||
@ -152,11 +161,15 @@ class MigrationProcessHolder(
|
|||||||
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
||||||
|
|
||||||
if (latestChapter > 0f) {
|
if (latestChapter > 0f) {
|
||||||
manga_last_chapter_label.text = context.getString(R.string.latest_,
|
manga_last_chapter_label.text = context.getString(
|
||||||
DecimalFormat("#.#").format(latestChapter))
|
R.string.latest_,
|
||||||
|
DecimalFormat("#.#").format(latestChapter)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
manga_last_chapter_label.text = context.getString(R.string.latest_,
|
manga_last_chapter_label.text = context.getString(
|
||||||
context.getString(R.string.unknown))
|
R.string.latest_,
|
||||||
|
context.getString(R.string.unknown)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +98,11 @@ class SettingsEhController : SettingsController() {
|
|||||||
preferences.enableExhentai().set(false)
|
preferences.enableExhentai().set(false)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
router.pushController(RouterTransaction.with(LoginController())
|
router.pushController(
|
||||||
|
RouterTransaction.with(LoginController())
|
||||||
.pushChangeHandler(FadeChangeHandler())
|
.pushChangeHandler(FadeChangeHandler())
|
||||||
.popChangeHandler(FadeChangeHandler()))
|
.popChangeHandler(FadeChangeHandler())
|
||||||
|
)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,10 +75,12 @@ class SettingsLibraryController : SettingsController() {
|
|||||||
intListPreference {
|
intListPreference {
|
||||||
key = Keys.eh_library_rounded_corners
|
key = Keys.eh_library_rounded_corners
|
||||||
title = "Rounded Corner Radius"
|
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_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_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")
|
entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
|
||||||
defaultValue = "4"
|
defaultValue = "4"
|
||||||
summaryRes = R.string.eh_rounded_corners_desc
|
summaryRes = R.string.eh_rounded_corners_desc
|
||||||
@ -211,7 +213,8 @@ class SettingsLibraryController : SettingsController() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (preferences.skipPreMigration().get() || preferences.migrationSources()
|
if (preferences.skipPreMigration().get() || preferences.migrationSources()
|
||||||
.getOrDefault().isNotEmpty()) {
|
.getOrDefault().isNotEmpty()
|
||||||
|
) {
|
||||||
switchPreference {
|
switchPreference {
|
||||||
key = Keys.skipPreMigration
|
key = Keys.skipPreMigration
|
||||||
titleRes = R.string.pref_skip_pre_migration
|
titleRes = R.string.pref_skip_pre_migration
|
||||||
|
@ -45,22 +45,28 @@ object EXHMigrations {
|
|||||||
if (oldVersion < 1) {
|
if (oldVersion < 1) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
// Migrate HentaiCafe source IDs
|
// Migrate HentaiCafe source IDs
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
UPDATE ${MangaTable.TABLE}
|
UPDATE ${MangaTable.TABLE}
|
||||||
SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID
|
SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID
|
||||||
WHERE ${MangaTable.COL_SOURCE} = 6908
|
WHERE ${MangaTable.COL_SOURCE} = 6908
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
// Migrate nhentai URLs
|
// Migrate nhentai URLs
|
||||||
val nhentaiManga = db.db.get()
|
val nhentaiManga = db.db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID")
|
.where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID")
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
.executeAsBlocking()
|
.executeAsBlocking()
|
||||||
|
|
||||||
@ -85,14 +91,18 @@ object EXHMigrations {
|
|||||||
if (oldVersion < 8405) {
|
if (oldVersion < 8405) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
// Migrate HBrowse source IDs
|
// Migrate HBrowse source IDs
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
UPDATE ${MangaTable.TABLE}
|
UPDATE ${MangaTable.TABLE}
|
||||||
SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID
|
SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID
|
||||||
WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222
|
WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel old scheduler jobs with old ids
|
// Cancel old scheduler jobs with old ids
|
||||||
@ -101,14 +111,18 @@ object EXHMigrations {
|
|||||||
if (oldVersion < 8408) {
|
if (oldVersion < 8408) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
// Migrate Tsumino source IDs
|
// Migrate Tsumino source IDs
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
UPDATE ${MangaTable.TABLE}
|
UPDATE ${MangaTable.TABLE}
|
||||||
SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID
|
SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID
|
||||||
WHERE ${MangaTable.COL_SOURCE} = 6909
|
WHERE ${MangaTable.COL_SOURCE} = 6909
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 8409) {
|
if (oldVersion < 8409) {
|
||||||
@ -214,10 +228,12 @@ object EXHMigrations {
|
|||||||
return try {
|
return try {
|
||||||
val uri = URI(orig)
|
val uri = URI(orig)
|
||||||
var out = uri.path
|
var out = uri.path
|
||||||
if (uri.query != null)
|
if (uri.query != null) {
|
||||||
out += "?" + uri.query
|
out += "?" + uri.query
|
||||||
if (uri.fragment != null)
|
}
|
||||||
|
if (uri.fragment != null) {
|
||||||
out += "#" + uri.fragment
|
out += "#" + uri.fragment
|
||||||
|
}
|
||||||
out
|
out
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
orig
|
orig
|
||||||
|
@ -111,8 +111,10 @@ class GalleryAdder {
|
|||||||
return GalleryAddEvent.Fail.NotFound(url)
|
return GalleryAddEvent.Fail.NotFound(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GalleryAddEvent.Fail.Error(url,
|
return GalleryAddEvent.Fail.Error(
|
||||||
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim())
|
url,
|
||||||
|
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,9 @@ object DebugFunctions {
|
|||||||
val metadataManga = db.getFavoriteMangaWithMetadata().await()
|
val metadataManga = db.getFavoriteMangaWithMetadata().await()
|
||||||
|
|
||||||
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||||
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID)
|
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
}
|
||||||
manga
|
manga
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
@ -56,13 +57,17 @@ object DebugFunctions {
|
|||||||
|
|
||||||
fun addAllMangaInDatabaseToLibrary() {
|
fun addAllMangaInDatabaseToLibrary() {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
UPDATE ${MangaTable.TABLE}
|
UPDATE ${MangaTable.TABLE}
|
||||||
SET ${MangaTable.COL_FAVORITE} = 1
|
SET ${MangaTable.COL_FAVORITE} = 1
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,13 +115,17 @@ object DebugFunctions {
|
|||||||
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
|
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
|
||||||
|
|
||||||
private fun convertSources(from: Long, to: Long) {
|
private fun convertSources(from: Long, to: Long) {
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
|
.query(
|
||||||
|
"""
|
||||||
UPDATE ${MangaTable.TABLE}
|
UPDATE ${MangaTable.TABLE}
|
||||||
SET ${MangaTable.COL_SOURCE} = $to
|
SET ${MangaTable.COL_SOURCE} = $to
|
||||||
WHERE ${MangaTable.COL_SOURCE} = $from
|
WHERE ${MangaTable.COL_SOURCE} = $from
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,13 @@ class EHentaiThrottleManager(
|
|||||||
// Throttle requests if necessary
|
// Throttle requests if necessary
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val timeDiff = now - lastThrottleTime
|
val timeDiff = now - lastThrottleTime
|
||||||
if (timeDiff < throttleTime)
|
if (timeDiff < throttleTime) {
|
||||||
Thread.sleep(throttleTime - timeDiff)
|
Thread.sleep(throttleTime - timeDiff)
|
||||||
|
}
|
||||||
|
|
||||||
if (throttleTime < max)
|
if (throttleTime < max) {
|
||||||
throttleTime += inc
|
throttleTime += inc
|
||||||
|
}
|
||||||
|
|
||||||
lastThrottleTime = System.currentTimeMillis()
|
lastThrottleTime = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,11 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun findAcceptedRootAndDiscardOthers(sourceId: Long, chapters: List<Chapter>): Single<Triple<ChapterChain, List<ChapterChain>, Boolean>> {
|
fun findAcceptedRootAndDiscardOthers(sourceId: Long, chapters: List<Chapter>): Single<Triple<ChapterChain, List<ChapterChain>, Boolean>> {
|
||||||
// Find other chains
|
// Find other chains
|
||||||
val chainsObservable = Observable.merge(chapters.map { chapter ->
|
val chainsObservable = Observable.merge(
|
||||||
|
chapters.map { chapter ->
|
||||||
db.getChapters(chapter.url).asRxSingle().toObservable()
|
db.getChapters(chapter.url).asRxSingle().toObservable()
|
||||||
}).toList().map { allChapters ->
|
}
|
||||||
|
).toList().map { allChapters ->
|
||||||
allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct()
|
allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct()
|
||||||
}.flatMap { mangaIds ->
|
}.flatMap { mangaIds ->
|
||||||
Observable.merge(
|
Observable.merge(
|
||||||
@ -77,7 +79,8 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
// Convert old style chapters to new style chapters if possible
|
// Convert old style chapters to new style chapters if possible
|
||||||
if (chapter.date_upload <= 0 &&
|
if (chapter.date_upload <= 0 &&
|
||||||
meta?.datePosted != null &&
|
meta?.datePosted != null &&
|
||||||
meta?.title != null) {
|
meta?.title != null
|
||||||
|
) {
|
||||||
chapter.name = meta!!.title!!
|
chapter.name = meta!!.title!!
|
||||||
chapter.date_upload = meta!!.datePosted!!
|
chapter.date_upload = meta!!.datePosted!!
|
||||||
}
|
}
|
||||||
|
@ -137,8 +137,9 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
logger.d("Filtering manga and raising metadata...")
|
logger.d("Filtering manga and raising metadata...")
|
||||||
val curTime = System.currentTimeMillis()
|
val curTime = System.currentTimeMillis()
|
||||||
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||||
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID)
|
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
val meta = db.getFlatMetadataForManga(manga.id!!).asRxSingle().await()
|
val meta = db.getFlatMetadataForManga(manga.id!!).asRxSingle().await()
|
||||||
?: return@mapNotNull null
|
?: return@mapNotNull null
|
||||||
@ -146,8 +147,9 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
|
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
|
||||||
|
|
||||||
// Don't update galleries too frequently
|
// Don't update galleries too frequently
|
||||||
if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled))
|
if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled)) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minBy {
|
val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minBy {
|
||||||
it.date_upload
|
it.date_upload
|
||||||
@ -172,13 +174,15 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.d("Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...",
|
logger.d(
|
||||||
|
"Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...",
|
||||||
index,
|
index,
|
||||||
manga.id,
|
manga.id,
|
||||||
meta.gId,
|
meta.gId,
|
||||||
meta.gToken,
|
meta.gToken,
|
||||||
failuresThisIteration,
|
failuresThisIteration,
|
||||||
modifiedThisIteration.size)
|
modifiedThisIteration.size
|
||||||
|
)
|
||||||
|
|
||||||
if (manga.id in modifiedThisIteration) {
|
if (manga.id in modifiedThisIteration) {
|
||||||
// We already processed this manga!
|
// We already processed this manga!
|
||||||
@ -194,22 +198,26 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
failuresThisIteration++
|
failuresThisIteration++
|
||||||
|
|
||||||
logger.e("> Network error while updating gallery!", e)
|
logger.e("> Network error while updating gallery!", e)
|
||||||
logger.e("> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)",
|
logger.e(
|
||||||
|
"> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)",
|
||||||
manga.id,
|
manga.id,
|
||||||
meta.gId,
|
meta.gId,
|
||||||
meta.gToken,
|
meta.gToken,
|
||||||
failuresThisIteration)
|
failuresThisIteration
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chapters.isEmpty()) {
|
if (chapters.isEmpty()) {
|
||||||
logger.e("No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!",
|
logger.e(
|
||||||
|
"No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!",
|
||||||
manga.id,
|
manga.id,
|
||||||
meta.gId,
|
meta.gId,
|
||||||
meta.gToken,
|
meta.gToken,
|
||||||
failuresThisIteration)
|
failuresThisIteration
|
||||||
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -219,7 +227,8 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await()
|
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await()
|
||||||
|
|
||||||
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
|
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
|
||||||
(hasNew && updatedManga.none { it.id == acceptedRoot.manga.id })) {
|
(hasNew && updatedManga.none { it.id == acceptedRoot.manga.id })
|
||||||
|
) {
|
||||||
updatedManga += acceptedRoot.manga
|
updatedManga += acceptedRoot.manga
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,7 +298,9 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder {
|
private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder {
|
||||||
return JobInfo.Builder(
|
return JobInfo.Builder(
|
||||||
if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST
|
if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST
|
||||||
else JOB_ID_UPDATE_BACKGROUND, componentName())
|
else JOB_ID_UPDATE_BACKGROUND,
|
||||||
|
componentName()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.periodicBackgroundJobInfo(
|
private fun Context.periodicBackgroundJobInfo(
|
||||||
@ -302,14 +313,17 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
.setPersisted(true)
|
.setPersisted(true)
|
||||||
.setRequiredNetworkType(
|
.setRequiredNetworkType(
|
||||||
if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED
|
if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED
|
||||||
else JobInfo.NETWORK_TYPE_ANY)
|
else JobInfo.NETWORK_TYPE_ANY
|
||||||
|
)
|
||||||
.apply {
|
.apply {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
setRequiresBatteryNotLow(true)
|
setRequiresBatteryNotLow(true)
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
setEstimatedNetworkBytes(15000L * UPDATES_PER_ITERATION,
|
setEstimatedNetworkBytes(
|
||||||
1000L * UPDATES_PER_ITERATION)
|
15000L * UPDATES_PER_ITERATION,
|
||||||
|
1000L * UPDATES_PER_ITERATION
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setRequiresCharging(requireCharging)
|
.setRequiresCharging(requireCharging)
|
||||||
|
@ -18,7 +18,8 @@ class FavoritesIntroDialog {
|
|||||||
.cancelable(false)
|
.cancelable(false)
|
||||||
.show()
|
.show()
|
||||||
|
|
||||||
private val FAVORITES_INTRO_TEXT = """
|
private val FAVORITES_INTRO_TEXT =
|
||||||
|
"""
|
||||||
1. Changes to category names in the app are <b>NOT</b> synced! Please <i>change the category names on ExHentai instead</i>. The category names will be copied from the ExHentai servers every sync.
|
1. Changes to category names in the app are <b>NOT</b> synced! Please <i>change the category names on ExHentai instead</i>. The category names will be copied from the ExHentai servers every sync.
|
||||||
<br><br>
|
<br><br>
|
||||||
2. The favorite categories on ExHentai correspond to the <b>first 10 categories in the app</b> (excluding the 'Default' category). <i>Galleries in other categories will <b>NOT</b> be synced!</i>
|
2. The favorite categories on ExHentai correspond to the <b>first 10 categories in the app</b> (excluding the 'Default' category). <i>Galleries in other categories will <b>NOT</b> be synced!</i>
|
||||||
|
@ -82,8 +82,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
|
|
||||||
if (it.id in seenManga) {
|
if (it.id in seenManga) {
|
||||||
val inCategories = db.getCategoriesForManga(it).executeAsBlocking()
|
val inCategories = db.getCategoriesForManga(it).executeAsBlocking()
|
||||||
status.onNext(FavoritesSyncStatus.BadLibraryState
|
status.onNext(
|
||||||
.MangaInMultipleCategories(it, inCategories))
|
FavoritesSyncStatus.BadLibraryState
|
||||||
|
.MangaInMultipleCategories(it, inCategories)
|
||||||
|
)
|
||||||
logger.w("Manga %s is in multiple categories!", it.id)
|
logger.w("Manga %s is in multiple categories!", it.id)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@ -107,13 +109,17 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Take wake + wifi locks
|
// Take wake + wifi locks
|
||||||
ignore { wakeLock?.release() }
|
ignore { wakeLock?.release() }
|
||||||
wakeLock = ignore {
|
wakeLock = ignore {
|
||||||
context.powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
context.powerManager.newWakeLock(
|
||||||
"teh:ExhFavoritesSyncWakelock")
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
"teh:ExhFavoritesSyncWakelock"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ignore { wifiLock?.release() }
|
ignore { wifiLock?.release() }
|
||||||
wifiLock = ignore {
|
wifiLock = ignore {
|
||||||
context.wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL,
|
context.wifiManager.createWifiLock(
|
||||||
"teh:ExhFavoritesSyncWifi")
|
WifiManager.WIFI_MODE_FULL,
|
||||||
|
"teh:ExhFavoritesSyncWifi"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not update galleries while syncing favorites
|
// Do not update galleries while syncing favorites
|
||||||
@ -137,8 +143,9 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
|
|
||||||
// Apply change sets
|
// Apply change sets
|
||||||
applyChangeSetToLocal(errorList, remoteChanges)
|
applyChangeSetToLocal(errorList, remoteChanges)
|
||||||
if (localChanges != null)
|
if (localChanges != null) {
|
||||||
applyChangeSetToRemote(errorList, localChanges)
|
applyChangeSetToRemote(errorList, localChanges)
|
||||||
|
}
|
||||||
|
|
||||||
status.onNext(FavoritesSyncStatus.Processing("Cleaning up"))
|
status.onNext(FavoritesSyncStatus.Processing("Cleaning up"))
|
||||||
storage.snapshotEntries(realm)
|
storage.snapshotEntries(realm)
|
||||||
@ -173,11 +180,12 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
EHentaiUpdateWorker.scheduleBackground(context)
|
EHentaiUpdateWorker.scheduleBackground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorList.isEmpty())
|
if (errorList.isEmpty()) {
|
||||||
status.onNext(FavoritesSyncStatus.Idle())
|
status.onNext(FavoritesSyncStatus.Idle())
|
||||||
else
|
} else {
|
||||||
status.onNext(FavoritesSyncStatus.CompleteWithErrors(errorList))
|
status.onNext(FavoritesSyncStatus.CompleteWithErrors(errorList))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun applyRemoteCategories(errorList: MutableList<String>, categories: List<String>) {
|
private fun applyRemoteCategories(errorList: MutableList<String>, categories: List<String>) {
|
||||||
val localCategories = db.getCategories().executeAsBlocking()
|
val localCategories = db.getCategories().executeAsBlocking()
|
||||||
@ -217,21 +225,24 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only insert categories if changed
|
// Only insert categories if changed
|
||||||
if (changed)
|
if (changed) {
|
||||||
db.insertCategories(newLocalCategories).executeAsBlocking()
|
db.insertCategories(newLocalCategories).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) {
|
private fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) {
|
||||||
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
|
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.post(FormBody.Builder()
|
.post(
|
||||||
|
FormBody.Builder()
|
||||||
.add("favcat", gallery.category.toString())
|
.add("favcat", gallery.category.toString())
|
||||||
.add("favnote", "")
|
.add("favnote", "")
|
||||||
.add("apply", "Add to Favorites")
|
.add("apply", "Add to Favorites")
|
||||||
.add("update", "1")
|
.add("update", "1")
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
if (!explicitlyRetryExhRequest(10, request)) {
|
if (!explicitlyRetryExhRequest(10, request)) {
|
||||||
@ -299,8 +310,12 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Apply additions
|
// Apply additions
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
changeSet.added.forEachIndexed { index, it ->
|
changeSet.added.forEachIndexed { index, it ->
|
||||||
status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to remote server",
|
status.onNext(
|
||||||
needWarnThrottle()))
|
FavoritesSyncStatus.Processing(
|
||||||
|
"Adding gallery ${index + 1} of ${changeSet.added.size} to remote server",
|
||||||
|
needWarnThrottle()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
throttleManager.throttle()
|
throttleManager.throttle()
|
||||||
|
|
||||||
@ -317,8 +332,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
val url = it.getUrl()
|
val url = it.getUrl()
|
||||||
|
|
||||||
// Consider both EX and EH sources
|
// Consider both EX and EH sources
|
||||||
listOf(db.getManga(url, EXH_SOURCE_ID),
|
listOf(
|
||||||
db.getManga(url, EH_SOURCE_ID)).forEach {
|
db.getManga(url, EXH_SOURCE_ID),
|
||||||
|
db.getManga(url, EH_SOURCE_ID)
|
||||||
|
).forEach {
|
||||||
val manga = it.executeAsBlocking()
|
val manga = it.executeAsBlocking()
|
||||||
|
|
||||||
if (manga?.favorite == true) {
|
if (manga?.favorite == true) {
|
||||||
@ -340,16 +357,22 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Apply additions
|
// Apply additions
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
changeSet.added.forEachIndexed { index, it ->
|
changeSet.added.forEachIndexed { index, it ->
|
||||||
status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to local library",
|
status.onNext(
|
||||||
needWarnThrottle()))
|
FavoritesSyncStatus.Processing(
|
||||||
|
"Adding gallery ${index + 1} of ${changeSet.added.size} to local library",
|
||||||
|
needWarnThrottle()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
throttleManager.throttle()
|
throttleManager.throttle()
|
||||||
|
|
||||||
// Import using gallery adder
|
// Import using gallery adder
|
||||||
val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}",
|
val result = galleryAdder.addGallery(
|
||||||
|
"${exh.baseUrl}${it.getUrl()}",
|
||||||
true,
|
true,
|
||||||
exh,
|
exh,
|
||||||
throttleManager::throttle)
|
throttleManager::throttle
|
||||||
|
)
|
||||||
|
|
||||||
if (result is GalleryAddEvent.Fail) {
|
if (result is GalleryAddEvent.Fail) {
|
||||||
if (result is GalleryAddEvent.Fail.NotFound) {
|
if (result is GalleryAddEvent.Fail.NotFound) {
|
||||||
@ -370,8 +393,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
throw IgnoredException()
|
throw IgnoredException()
|
||||||
}
|
}
|
||||||
} else if (result is GalleryAddEvent.Success) {
|
} else if (result is GalleryAddEvent.Success) {
|
||||||
insertedMangaCategories += MangaCategory.create(result.manga,
|
insertedMangaCategories += MangaCategory.create(
|
||||||
categories[it.category]) to result.manga
|
result.manga,
|
||||||
|
categories[it.category]
|
||||||
|
) to result.manga
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,9 +429,12 @@ sealed class FavoritesSyncStatus(val message: String) {
|
|||||||
BadLibraryState("The gallery: ${manga.title} is in more than one category (${categories.joinToString { it.name }})!")
|
BadLibraryState("The gallery: ${manga.title} is in more than one category (${categories.joinToString { it.name }})!")
|
||||||
}
|
}
|
||||||
class Initializing : FavoritesSyncStatus("Initializing sync")
|
class Initializing : FavoritesSyncStatus("Initializing sync")
|
||||||
class Processing(message: String, isThrottle: Boolean = false) : FavoritesSyncStatus(if (isThrottle)
|
class Processing(message: String, isThrottle: Boolean = false) : FavoritesSyncStatus(
|
||||||
|
if (isThrottle) {
|
||||||
"$message\n\nSync is currently throttling (to avoid being banned from ExHentai) and may take a long time to complete."
|
"$message\n\nSync is currently throttling (to avoid being banned from ExHentai) and may take a long time to complete."
|
||||||
else
|
} else {
|
||||||
message)
|
message
|
||||||
|
}
|
||||||
|
)
|
||||||
class CompleteWithErrors(messages: List<String>) : FavoritesSyncStatus(messages.joinToString("\n"))
|
class CompleteWithErrors(messages: List<String>) : FavoritesSyncStatus(messages.joinToString("\n"))
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@ class LocalFavoritesStorage {
|
|||||||
fun getRealm() = Realm.getInstance(realmConfig)
|
fun getRealm() = Realm.getInstance(realmConfig)
|
||||||
|
|
||||||
fun getChangedDbEntries(realm: Realm) =
|
fun getChangedDbEntries(realm: Realm) =
|
||||||
getChangedEntries(realm,
|
getChangedEntries(
|
||||||
|
realm,
|
||||||
parseToFavoriteEntries(
|
parseToFavoriteEntries(
|
||||||
loadDbCategories(
|
loadDbCategories(
|
||||||
db.getFavoriteMangas()
|
db.getFavoriteMangas()
|
||||||
@ -32,12 +33,16 @@ class LocalFavoritesStorage {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) =
|
fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) =
|
||||||
getChangedEntries(realm,
|
getChangedEntries(
|
||||||
|
realm,
|
||||||
parseToFavoriteEntries(
|
parseToFavoriteEntries(
|
||||||
entries.asSequence().map {
|
entries.asSequence().map {
|
||||||
Pair(it.fav, it.manga.apply {
|
Pair(
|
||||||
|
it.fav,
|
||||||
|
it.manga.apply {
|
||||||
favorite = true
|
favorite = true
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -100,8 +105,13 @@ class LocalFavoritesStorage {
|
|||||||
return manga.filter(this::validateDbManga).mapNotNull {
|
return manga.filter(this::validateDbManga).mapNotNull {
|
||||||
val category = db.getCategoriesForManga(it).executeAsBlocking()
|
val category = db.getCategoriesForManga(it).executeAsBlocking()
|
||||||
|
|
||||||
Pair(dbCategories.indexOf(category.firstOrNull()
|
Pair(
|
||||||
?: return@mapNotNull null), it)
|
dbCategories.indexOf(
|
||||||
|
category.firstOrNull()
|
||||||
|
?: return@mapNotNull null
|
||||||
|
),
|
||||||
|
it
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,10 +125,11 @@ class LocalFavoritesStorage {
|
|||||||
token = EHentaiSearchMetadata.galleryToken(it.second.url)
|
token = EHentaiSearchMetadata.galleryToken(it.second.url)
|
||||||
category = it.first
|
category = it.first
|
||||||
|
|
||||||
if (this.category > MAX_CATEGORIES)
|
if (this.category > MAX_CATEGORIES) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun validateDbManga(manga: Manga) =
|
private fun validateDbManga(manga: Manga) =
|
||||||
manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID)
|
manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID)
|
||||||
|
@ -71,13 +71,15 @@ class HitomiNozomi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getGalleryIdsFromData(data: DataPair?): Single<List<Int>> {
|
private fun getGalleryIdsFromData(data: DataPair?): Single<List<Int>> {
|
||||||
if (data == null)
|
if (data == null) {
|
||||||
return Single.just(emptyList())
|
return Single.just(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
val url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.data"
|
val url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.data"
|
||||||
val (offset, length) = data
|
val (offset, length) = data
|
||||||
if (length > 100000000 || length <= 0)
|
if (length > 100000000 || length <= 0) {
|
||||||
return Single.just(emptyList())
|
return Single.just(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
return client.newCall(rangedGet(url, offset, offset + length - 1))
|
return client.newCall(rangedGet(url, offset, offset + length - 1))
|
||||||
.asObservable()
|
.asObservable()
|
||||||
@ -86,8 +88,9 @@ class HitomiNozomi(
|
|||||||
}
|
}
|
||||||
.onErrorReturn { ByteArray(0) }
|
.onErrorReturn { ByteArray(0) }
|
||||||
.map { inbuf ->
|
.map { inbuf ->
|
||||||
if (inbuf.isEmpty())
|
if (inbuf.isEmpty()) {
|
||||||
return@map emptyList<Int>()
|
return@map emptyList<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
val view = ByteCursor(inbuf)
|
val view = ByteCursor(inbuf)
|
||||||
val numberOfGalleryIds = view.nextInt()
|
val numberOfGalleryIds = view.nextInt()
|
||||||
@ -96,7 +99,8 @@ class HitomiNozomi(
|
|||||||
|
|
||||||
if (numberOfGalleryIds > 10000000 ||
|
if (numberOfGalleryIds > 10000000 ||
|
||||||
numberOfGalleryIds <= 0 ||
|
numberOfGalleryIds <= 0 ||
|
||||||
inbuf.size != expectedLength) {
|
inbuf.size != expectedLength
|
||||||
|
) {
|
||||||
return@map emptyList<Int>()
|
return@map emptyList<Int>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,11 +116,12 @@ class HitomiNozomi(
|
|||||||
for (i in 0 until top) {
|
for (i in 0 until top) {
|
||||||
val dv1i = dv1[i].toInt() and 0xFF
|
val dv1i = dv1[i].toInt() and 0xFF
|
||||||
val dv2i = dv2[i].toInt() and 0xFF
|
val dv2i = dv2[i].toInt() and 0xFF
|
||||||
if (dv1i < dv2i)
|
if (dv1i < dv2i) {
|
||||||
return -1
|
return -1
|
||||||
else if (dv1i > dv2i)
|
} else if (dv1i > dv2i) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,9 +208,11 @@ class HitomiNozomi(
|
|||||||
nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION"
|
nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.newCall(Request.Builder()
|
return client.newCall(
|
||||||
|
Request.Builder()
|
||||||
.url(nozomiAddress)
|
.url(nozomiAddress)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { resp ->
|
.map { resp ->
|
||||||
val body = resp.body!!.bytes()
|
val body = resp.body!!.bytes()
|
||||||
@ -233,9 +240,12 @@ class HitomiNozomi(
|
|||||||
private val HASH_CHARSET = Charsets.UTF_8
|
private val HASH_CHARSET = Charsets.UTF_8
|
||||||
|
|
||||||
fun rangedGet(url: String, rangeBegin: Long, rangeEnd: Long?): Request {
|
fun rangedGet(url: String, rangeBegin: Long, rangeEnd: Long?): Request {
|
||||||
return GET(url, Headers.Builder()
|
return GET(
|
||||||
|
url,
|
||||||
|
Headers.Builder()
|
||||||
.add("Range", "bytes=$rangeBegin-${rangeEnd ?: ""}")
|
.add("Range", "bytes=$rangeBegin-${rangeEnd ?: ""}")
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable<Long> {
|
fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable<Long> {
|
||||||
|
@ -50,7 +50,8 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule<String>(n
|
|||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildInfo() = """
|
fun buildInfo() =
|
||||||
|
"""
|
||||||
<font color='green'>===[ ${context.getString(R.string.app_name)} ]===</font><br>
|
<font color='green'>===[ ${context.getString(R.string.app_name)} ]===</font><br>
|
||||||
<b>Build type:</b> ${BuildConfig.BUILD_TYPE}<br>
|
<b>Build type:</b> ${BuildConfig.BUILD_TYPE}<br>
|
||||||
<b>Debug mode:</b> ${BuildConfig.DEBUG.asEnabledString()}<br>
|
<b>Debug mode:</b> ${BuildConfig.DEBUG.asEnabledString()}<br>
|
||||||
|
@ -35,10 +35,11 @@ fun parseHumanReadableByteCount(arg0: String): Double? {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String?.nullIfBlank(): String? = if (isNullOrBlank())
|
fun String?.nullIfBlank(): String? = if (isNullOrBlank()) {
|
||||||
null
|
null
|
||||||
else
|
} else {
|
||||||
this
|
this
|
||||||
|
}
|
||||||
|
|
||||||
fun <K, V> Set<Map.Entry<K, V>>.forEach(action: (K, V) -> Unit) {
|
fun <K, V> Set<Map.Entry<K, V>>.forEach(action: (K, V) -> Unit) {
|
||||||
forEach { action(it.key, it.value) }
|
forEach { action(it.key, it.value) }
|
||||||
|
@ -50,10 +50,11 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
thumbnailUrl?.let { manga.thumbnail_url = it }
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
|
||||||
// No title bug?
|
// No title bug?
|
||||||
val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault())
|
val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault()) {
|
||||||
altTitle ?: title
|
altTitle ?: title
|
||||||
else
|
} else {
|
||||||
title
|
title
|
||||||
|
}
|
||||||
titleObj?.let { manga.title = it }
|
titleObj?.let { manga.title = it }
|
||||||
|
|
||||||
// Set artist (if we can find one)
|
// Set artist (if we can find one)
|
||||||
@ -119,10 +120,11 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
private fun splitGalleryUrl(url: String) =
|
private fun splitGalleryUrl(url: String) =
|
||||||
url.let {
|
url.let {
|
||||||
// Only parse URL if is full URL
|
// Only parse URL if is full URL
|
||||||
val pathSegments = if (it.startsWith("http"))
|
val pathSegments = if (it.startsWith("http")) {
|
||||||
Uri.parse(it).pathSegments
|
Uri.parse(it).pathSegments
|
||||||
else
|
} else {
|
||||||
it.split('/')
|
it.split('/')
|
||||||
|
}
|
||||||
pathSegments.filterNot(String::isNullOrBlank)
|
pathSegments.filterNot(String::isNullOrBlank)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +62,13 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
detailsDesc += "Language: ${it.capitalize()}\n"
|
detailsDesc += "Language: ${it.capitalize()}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (series.isNotEmpty())
|
if (series.isNotEmpty()) {
|
||||||
detailsDesc += "Series: ${series.joinToString()}\n"
|
detailsDesc += "Series: ${series.joinToString()}\n"
|
||||||
|
}
|
||||||
|
|
||||||
if (characters.isNotEmpty())
|
if (characters.isNotEmpty()) {
|
||||||
detailsDesc += "Characters: ${characters.joinToString()}\n"
|
detailsDesc += "Characters: ${characters.joinToString()}\n"
|
||||||
|
}
|
||||||
|
|
||||||
uploadDate?.let {
|
uploadDate?.let {
|
||||||
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
|
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
|
||||||
|
@ -44,9 +44,11 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
val hqThumbs = Injekt.get<PreferencesHelper>().eh_nh_useHighQualityThumbs().getOrDefault()
|
val hqThumbs = Injekt.get<PreferencesHelper>().eh_nh_useHighQualityThumbs().getOrDefault()
|
||||||
typeToExtension(if (hqThumbs) coverImageType else thumbnailImageType)?.let {
|
typeToExtension(if (hqThumbs) coverImageType else thumbnailImageType)?.let {
|
||||||
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs)
|
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs) {
|
||||||
"cover"
|
"cover"
|
||||||
else "thumb"}.$it"
|
} else {
|
||||||
|
"thumb"
|
||||||
|
}}.$it"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,11 +41,12 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
|
|||||||
manga.title = it
|
manga.title = it
|
||||||
titleDesc += "Title: $it\n"
|
titleDesc += "Title: $it\n"
|
||||||
}
|
}
|
||||||
if (altTitles.isNotEmpty())
|
if (altTitles.isNotEmpty()) {
|
||||||
titleDesc += "Alternate Titles: \n" + altTitles
|
titleDesc += "Alternate Titles: \n" + altTitles
|
||||||
.joinToString(separator = "\n", postfix = "\n") {
|
.joinToString(separator = "\n", postfix = "\n") {
|
||||||
"▪ $it"
|
"▪ $it"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
val detailsDesc = StringBuilder()
|
||||||
artist?.let {
|
artist?.let {
|
||||||
|
@ -10,19 +10,23 @@ import exh.metadata.sql.tables.SearchTagTable
|
|||||||
interface SearchTagQueries : DbProvider {
|
interface SearchTagQueries : DbProvider {
|
||||||
fun getSearchTagsForManga(mangaId: Long) = db.get()
|
fun getSearchTagsForManga(mangaId: Long) = db.get()
|
||||||
.listOfObjects(SearchTag::class.java)
|
.listOfObjects(SearchTag::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(SearchTagTable.TABLE)
|
.table(SearchTagTable.TABLE)
|
||||||
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteSearchTagsForManga(mangaId: Long) = db.delete()
|
fun deleteSearchTagsForManga(mangaId: Long) = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
.table(SearchTagTable.TABLE)
|
.table(SearchTagTable.TABLE)
|
||||||
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare()
|
fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare()
|
||||||
@ -31,9 +35,11 @@ interface SearchTagQueries : DbProvider {
|
|||||||
|
|
||||||
fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare()
|
fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare()
|
||||||
|
|
||||||
fun deleteAllSearchTags() = db.delete().byQuery(DeleteQuery.builder()
|
fun deleteAllSearchTags() = db.delete().byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
.table(SearchTagTable.TABLE)
|
.table(SearchTagTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun setSearchTagsForManga(mangaId: Long, tags: List<SearchTag>) {
|
fun setSearchTagsForManga(mangaId: Long, tags: List<SearchTag>) {
|
||||||
|
@ -10,19 +10,23 @@ import exh.metadata.sql.tables.SearchTitleTable
|
|||||||
interface SearchTitleQueries : DbProvider {
|
interface SearchTitleQueries : DbProvider {
|
||||||
fun getSearchTitlesForManga(mangaId: Long) = db.get()
|
fun getSearchTitlesForManga(mangaId: Long) = db.get()
|
||||||
.listOfObjects(SearchTitle::class.java)
|
.listOfObjects(SearchTitle::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(SearchTitleTable.TABLE)
|
.table(SearchTitleTable.TABLE)
|
||||||
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun deleteSearchTitlesForManga(mangaId: Long) = db.delete()
|
fun deleteSearchTitlesForManga(mangaId: Long) = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
.table(SearchTitleTable.TABLE)
|
.table(SearchTitleTable.TABLE)
|
||||||
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare()
|
fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare()
|
||||||
@ -31,9 +35,11 @@ interface SearchTitleQueries : DbProvider {
|
|||||||
|
|
||||||
fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare()
|
fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare()
|
||||||
|
|
||||||
fun deleteAllSearchTitle() = db.delete().byQuery(DeleteQuery.builder()
|
fun deleteAllSearchTitle() = db.delete().byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
.table(SearchTitleTable.TABLE)
|
.table(SearchTitleTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun setSearchTitlesForManga(mangaId: Long, titles: List<SearchTitle>) {
|
fun setSearchTitlesForManga(mangaId: Long, titles: List<SearchTitle>) {
|
||||||
|
@ -17,7 +17,8 @@ object SearchMetadataTable {
|
|||||||
|
|
||||||
// Insane foreign, primary key to avoid touch manga table
|
// Insane foreign, primary key to avoid touch manga table
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_UPLOADER TEXT,
|
$COL_UPLOADER TEXT,
|
||||||
$COL_EXTRA TEXT NOT NULL,
|
$COL_EXTRA TEXT NOT NULL,
|
||||||
|
@ -16,7 +16,8 @@ object SearchTagTable {
|
|||||||
const val COL_TYPE = "type"
|
const val COL_TYPE = "type"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_MANGA_ID INTEGER NOT NULL,
|
$COL_MANGA_ID INTEGER NOT NULL,
|
||||||
$COL_NAMESPACE TEXT,
|
$COL_NAMESPACE TEXT,
|
||||||
|
@ -14,7 +14,8 @@ object SearchTitleTable {
|
|||||||
const val COL_TYPE = "type"
|
const val COL_TYPE = "type"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_MANGA_ID INTEGER NOT NULL,
|
$COL_MANGA_ID INTEGER NOT NULL,
|
||||||
$COL_TITLE TEXT NOT NULL,
|
$COL_TITLE TEXT NOT NULL,
|
||||||
|
@ -9,7 +9,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
private val HIDE_SCRIPT = """
|
private val HIDE_SCRIPT =
|
||||||
|
"""
|
||||||
document.querySelector("#forgot_button").style.visibility = "hidden";
|
document.querySelector("#forgot_button").style.visibility = "hidden";
|
||||||
document.querySelector("#signup_button").style.visibility = "hidden";
|
document.querySelector("#signup_button").style.visibility = "hidden";
|
||||||
document.querySelector("#announcement").style.visibility = "hidden";
|
document.querySelector("#announcement").style.visibility = "hidden";
|
||||||
|
@ -16,8 +16,10 @@ fun OkHttpClient.Builder.injectPatches(sourceIdProducer: () -> Long): OkHttpClie
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findAndApplyPatches(sourceId: Long): EHInterceptor {
|
fun findAndApplyPatches(sourceId: Long): EHInterceptor {
|
||||||
return ((EH_INTERCEPTORS[sourceId] ?: emptyList()) +
|
return (
|
||||||
(EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList())).merge()
|
(EH_INTERCEPTORS[sourceId] ?: emptyList()) +
|
||||||
|
(EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList())
|
||||||
|
).merge()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<EHInterceptor>.merge(): EHInterceptor {
|
fun List<EHInterceptor>.merge(): EHInterceptor {
|
||||||
|
@ -12,11 +12,12 @@ class SearchEngine {
|
|||||||
component: Text?
|
component: Text?
|
||||||
): Pair<String, List<String>>? {
|
): Pair<String, List<String>>? {
|
||||||
val maybeLenientComponent = component?.let {
|
val maybeLenientComponent = component?.let {
|
||||||
if (!it.exact)
|
if (!it.exact) {
|
||||||
it.asLenientTagQueries()
|
it.asLenientTagQueries()
|
||||||
else
|
} else {
|
||||||
listOf(it.asQuery())
|
listOf(it.asQuery())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val componentTagQuery = maybeLenientComponent?.let {
|
val componentTagQuery = maybeLenientComponent?.let {
|
||||||
val params = mutableListOf<String>()
|
val params = mutableListOf<String>()
|
||||||
it.map { q ->
|
it.map { q ->
|
||||||
@ -25,7 +26,8 @@ class SearchEngine {
|
|||||||
}.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params
|
}.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params
|
||||||
}
|
}
|
||||||
return if (namespace != null) {
|
return if (namespace != null) {
|
||||||
var query = """
|
var query =
|
||||||
|
"""
|
||||||
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||||
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
|
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
|
||||||
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
|
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
|
||||||
@ -39,12 +41,14 @@ class SearchEngine {
|
|||||||
"$query)" to params
|
"$query)" to params
|
||||||
} else if (component != null) {
|
} else if (component != null) {
|
||||||
// Match title + tags
|
// Match title + tags
|
||||||
val tagQuery = """
|
val tagQuery =
|
||||||
|
"""
|
||||||
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||||
WHERE ${componentTagQuery!!.first}
|
WHERE ${componentTagQuery!!.first}
|
||||||
""".trimIndent() to componentTagQuery.second
|
""".trimIndent() to componentTagQuery.second
|
||||||
|
|
||||||
val titleQuery = """
|
val titleQuery =
|
||||||
|
"""
|
||||||
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
|
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
|
||||||
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
|
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
|
||||||
""".trimIndent() to listOf(component.asLenientTitleQuery())
|
""".trimIndent() to listOf(component.asLenientTitleQuery())
|
||||||
@ -86,16 +90,19 @@ class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val completeParams = mutableListOf<String>()
|
val completeParams = mutableListOf<String>()
|
||||||
var baseQuery = """
|
var baseQuery =
|
||||||
|
"""
|
||||||
SELECT ${SearchMetadataTable.COL_MANGA_ID}
|
SELECT ${SearchMetadataTable.COL_MANGA_ID}
|
||||||
FROM ${SearchMetadataTable.TABLE} meta
|
FROM ${SearchMetadataTable.TABLE} meta
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
include.forEachIndexed { index, pair ->
|
include.forEachIndexed { index, pair ->
|
||||||
baseQuery += "\n" + ("""
|
baseQuery += "\n" + (
|
||||||
|
"""
|
||||||
INNER JOIN ${pair.first} i$index
|
INNER JOIN ${pair.first} i$index
|
||||||
ON i$index.$COL_MANGA_ID = meta.${SearchMetadataTable.COL_MANGA_ID}
|
ON i$index.$COL_MANGA_ID = meta.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
completeParams += pair.second
|
completeParams += pair.second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +52,9 @@ class Text : QueryComponent() {
|
|||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rawTextOnly() = if (rawText != null)
|
fun rawTextOnly() = if (rawText != null) {
|
||||||
rawText!!
|
rawText!!
|
||||||
else {
|
} else {
|
||||||
rawText = components
|
rawText = components
|
||||||
.joinToString(separator = "", transform = { it.rawText })
|
.joinToString(separator = "", transform = { it.rawText })
|
||||||
rawText!!
|
rawText!!
|
||||||
|
@ -62,8 +62,9 @@ class SmartSearchEngine(
|
|||||||
} else title
|
} else title
|
||||||
val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io())
|
val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io())
|
||||||
|
|
||||||
if (searchResults.mangas.size == 1)
|
if (searchResults.mangas.size == 1) {
|
||||||
return@supervisorScope listOf(SearchEntry(searchResults.mangas.first(), 0.0))
|
return@supervisorScope listOf(SearchEntry(searchResults.mangas.first(), 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
searchResults.mangas.map {
|
searchResults.mangas.map {
|
||||||
val normalizedDistance = normalizedLevenshtein.similarity(title, it.title)
|
val normalizedDistance = normalizedLevenshtein.similarity(title, it.title)
|
||||||
|
@ -240,7 +240,8 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
|
|
||||||
private fun ensureDelegateCompatible() {
|
private fun ensureDelegateCompatible() {
|
||||||
if (versionId != delegate.versionId ||
|
if (versionId != delegate.versionId ||
|
||||||
lang != delegate.lang) {
|
lang != delegate.lang
|
||||||
|
) {
|
||||||
throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!")
|
throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ class ConfiguringDialogController : DialogController() {
|
|||||||
private var materialDialog: MaterialDialog? = null
|
private var materialDialog: MaterialDialog? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
if (savedViewState == null)
|
if (savedViewState == null) {
|
||||||
thread {
|
thread {
|
||||||
try {
|
try {
|
||||||
EHConfigurator().configureAll()
|
EHConfigurator().configureAll()
|
||||||
@ -37,6 +37,7 @@ class ConfiguringDialogController : DialogController() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.title(text = "Uploading settings to server")
|
.title(text = "Uploading settings to server")
|
||||||
|
@ -30,14 +30,18 @@ class EHConfigurator {
|
|||||||
set: String,
|
set: String,
|
||||||
sp: Int
|
sp: Int
|
||||||
) =
|
) =
|
||||||
configuratorClient.newCall(requestWithCreds(sp)
|
configuratorClient.newCall(
|
||||||
|
requestWithCreds(sp)
|
||||||
.url(uconfigUrl)
|
.url(uconfigUrl)
|
||||||
.post(FormBody.Builder()
|
.post(
|
||||||
|
FormBody.Builder()
|
||||||
.add("profile_action", action)
|
.add("profile_action", action)
|
||||||
.add("profile_name", name)
|
.add("profile_name", name)
|
||||||
.add("profile_set", set)
|
.add("profile_set", set)
|
||||||
.build())
|
.build()
|
||||||
.build())
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL
|
private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL
|
||||||
@ -47,9 +51,11 @@ class EHConfigurator {
|
|||||||
val exhSource = sources.get(EXH_SOURCE_ID) as EHentai
|
val exhSource = sources.get(EXH_SOURCE_ID) as EHentai
|
||||||
|
|
||||||
// Get hath perks
|
// Get hath perks
|
||||||
val perksPage = configuratorClient.newCall(ehSource.requestWithCreds()
|
val perksPage = configuratorClient.newCall(
|
||||||
|
ehSource.requestWithCreds()
|
||||||
.url(HATH_PERKS_URL)
|
.url(HATH_PERKS_URL)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.execute().asJsoup()
|
.execute().asJsoup()
|
||||||
|
|
||||||
val hathPerks = EHHathPerksResponse()
|
val hathPerks = EHHathPerksResponse()
|
||||||
@ -97,24 +103,29 @@ class EHConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No profile slots left :(
|
// No profile slots left :(
|
||||||
if (availableProfiles.isEmpty())
|
if (availableProfiles.isEmpty()) {
|
||||||
throw IllegalStateException("You are out of profile slots on ${source.name}, please delete a profile!")
|
throw IllegalStateException("You are out of profile slots on ${source.name}, please delete a profile!")
|
||||||
|
}
|
||||||
// Create profile in available slot
|
// Create profile in available slot
|
||||||
|
|
||||||
val slot = availableProfiles.first()
|
val slot = availableProfiles.first()
|
||||||
val response = source.execProfileActions("create",
|
val response = source.execProfileActions(
|
||||||
|
"create",
|
||||||
PROFILE_NAME,
|
PROFILE_NAME,
|
||||||
slot.toString(),
|
slot.toString(),
|
||||||
1)
|
1
|
||||||
|
)
|
||||||
|
|
||||||
// Build new profile
|
// Build new profile
|
||||||
val form = EhUConfigBuilder().build(hathPerks)
|
val form = EhUConfigBuilder().build(hathPerks)
|
||||||
|
|
||||||
// Send new profile to server
|
// Send new profile to server
|
||||||
configuratorClient.newCall(source.requestWithCreds(sp = slot)
|
configuratorClient.newCall(
|
||||||
|
source.requestWithCreds(sp = slot)
|
||||||
.url(source.uconfigUrl)
|
.url(source.uconfigUrl)
|
||||||
.post(form)
|
.post(form)
|
||||||
.build()).execute()
|
.build()
|
||||||
|
).execute()
|
||||||
|
|
||||||
// Persist slot + sk
|
// Persist slot + sk
|
||||||
source.spPref().set(slot)
|
source.spPref().set(slot)
|
||||||
@ -129,13 +140,16 @@ class EHConfigurator {
|
|||||||
it.startsWith("hath_perks=")
|
it.startsWith("hath_perks=")
|
||||||
}?.removePrefix("hath_perks=")?.substringBefore(';')
|
}?.removePrefix("hath_perks=")?.substringBefore(';')
|
||||||
|
|
||||||
if (keyCookie != null)
|
if (keyCookie != null) {
|
||||||
prefs.eh_settingsKey().set(keyCookie)
|
prefs.eh_settingsKey().set(keyCookie)
|
||||||
if (sessionCookie != null)
|
}
|
||||||
|
if (sessionCookie != null) {
|
||||||
prefs.eh_sessionCookie().set(sessionCookie)
|
prefs.eh_sessionCookie().set(sessionCookie)
|
||||||
if (hathPerksCookie != null)
|
}
|
||||||
|
if (hathPerksCookie != null) {
|
||||||
prefs.eh_hathPerksCookies().set(hathPerksCookie)
|
prefs.eh_hathPerksCookies().set(hathPerksCookie)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PROFILE_NAME = "TachiyomiEH App"
|
private const val PROFILE_NAME = "TachiyomiEH App"
|
||||||
|
@ -11,9 +11,11 @@ class EhUConfigBuilder {
|
|||||||
fun build(hathPerks: EHHathPerksResponse): FormBody {
|
fun build(hathPerks: EHHathPerksResponse): FormBody {
|
||||||
val configItems = mutableListOf<ConfigItem>()
|
val configItems = mutableListOf<ConfigItem>()
|
||||||
|
|
||||||
configItems += when (prefs.imageQuality()
|
configItems += when (
|
||||||
|
prefs.imageQuality()
|
||||||
.getOrDefault()
|
.getOrDefault()
|
||||||
.toLowerCase()) {
|
.toLowerCase()
|
||||||
|
) {
|
||||||
"ovrs_2400" -> Entry.ImageSize.`2400`
|
"ovrs_2400" -> Entry.ImageSize.`2400`
|
||||||
"ovrs_1600" -> Entry.ImageSize.`1600`
|
"ovrs_1600" -> Entry.ImageSize.`1600`
|
||||||
"high" -> Entry.ImageSize.`1280`
|
"high" -> Entry.ImageSize.`1280`
|
||||||
@ -23,20 +25,23 @@ class EhUConfigBuilder {
|
|||||||
else -> Entry.ImageSize.AUTO
|
else -> Entry.ImageSize.AUTO
|
||||||
}
|
}
|
||||||
|
|
||||||
configItems += if (prefs.useHentaiAtHome().getOrDefault())
|
configItems += if (prefs.useHentaiAtHome().getOrDefault()) {
|
||||||
Entry.UseHentaiAtHome.YES
|
Entry.UseHentaiAtHome.YES
|
||||||
else
|
} else {
|
||||||
Entry.UseHentaiAtHome.NO
|
Entry.UseHentaiAtHome.NO
|
||||||
|
}
|
||||||
|
|
||||||
configItems += if (prefs.useJapaneseTitle().getOrDefault())
|
configItems += if (prefs.useJapaneseTitle().getOrDefault()) {
|
||||||
Entry.TitleDisplayLanguage.JAPANESE
|
Entry.TitleDisplayLanguage.JAPANESE
|
||||||
else
|
} else {
|
||||||
Entry.TitleDisplayLanguage.DEFAULT
|
Entry.TitleDisplayLanguage.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
configItems += if (prefs.eh_useOriginalImages().getOrDefault())
|
configItems += if (prefs.eh_useOriginalImages().getOrDefault()) {
|
||||||
Entry.UseOriginalImages.YES
|
Entry.UseOriginalImages.YES
|
||||||
else
|
} else {
|
||||||
Entry.UseOriginalImages.NO
|
Entry.UseOriginalImages.NO
|
||||||
|
}
|
||||||
|
|
||||||
configItems += when {
|
configItems += when {
|
||||||
hathPerks.allThumbs -> Entry.ThumbnailRows.`40`
|
hathPerks.allThumbs -> Entry.ThumbnailRows.`40`
|
||||||
|
@ -15,11 +15,14 @@ class WarnConfigureDialogController : DialogController() {
|
|||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.title(text = "Settings profile note")
|
.title(text = "Settings profile note")
|
||||||
.message(text = """
|
.message(
|
||||||
|
text =
|
||||||
|
"""
|
||||||
The app will now add a new settings profile on E-Hentai and ExHentai to optimize app performance. Please ensure that you have less than three profiles on both sites.
|
The app will now add a new settings profile on E-Hentai and ExHentai to optimize app performance. Please ensure that you have less than three profiles on both sites.
|
||||||
|
|
||||||
If you have no idea what settings profiles are, then it probably doesn't matter, just hit 'OK'.
|
If you have no idea what settings profiles are, then it probably doesn't matter, just hit 'OK'.
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
.positiveButton(android.R.string.ok) {
|
.positiveButton(android.R.string.ok) {
|
||||||
prefs.eh_showSettingsUploadWarning().set(false)
|
prefs.eh_showSettingsUploadWarning().set(false)
|
||||||
ConfiguringDialogController().showDialog(router)
|
ConfiguringDialogController().showDialog(router)
|
||||||
@ -29,10 +32,11 @@ class WarnConfigureDialogController : DialogController() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun uploadSettings(router: Router) {
|
fun uploadSettings(router: Router) {
|
||||||
if (Injekt.get<PreferencesHelper>().eh_showSettingsUploadWarning().get())
|
if (Injekt.get<PreferencesHelper>().eh_showSettingsUploadWarning().get()) {
|
||||||
WarnConfigureDialogController().showDialog(router)
|
WarnConfigureDialogController().showDialog(router)
|
||||||
else
|
} else {
|
||||||
ConfiguringDialogController().showDialog(router)
|
ConfiguringDialogController().showDialog(router)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -59,9 +59,11 @@ class BatchAddController : NucleusController<EhFragmentBatchAddBinding, BatchAdd
|
|||||||
.combineLatest(presenter.progressTotalRelay) { progress, total ->
|
.combineLatest(presenter.progressTotalRelay) { progress, total ->
|
||||||
// Show hide dismiss button
|
// Show hide dismiss button
|
||||||
binding.progressDismissBtn.visibility =
|
binding.progressDismissBtn.visibility =
|
||||||
if (progress == total)
|
if (progress == total) {
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
else View.GONE
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
formatProgress(progress, total)
|
formatProgress(progress, total)
|
||||||
}.subscribeUntilDestroy {
|
}.subscribeUntilDestroy {
|
||||||
|
@ -40,10 +40,14 @@ class BatchAddPresenter : BasePresenter<BatchAddController>() {
|
|||||||
failed.add(s)
|
failed.add(s)
|
||||||
}
|
}
|
||||||
progressRelay.call(i + 1)
|
progressRelay.call(i + 1)
|
||||||
eventRelay?.call((when (result) {
|
eventRelay?.call(
|
||||||
|
(
|
||||||
|
when (result) {
|
||||||
is GalleryAddEvent.Success -> "[OK]"
|
is GalleryAddEvent.Success -> "[OK]"
|
||||||
is GalleryAddEvent.Fail -> "[ERROR]"
|
is GalleryAddEvent.Fail -> "[ERROR]"
|
||||||
}) + " " + result.logMessage)
|
}
|
||||||
|
) + " " + result.logMessage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show report
|
// Show report
|
||||||
|
@ -14,8 +14,9 @@ open class BasicWebViewClient(
|
|||||||
if (verifyComplete(url)) {
|
if (verifyComplete(url)) {
|
||||||
activity.finish()
|
activity.finish()
|
||||||
} else {
|
} else {
|
||||||
if (injectScript != null)
|
if (injectScript != null) {
|
||||||
view.evaluateJavascript("(function() {$injectScript})();", null)
|
view.evaluateJavascript("(function() {$injectScript})();", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -69,9 +69,11 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
val headers = ((source as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
val headers = (
|
||||||
|
(source as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
||||||
it.value.joinToString(",")
|
it.value.joinToString(",")
|
||||||
} ?: emptyMap()) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap())
|
} ?: emptyMap()
|
||||||
|
) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap())
|
||||||
|
|
||||||
val cookies: HashMap<String, String>? =
|
val cookies: HashMap<String, String>? =
|
||||||
intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
|
intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
|
||||||
@ -79,7 +81,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
val url: String? = intent.getStringExtra(URL_EXTRA)
|
val url: String? = intent.getStringExtra(URL_EXTRA)
|
||||||
val actionName = intent.getStringExtra(ACTION_NAME_EXTRA)
|
val actionName = intent.getStringExtra(ACTION_NAME_EXTRA)
|
||||||
|
|
||||||
@Suppress("NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE") val verifyComplete = if (source != null) {
|
@Suppress("NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE")
|
||||||
|
val verifyComplete = if (source != null) {
|
||||||
source::verifyComplete!!
|
source::verifyComplete!!
|
||||||
} else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean
|
} else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean
|
||||||
|
|
||||||
@ -139,10 +142,12 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
webview.webViewClient = if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
|
webview.webViewClient = if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
|
||||||
// Fetch auto-solve credentials early for speed
|
// Fetch auto-solve credentials early for speed
|
||||||
credentialsObservable = httpClient.newCall(Request.Builder()
|
credentialsObservable = httpClient.newCall(
|
||||||
|
Request.Builder()
|
||||||
// Rob demo credentials
|
// Rob demo credentials
|
||||||
.url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials")
|
.url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials")
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.map {
|
.map {
|
||||||
@ -192,13 +197,19 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
when (stage) {
|
when (stage) {
|
||||||
STAGE_CHECKBOX -> {
|
STAGE_CHECKBOX -> {
|
||||||
if (result!!.toBoolean()) {
|
if (result!!.toBoolean()) {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
|
{
|
||||||
getAudioButtonLocation(loopId)
|
getAudioButtonLocation(loopId)
|
||||||
}, 250)
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
|
{
|
||||||
doStageCheckbox(loopId)
|
doStageCheckbox(loopId)
|
||||||
}, 250)
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAGE_GET_AUDIO_BTN_LOCATION -> {
|
STAGE_GET_AUDIO_BTN_LOCATION -> {
|
||||||
@ -216,9 +227,12 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
doStageDownloadAudio(loopId)
|
doStageDownloadAudio(loopId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
|
{
|
||||||
getAudioButtonLocation(loopId)
|
getAudioButtonLocation(loopId)
|
||||||
}, 250)
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAGE_DOWNLOAD_AUDIO -> {
|
STAGE_DOWNLOAD_AUDIO -> {
|
||||||
@ -226,21 +240,30 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
Timber.d("Got audio URL: $result")
|
Timber.d("Got audio URL: $result")
|
||||||
performRecognize(result)
|
performRecognize(result)
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
Timber.d("Got audio transcript: $it")
|
Timber.d("Got audio transcript: $it")
|
||||||
webview.post {
|
webview.post {
|
||||||
typeResult(loopId, it!!
|
typeResult(
|
||||||
|
loopId,
|
||||||
|
it!!
|
||||||
.replace(TRANSCRIPT_CLEANER_REGEX, "")
|
.replace(TRANSCRIPT_CLEANER_REGEX, "")
|
||||||
.replace(SPACE_DEDUPE_REGEX, " ")
|
.replace(SPACE_DEDUPE_REGEX, " ")
|
||||||
.trim())
|
.trim()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
captchaSolveFail()
|
captchaSolveFail()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
|
{
|
||||||
doStageDownloadAudio(loopId)
|
doStageDownloadAudio(loopId)
|
||||||
}, 250)
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAGE_TYPE_RESULT -> {
|
STAGE_TYPE_RESULT -> {
|
||||||
@ -256,27 +279,37 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
fun performRecognize(url: String): Single<String> {
|
fun performRecognize(url: String): Single<String> {
|
||||||
return credentialsObservable.flatMap { token ->
|
return credentialsObservable.flatMap { token ->
|
||||||
httpClient.newCall(Request.Builder()
|
httpClient.newCall(
|
||||||
|
Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.build()).asObservableSuccess().map {
|
.build()
|
||||||
|
).asObservableSuccess().map {
|
||||||
token to it
|
token to it
|
||||||
}
|
}
|
||||||
}.flatMap { (token, response) ->
|
}.flatMap { (token, response) ->
|
||||||
val audioFile = response.body!!.bytes()
|
val audioFile = response.body!!.bytes()
|
||||||
|
|
||||||
httpClient.newCall(Request.Builder()
|
httpClient.newCall(
|
||||||
.url("https://stream.watsonplatform.net/speech-to-text/api/v1/recognize".toHttpUrlOrNull()!!
|
Request.Builder()
|
||||||
|
.url(
|
||||||
|
"https://stream.watsonplatform.net/speech-to-text/api/v1/recognize".toHttpUrlOrNull()!!
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("watson-token", token)
|
.addQueryParameter("watson-token", token)
|
||||||
.build())
|
.build()
|
||||||
.post(MultipartBody.Builder()
|
)
|
||||||
|
.post(
|
||||||
|
MultipartBody.Builder()
|
||||||
.setType(MultipartBody.FORM)
|
.setType(MultipartBody.FORM)
|
||||||
.addFormDataPart("jsonDescription", RECOGNIZE_JSON)
|
.addFormDataPart("jsonDescription", RECOGNIZE_JSON)
|
||||||
.addFormDataPart("audio.mp3",
|
.addFormDataPart(
|
||||||
"audio.mp3",
|
"audio.mp3",
|
||||||
RequestBody.create("audio/mp3".toMediaTypeOrNull(), audioFile))
|
"audio.mp3",
|
||||||
.build())
|
RequestBody.create("audio/mp3".toMediaTypeOrNull(), audioFile)
|
||||||
.build()).asObservableSuccess()
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
).asObservableSuccess()
|
||||||
}.map { response ->
|
}.map { response ->
|
||||||
JsonParser.parseString(response.body!!.string())["results"][0]["alternatives"][0]["transcript"].string.trim()
|
JsonParser.parseString(response.body!!.string())["results"][0]["alternatives"][0]["transcript"].string.trim()
|
||||||
}.toSingle()
|
}.toSingle()
|
||||||
@ -285,7 +318,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
fun doStageCheckbox(loopId: String) {
|
fun doStageCheckbox(loopId: String) {
|
||||||
if (loopId != currentLoopId) return
|
if (loopId != currentLoopId) return
|
||||||
|
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -307,11 +341,14 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback("false", '$loopId', $STAGE_CHECKBOX);
|
exh.callback("false", '$loopId', $STAGE_CHECKBOX);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAudioButtonLocation(loopId: String) {
|
fun getAudioButtonLocation(loopId: String) {
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -339,11 +376,14 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback(null, '$loopId', $STAGE_GET_AUDIO_BTN_LOCATION);
|
exh.callback(null, '$loopId', $STAGE_GET_AUDIO_BTN_LOCATION);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doStageDownloadAudio(loopId: String) {
|
fun doStageDownloadAudio(loopId: String) {
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -364,11 +404,14 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback(null, '$loopId', $STAGE_DOWNLOAD_AUDIO);
|
exh.callback(null, '$loopId', $STAGE_DOWNLOAD_AUDIO);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun typeResult(loopId: String, result: String) {
|
fun typeResult(loopId: String, result: String) {
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -392,7 +435,9 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback("false", '$loopId', $STAGE_TYPE_RESULT);
|
exh.callback("false", '$loopId', $STAGE_TYPE_RESULT);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun beginSolveLoop() {
|
fun beginSolveLoop() {
|
||||||
@ -419,12 +464,16 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
val savedStrictValidationStartTime = strictValidationStartTime
|
val savedStrictValidationStartTime = strictValidationStartTime
|
||||||
if (savedStrictValidationStartTime != null &&
|
if (savedStrictValidationStartTime != null &&
|
||||||
System.currentTimeMillis() > savedStrictValidationStartTime) {
|
System.currentTimeMillis() > savedStrictValidationStartTime
|
||||||
|
) {
|
||||||
captchaSolveFail()
|
captchaSolveFail()
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
|
{
|
||||||
runValidateCaptcha(loopId)
|
runValidateCaptcha(loopId)
|
||||||
}, 250)
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,7 +481,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
fun runValidateCaptcha(loopId: String) {
|
fun runValidateCaptcha(loopId: String) {
|
||||||
if (loopId != validateCurrentLoopId) return
|
if (loopId != validateCurrentLoopId) return
|
||||||
|
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -453,7 +503,9 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.validateCaptchaCallback(false, '$loopId');
|
exh.validateCaptchaCallback(false, '$loopId');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun beginValidateCaptchaLoop() {
|
fun beginValidateCaptchaLoop() {
|
||||||
@ -502,7 +554,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
const val STAGE_DOWNLOAD_AUDIO = 2
|
const val STAGE_DOWNLOAD_AUDIO = 2
|
||||||
const val STAGE_TYPE_RESULT = 3
|
const val STAGE_TYPE_RESULT = 3
|
||||||
|
|
||||||
val CROSS_WINDOW_SCRIPT_OUTER = """
|
val CROSS_WINDOW_SCRIPT_OUTER =
|
||||||
|
"""
|
||||||
function cwmExec(element, code, cb) {
|
function cwmExec(element, code, cb) {
|
||||||
console.log(">>> [CWM-Outer] Running: " + code);
|
console.log(">>> [CWM-Outer] Running: " + code);
|
||||||
let runId = Math.random();
|
let runId = Math.random();
|
||||||
@ -525,7 +578,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
""".trimIndent().replace("\n", "")
|
""".trimIndent().replace("\n", "")
|
||||||
|
|
||||||
val CROSS_WINDOW_SCRIPT_INNER = """
|
val CROSS_WINDOW_SCRIPT_INNER =
|
||||||
|
"""
|
||||||
window.addEventListener('message', function(event) {
|
window.addEventListener('message', function(event) {
|
||||||
if(typeof event.data === "string" && event.data.startsWith("exh-")) {
|
if(typeof event.data === "string" && event.data.startsWith("exh-")) {
|
||||||
let request = JSON.parse(event.data.substring(4));
|
let request = JSON.parse(event.data.substring(4));
|
||||||
@ -540,7 +594,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
alert("exh-");
|
alert("exh-");
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val SOLVE_UI_SCRIPT_SHOW = """
|
val SOLVE_UI_SCRIPT_SHOW =
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
let exh_overlay = document.createElement("div");
|
let exh_overlay = document.createElement("div");
|
||||||
exh_overlay.id = "exh_overlay";
|
exh_overlay.id = "exh_overlay";
|
||||||
@ -570,7 +625,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
})();
|
})();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val SOLVE_UI_SCRIPT_HIDE = """
|
val SOLVE_UI_SCRIPT_HIDE =
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
let exh_overlay = document.getElementById("exh_overlay");
|
let exh_overlay = document.getElementById("exh_overlay");
|
||||||
let exh_otext = document.getElementById("exh_otext");
|
let exh_otext = document.getElementById("exh_otext");
|
||||||
@ -579,7 +635,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
})();
|
})();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val RECOGNIZE_JSON = """
|
val RECOGNIZE_JSON =
|
||||||
|
"""
|
||||||
{
|
{
|
||||||
"part_content_type": "audio/mp3",
|
"part_content_type": "audio/mp3",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@ -689,7 +746,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoopActionCompletionVerifier(private val source: HttpSource) : DelegatedHttpSource(source),
|
class NoopActionCompletionVerifier(private val source: HttpSource) :
|
||||||
|
DelegatedHttpSource(source),
|
||||||
ActionCompletionVerifier {
|
ActionCompletionVerifier {
|
||||||
override val versionId get() = source.versionId
|
override val versionId get() = source.versionId
|
||||||
override val lang: String get() = source.lang
|
override val lang: String get() = source.lang
|
||||||
|
@ -61,10 +61,12 @@ class InterceptActivity : BaseRxActivity<EhActivityInterceptBinding, InterceptAc
|
|||||||
binding.interceptProgress.gone()
|
binding.interceptProgress.gone()
|
||||||
binding.interceptStatus.text = "Launching app..."
|
binding.interceptStatus.text = "Launching app..."
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
startActivity(Intent(this, MainActivity::class.java)
|
startActivity(
|
||||||
|
Intent(this, MainActivity::class.java)
|
||||||
.setAction(MainActivity.SHORTCUT_MANGA)
|
.setAction(MainActivity.SHORTCUT_MANGA)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
.putExtra(MangaController.MANGA_EXTRA, it.mangaId))
|
.putExtra(MangaController.MANGA_EXTRA, it.mangaId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is InterceptResult.Failure -> {
|
is InterceptResult.Failure -> {
|
||||||
binding.interceptProgress.gone()
|
binding.interceptProgress.gone()
|
||||||
|
@ -21,12 +21,14 @@ class InterceptActivityPresenter : BasePresenter<InterceptActivity>() {
|
|||||||
thread {
|
thread {
|
||||||
val result = galleryAdder.addGallery(gallery)
|
val result = galleryAdder.addGallery(gallery)
|
||||||
|
|
||||||
status.onNext(when (result) {
|
status.onNext(
|
||||||
|
when (result) {
|
||||||
is GalleryAddEvent.Success -> result.manga.id?.let {
|
is GalleryAddEvent.Success -> result.manga.id?.let {
|
||||||
InterceptResult.Success(it)
|
InterceptResult.Success(it)
|
||||||
} ?: InterceptResult.Failure("Manga ID is null!")
|
} ?: InterceptResult.Failure("Manga ID is null!")
|
||||||
is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage)
|
is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,39 +44,44 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
if (fingerprintSupported) {
|
if (fingerprintSupported) {
|
||||||
updateSummary()
|
updateSummary()
|
||||||
onChange {
|
onChange {
|
||||||
if (it as Boolean)
|
if (it as Boolean) {
|
||||||
tryChange()
|
tryChange()
|
||||||
else
|
} else {
|
||||||
prefs.eh_lockUseFingerprint().set(false)
|
prefs.eh_lockUseFingerprint().set(false)
|
||||||
|
}
|
||||||
!it
|
!it
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
title = "Fingerprint unsupported"
|
title = "Fingerprint unsupported"
|
||||||
shouldDisableView = true
|
shouldDisableView = true
|
||||||
summary = if (!Reprint.hasFingerprintRegistered())
|
summary = if (!Reprint.hasFingerprintRegistered()) {
|
||||||
"No fingerprints enrolled!"
|
"No fingerprints enrolled!"
|
||||||
else
|
} else {
|
||||||
"Fingerprint unlock is unsupported on this device!"
|
"Fingerprint unlock is unsupported on this device!"
|
||||||
|
}
|
||||||
onChange { false }
|
onChange { false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSummary() {
|
private fun updateSummary() {
|
||||||
isChecked = useFingerprint
|
isChecked = useFingerprint
|
||||||
title = if (isChecked)
|
title = if (isChecked) {
|
||||||
"Fingerprint enabled"
|
"Fingerprint enabled"
|
||||||
else
|
} else {
|
||||||
"Fingerprint disabled"
|
"Fingerprint disabled"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
fun tryChange() {
|
fun tryChange() {
|
||||||
val statusTextView = TextView(context).apply {
|
val statusTextView = TextView(context).apply {
|
||||||
text = "Please touch the fingerprint sensor"
|
text = "Please touch the fingerprint sensor"
|
||||||
val size = ViewGroup.LayoutParams.WRAP_CONTENT
|
val size = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
layoutParams = (layoutParams ?: ViewGroup.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: ViewGroup.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
setPadding(0, 0, dpToPx(context, 8), 0)
|
setPadding(0, 0, dpToPx(context, 8), 0)
|
||||||
@ -84,9 +89,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
}
|
}
|
||||||
val iconView = SwirlView(context).apply {
|
val iconView = SwirlView(context).apply {
|
||||||
val size = dpToPx(context, 30)
|
val size = dpToPx(context, 30)
|
||||||
layoutParams = (layoutParams ?: ViewGroup.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: ViewGroup.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
}
|
}
|
||||||
@ -96,9 +103,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
orientation = LinearLayoutCompat.HORIZONTAL
|
orientation = LinearLayoutCompat.HORIZONTAL
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
gravity = Gravity.CENTER_VERTICAL
|
||||||
val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT
|
val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT
|
||||||
layoutParams = (layoutParams ?: LinearLayoutCompat.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: LinearLayoutCompat.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
val pSize = dpToPx(context, 24)
|
val pSize = dpToPx(context, 24)
|
||||||
|
@ -20,8 +20,10 @@ object LockActivityDelegate {
|
|||||||
private val uiScope = CoroutineScope(Dispatchers.Main)
|
private val uiScope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
fun doLock(router: Router, animate: Boolean = false) {
|
fun doLock(router: Router, animate: Boolean = false) {
|
||||||
router.pushController(RouterTransaction.with(LockController())
|
router.pushController(
|
||||||
.popChangeHandler(LockChangeHandler(animate)))
|
RouterTransaction.with(LockController())
|
||||||
|
.popChangeHandler(LockChangeHandler(animate))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCreate(activity: FragmentActivity) {
|
fun onCreate(activity: FragmentActivity) {
|
||||||
|
@ -79,9 +79,11 @@ class LockController : NucleusController<ActivityLockBinding, LockPresenter>() {
|
|||||||
binding.swirlContainer.removeAllViews()
|
binding.swirlContainer.removeAllViews()
|
||||||
val icon = SwirlView(context).apply {
|
val icon = SwirlView(context).apply {
|
||||||
val size = dpToPx(context, 60)
|
val size = dpToPx(context, 60)
|
||||||
layoutParams = (layoutParams ?: ViewGroup.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: ViewGroup.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
|
|
||||||
@ -92,8 +94,9 @@ class LockController : NucleusController<ActivityLockBinding, LockPresenter>() {
|
|||||||
setBackgroundColor(lockColor)
|
setBackgroundColor(lockColor)
|
||||||
val bgColor = resolvColor(android.R.attr.colorBackground)
|
val bgColor = resolvColor(android.R.attr.colorBackground)
|
||||||
// Disable elevation if lock color is same as background color
|
// Disable elevation if lock color is same as background color
|
||||||
if (lockColor == bgColor)
|
if (lockColor == bgColor) {
|
||||||
this@with.swirl_container.cardElevation = 0f
|
this@with.swirl_container.cardElevation = 0f
|
||||||
|
}
|
||||||
setState(SwirlView.State.OFF, true)
|
setState(SwirlView.State.OFF, true)
|
||||||
}
|
}
|
||||||
binding.swirlContainer.addView(icon)
|
binding.swirlContainer.addView(icon)
|
||||||
|
@ -53,12 +53,15 @@ fun notifyLockSecurity(
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
return false
|
return false
|
||||||
if (!prefs.eh_lockManually().getOrDefault() &&
|
if (!prefs.eh_lockManually().getOrDefault() &&
|
||||||
!hasAccessToUsageStats(context)) {
|
!hasAccessToUsageStats(context)
|
||||||
|
) {
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "Permission required")
|
.title(text = "Permission required")
|
||||||
.message(text = "${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
|
.message(
|
||||||
|
text = "${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
|
||||||
"This is required for the application lock to function properly. " +
|
"This is required for the application lock to function properly. " +
|
||||||
"Press OK to grant this permission now.")
|
"Press OK to grant this permission now."
|
||||||
|
)
|
||||||
.negativeButton(R.string.action_cancel)
|
.negativeButton(R.string.action_cancel)
|
||||||
.positiveButton(android.R.string.ok) {
|
.positiveButton(android.R.string.ok) {
|
||||||
try {
|
try {
|
||||||
@ -67,8 +70,10 @@ fun notifyLockSecurity(
|
|||||||
XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!")
|
XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!")
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "Grant permission manually")
|
.title(text = "Grant permission manually")
|
||||||
.message(text = "Failed to launch the window used to grant the usage stats permission. " +
|
.message(
|
||||||
"You can still grant this permission manually: go to your phone's settings and search for 'usage access'.")
|
text = "Failed to launch the window used to grant the usage stats permission. " +
|
||||||
|
"You can still grant this permission manually: go to your phone's settings and search for 'usage access'."
|
||||||
|
)
|
||||||
.positiveButton(android.R.string.ok) { it.dismiss() }
|
.positiveButton(android.R.string.ok) { it.dismiss() }
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(false)
|
.cancelOnTouchOutside(false)
|
||||||
|
@ -97,10 +97,11 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
val parsedUrl = Uri.parse(url)
|
val parsedUrl = Uri.parse(url)
|
||||||
if (parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
|
if (parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
|
||||||
// Hide distracting content
|
// Hide distracting content
|
||||||
if (!parsedUrl.queryParameterNames.contains(PARAM_SKIP_INJECT))
|
if (!parsedUrl.queryParameterNames.contains(PARAM_SKIP_INJECT)) {
|
||||||
view.evaluateJavascript(HIDE_JS, null)
|
view.evaluateJavascript(HIDE_JS, null)
|
||||||
|
}
|
||||||
// Check login result
|
// Check login result
|
||||||
|
|
||||||
if (parsedUrl.getQueryParameter("code")?.toInt() != 0) {
|
if (parsedUrl.getQueryParameter("code")?.toInt() != 0) {
|
||||||
if (checkLoginCookies(url)) view.loadUrl("https://exhentai.org/")
|
if (checkLoginCookies(url)) view.loadUrl("https://exhentai.org/")
|
||||||
}
|
}
|
||||||
@ -128,8 +129,10 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
fun checkLoginCookies(url: String): Boolean {
|
fun checkLoginCookies(url: String): Boolean {
|
||||||
getCookies(url)?.let { parsed ->
|
getCookies(url)?.let { parsed ->
|
||||||
return parsed.filter {
|
return parsed.filter {
|
||||||
(it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) ||
|
(
|
||||||
it.name.equals(PASS_HASH_COOKIE, ignoreCase = true)) &&
|
it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) ||
|
||||||
|
it.name.equals(PASS_HASH_COOKIE, ignoreCase = true)
|
||||||
|
) &&
|
||||||
it.value.isNotBlank()
|
it.value.isNotBlank()
|
||||||
}.count() >= 2
|
}.count() >= 2
|
||||||
}
|
}
|
||||||
@ -181,7 +184,8 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
const val PASS_HASH_COOKIE = "ipb_pass_hash"
|
const val PASS_HASH_COOKIE = "ipb_pass_hash"
|
||||||
const val IGNEOUS_COOKIE = "igneous"
|
const val IGNEOUS_COOKIE = "igneous"
|
||||||
|
|
||||||
const val HIDE_JS = """
|
const val HIDE_JS =
|
||||||
|
"""
|
||||||
javascript:(function () {
|
javascript:(function () {
|
||||||
document.getElementsByTagName('body')[0].style.visibility = 'hidden';
|
document.getElementsByTagName('body')[0].style.visibility = 'hidden';
|
||||||
document.getElementsByName('submit')[0].style.visibility = 'visible';
|
document.getElementsByName('submit')[0].style.visibility = 'visible';
|
||||||
|
@ -47,9 +47,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) {
|
private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" == \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
|
"\"$fieldName\" == \"$value\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun equalTo(fieldName: String, value: String): RealmQuery<E> {
|
fun equalTo(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -108,11 +112,18 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun appendIn(fieldName: String, values: Array<out Any?>, casing: Case? = null) {
|
fun appendIn(fieldName: String, values: Array<out Any?>, casing: Case? = null) {
|
||||||
log += sec("[${values.joinToString(separator = ", ", transform = {
|
log += sec(
|
||||||
|
"[${values.joinToString(
|
||||||
|
separator = ", ",
|
||||||
|
transform = {
|
||||||
"\"$it\""
|
"\"$it\""
|
||||||
})}] IN \"$fieldName\"" + (casing?.let {
|
}
|
||||||
|
)}] IN \"$fieldName\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun `in`(fieldName: String, values: Array<String>): RealmQuery<E> {
|
fun `in`(fieldName: String, values: Array<String>): RealmQuery<E> {
|
||||||
@ -166,9 +177,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" != \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
|
"\"$fieldName\" != \"$value\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notEqualTo(fieldName: String, value: String): RealmQuery<E> {
|
fun notEqualTo(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -372,9 +387,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" CONTAINS \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
|
"\"$fieldName\" CONTAINS \"$value\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contains(fieldName: String, value: String): RealmQuery<E> {
|
fun contains(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -388,9 +407,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" BEGINS WITH \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
|
"\"$fieldName\" BEGINS WITH \"$value\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun beginsWith(fieldName: String, value: String): RealmQuery<E> {
|
fun beginsWith(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -404,9 +427,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" ENDS WITH \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
|
"\"$fieldName\" ENDS WITH \"$value\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endsWith(fieldName: String, value: String): RealmQuery<E> {
|
fun endsWith(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -420,9 +447,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" LIKE \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
|
"\"$fieldName\" LIKE \"$value\"" + (
|
||||||
|
casing?.let {
|
||||||
" CASE ${casing.name}"
|
" CASE ${casing.name}"
|
||||||
} ?: ""))
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun like(fieldName: String, value: String): RealmQuery<E> {
|
fun like(fieldName: String, value: String): RealmQuery<E> {
|
||||||
|
@ -209,10 +209,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
override val entries: Set<Map.Entry<String, T>>
|
override val entries: Set<Map.Entry<String, T>>
|
||||||
get() {
|
get() {
|
||||||
val out = mutableSetOf<Map.Entry<String, T>>()
|
val out = mutableSetOf<Map.Entry<String, T>>()
|
||||||
node.walk("", { k, v ->
|
node.walk(
|
||||||
|
"",
|
||||||
|
{ k, v ->
|
||||||
out.add(AbstractMap.SimpleImmutableEntry(k, v))
|
out.add(AbstractMap.SimpleImmutableEntry(k, v))
|
||||||
true
|
true
|
||||||
}, leavesOnly)
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -221,10 +225,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
override val keys: Set<String>
|
override val keys: Set<String>
|
||||||
get() {
|
get() {
|
||||||
val out = mutableSetOf<String>()
|
val out = mutableSetOf<String>()
|
||||||
node.walk("", { k, _ ->
|
node.walk(
|
||||||
|
"",
|
||||||
|
{ k, _ ->
|
||||||
out.add(k)
|
out.add(k)
|
||||||
true
|
true
|
||||||
}, leavesOnly)
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,10 +251,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
override val values: Collection<T>
|
override val values: Collection<T>
|
||||||
get() {
|
get() {
|
||||||
val out = mutableSetOf<T>()
|
val out = mutableSetOf<T>()
|
||||||
node.walk("", { _, v ->
|
node.walk(
|
||||||
|
"",
|
||||||
|
{ _, v ->
|
||||||
out.add(v)
|
out.add(v)
|
||||||
true
|
true
|
||||||
}, leavesOnly)
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,10 +276,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
* Returns `true` if the map maps one or more keys to the specified [value].
|
* Returns `true` if the map maps one or more keys to the specified [value].
|
||||||
*/
|
*/
|
||||||
override fun containsValue(value: T): Boolean {
|
override fun containsValue(value: T): Boolean {
|
||||||
node.walk("", { _, v ->
|
node.walk(
|
||||||
|
"",
|
||||||
|
{ _, v ->
|
||||||
if (v == value) return true
|
if (v == value) return true
|
||||||
true
|
true
|
||||||
}, leavesOnly)
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,32 +331,38 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
* Returns a [MutableSet] of all key/value pairs in this map.
|
* Returns a [MutableSet] of all key/value pairs in this map.
|
||||||
*/
|
*/
|
||||||
override val entries: MutableSet<MutableMap.MutableEntry<String, T>>
|
override val entries: MutableSet<MutableMap.MutableEntry<String, T>>
|
||||||
get() = FakeMutableSet.fromSet(mutableSetOf<MutableMap.MutableEntry<String, T>>().apply {
|
get() = FakeMutableSet.fromSet(
|
||||||
|
mutableSetOf<MutableMap.MutableEntry<String, T>>().apply {
|
||||||
walk { k, v ->
|
walk { k, v ->
|
||||||
this += FakeMutableEntry.fromPair(k, v)
|
this += FakeMutableEntry.fromPair(k, v)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [MutableSet] of all keys in this map.
|
* Returns a [MutableSet] of all keys in this map.
|
||||||
*/
|
*/
|
||||||
override val keys: MutableSet<String>
|
override val keys: MutableSet<String>
|
||||||
get() = FakeMutableSet.fromSet(mutableSetOf<String>().apply {
|
get() = FakeMutableSet.fromSet(
|
||||||
|
mutableSetOf<String>().apply {
|
||||||
walk { k, _ ->
|
walk { k, _ ->
|
||||||
this += k
|
this += k
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [MutableCollection] of all values in this map. Note that this collection may contain duplicate values.
|
* Returns a [MutableCollection] of all values in this map. Note that this collection may contain duplicate values.
|
||||||
*/
|
*/
|
||||||
override val values: MutableCollection<T>
|
override val values: MutableCollection<T>
|
||||||
get() = FakeMutableCollection.fromCollection(mutableListOf<T>().apply {
|
get() = FakeMutableCollection.fromCollection(
|
||||||
|
mutableListOf<T>().apply {
|
||||||
walk { _, v ->
|
walk { _, v ->
|
||||||
this += v
|
this += v
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import org.jsoup.nodes.Document
|
|||||||
fun Response.interceptAsHtml(block: (Document) -> Unit): Response {
|
fun Response.interceptAsHtml(block: (Document) -> Unit): Response {
|
||||||
val body = body
|
val body = body
|
||||||
if (body?.contentType()?.type == "text" &&
|
if (body?.contentType()?.type == "text" &&
|
||||||
body.contentType()?.subtype == "html") {
|
body.contentType()?.subtype == "html"
|
||||||
|
) {
|
||||||
val bodyString = body.string()
|
val bodyString = body.string()
|
||||||
val rebuiltResponse = newBuilder()
|
val rebuiltResponse = newBuilder()
|
||||||
.body(ResponseBody.create(body.contentType(), bodyString))
|
.body(ResponseBody.create(body.contentType(), bodyString))
|
||||||
|
@ -37,14 +37,18 @@ suspend fun <T> Single<T>.await(subscribeOn: Scheduler? = null): T {
|
|||||||
return suspendCancellableCoroutine { continuation ->
|
return suspendCancellableCoroutine { continuation ->
|
||||||
val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this
|
val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this
|
||||||
lateinit var sub: Subscription
|
lateinit var sub: Subscription
|
||||||
sub = self.subscribe({
|
sub = self.subscribe(
|
||||||
|
{
|
||||||
continuation.resume(it) {
|
continuation.resume(it) {
|
||||||
sub.unsubscribe()
|
sub.unsubscribe()
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
if (!continuation.isCancelled)
|
{
|
||||||
|
if (!continuation.isCancelled) {
|
||||||
continuation.resumeWithException(it)
|
continuation.resumeWithException(it)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
continuation.invokeOnCancellation {
|
continuation.invokeOnCancellation {
|
||||||
sub.unsubscribe()
|
sub.unsubscribe()
|
||||||
@ -59,14 +63,18 @@ suspend fun Completable.awaitSuspending(subscribeOn: Scheduler? = null) {
|
|||||||
return suspendCancellableCoroutine { continuation ->
|
return suspendCancellableCoroutine { continuation ->
|
||||||
val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this
|
val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this
|
||||||
lateinit var sub: Subscription
|
lateinit var sub: Subscription
|
||||||
sub = self.subscribe({
|
sub = self.subscribe(
|
||||||
|
{
|
||||||
continuation.resume(Unit) {
|
continuation.resume(Unit) {
|
||||||
sub.unsubscribe()
|
sub.unsubscribe()
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
if (!continuation.isCancelled)
|
{
|
||||||
|
if (!continuation.isCancelled) {
|
||||||
continuation.resumeWithException(it)
|
continuation.resumeWithException(it)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
continuation.invokeOnCancellation {
|
continuation.invokeOnCancellation {
|
||||||
sub.unsubscribe()
|
sub.unsubscribe()
|
||||||
|
@ -18,10 +18,16 @@ fun UrlImportableSource.urlImportFetchSearchManga(query: String, fail: () -> Obs
|
|||||||
query.startsWith("http://") || query.startsWith("https://") -> {
|
query.startsWith("http://") || query.startsWith("https://") -> {
|
||||||
Observable.fromCallable {
|
Observable.fromCallable {
|
||||||
val res = galleryAdder.addGallery(query, false, this)
|
val res = galleryAdder.addGallery(query, false, this)
|
||||||
MangasPage((if (res is GalleryAddEvent.Success)
|
MangasPage(
|
||||||
|
(
|
||||||
|
if (res is GalleryAddEvent.Success) {
|
||||||
listOf(res.manga)
|
listOf(res.manga)
|
||||||
else
|
} else {
|
||||||
emptyList()), false)
|
emptyList()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> fail()
|
else -> fail()
|
||||||
|
@ -54,31 +54,35 @@ class ByteCursor(val content: ByteArray) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun expect(vararg bytes: Byte) {
|
fun expect(vararg bytes: Byte) {
|
||||||
if (bytes.size > remaining())
|
if (bytes.size > remaining()) {
|
||||||
throw IllegalStateException("Unexpected end of content!")
|
throw IllegalStateException("Unexpected end of content!")
|
||||||
|
}
|
||||||
|
|
||||||
for (i in 0..bytes.lastIndex) {
|
for (i in 0..bytes.lastIndex) {
|
||||||
val expected = bytes[i]
|
val expected = bytes[i]
|
||||||
val actual = content[index + i + 1]
|
val actual = content[index + i + 1]
|
||||||
|
|
||||||
if (expected != actual)
|
if (expected != actual) {
|
||||||
throw IllegalStateException("Unexpected content (expected: $expected, actual: $actual)!")
|
throw IllegalStateException("Unexpected content (expected: $expected, actual: $actual)!")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
index += bytes.size
|
index += bytes.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkEqual(vararg bytes: Byte): Boolean {
|
fun checkEqual(vararg bytes: Byte): Boolean {
|
||||||
if (bytes.size > remaining())
|
if (bytes.size > remaining()) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for (i in 0..bytes.lastIndex) {
|
for (i in 0..bytes.lastIndex) {
|
||||||
val expected = bytes[i]
|
val expected = bytes[i]
|
||||||
val actual = content[index + i + 1]
|
val actual = content[index + i + 1]
|
||||||
|
|
||||||
if (expected != actual)
|
if (expected != actual) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -147,10 +147,12 @@ class GroupSerializer(override val serializer: FilterSerializer) : Serializer<Fi
|
|||||||
override fun serialize(json: JsonObject, filter: Filter.Group<Any?>) {
|
override fun serialize(json: JsonObject, filter: Filter.Group<Any?>) {
|
||||||
json[STATE] = JsonArray().apply {
|
json[STATE] = JsonArray().apply {
|
||||||
filter.state.forEach {
|
filter.state.forEach {
|
||||||
add(if (it is Filter<*>)
|
add(
|
||||||
|
if (it is Filter<*>) {
|
||||||
serializer.serialize(it as Filter<Any?>)
|
serializer.serialize(it as Filter<Any?>)
|
||||||
else
|
} else {
|
||||||
JsonNull.INSTANCE
|
JsonNull.INSTANCE
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,10 +160,11 @@ class GroupSerializer(override val serializer: FilterSerializer) : Serializer<Fi
|
|||||||
|
|
||||||
override fun deserialize(json: JsonObject, filter: Filter.Group<Any?>) {
|
override fun deserialize(json: JsonObject, filter: Filter.Group<Any?>) {
|
||||||
json[STATE].asJsonArray.forEachIndexed { index, jsonElement ->
|
json[STATE].asJsonArray.forEachIndexed { index, jsonElement ->
|
||||||
if (!jsonElement.isJsonNull)
|
if (!jsonElement.isJsonNull) {
|
||||||
serializer.deserialize(filter.state[index] as Filter<Any?>, jsonElement.asJsonObject)
|
serializer.deserialize(filter.state[index] as Filter<Any?>, jsonElement.asJsonObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun mappings() = listOf(
|
override fun mappings() = listOf(
|
||||||
Pair(NAME, Filter.Group<Any?>::name)
|
Pair(NAME, Filter.Group<Any?>::name)
|
||||||
@ -195,8 +198,10 @@ class SortSerializer(override val serializer: FilterSerializer) : Serializer<Fil
|
|||||||
override fun deserialize(json: JsonObject, filter: Filter.Sort) {
|
override fun deserialize(json: JsonObject, filter: Filter.Sort) {
|
||||||
// Deserialize state
|
// Deserialize state
|
||||||
filter.state = json[STATE].nullObj?.let {
|
filter.state = json[STATE].nullObj?.let {
|
||||||
Filter.Sort.Selection(it[STATE_INDEX].int,
|
Filter.Sort.Selection(
|
||||||
it[STATE_ASCENDING].bool)
|
it[STATE_INDEX].int,
|
||||||
|
it[STATE_ASCENDING].bool
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { setUrl("https://maven.fabric.io/public") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user