diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index 0c9826962..327ffc75f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -83,10 +83,12 @@ class ChapterCache(private val context: Context) { // --> EH // Cache size is in MB private fun setupDiskCache(cacheSize: Long): DiskLruCache { - return DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY), - PARAMETER_APP_VERSION, - PARAMETER_VALUE_COUNT, - cacheSize * 1024 * 1024) + return DiskLruCache.open( + File(context.cacheDir, PARAMETER_CACHE_DIRECTORY), + PARAMETER_APP_VERSION, + PARAMETER_VALUE_COUNT, + cacheSize * 1024 * 1024 + ) } // <-- EH diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt index a255b0321..740d17cd3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt @@ -19,18 +19,22 @@ interface ChapterQueries : DbProvider { fun getChaptersByMangaId(mangaId: Long?) = db.get() .listOfObjects(Chapter::class.java) - .withQuery(Query.builder() + .withQuery( + Query.builder() .table(ChapterTable.TABLE) .where("${ChapterTable.COL_MANGA_ID} = ?") .whereArgs(mangaId) - .build()) + .build() + ) .prepare() fun getChaptersByMergedMangaId(mangaId: Long) = db.get() .listOfObjects(Chapter::class.java) - .withQuery(RawQuery.builder() + .withQuery( + RawQuery.builder() .query(getMergedChaptersQuery(mangaId)) - .build()) + .build() + ) .prepare() fun getRecentChapters(date: Date) = db.get() @@ -80,11 +84,13 @@ interface ChapterQueries : DbProvider { fun getChapters(url: String) = db.get() .listOfObjects(Chapter::class.java) - .withQuery(Query.builder() + .withQuery( + Query.builder() .table(ChapterTable.TABLE) .where("${ChapterTable.COL_URL} = ?") .whereArgs(url) - .build()) + .build() + ) .prepare() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index c84c95bdc..7a398d753 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -10,7 +10,8 @@ import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged /** * Query to get the manga merged into a merged manga */ -fun getMergedMangaQuery(id: Long) = """ +fun getMergedMangaQuery(id: Long) = + """ SELECT ${Manga.TABLE}.* FROM ( SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id @@ -22,7 +23,8 @@ fun getMergedMangaQuery(id: Long) = """ /** * Query to get the chapters of all manga in a merged manga */ -fun getMergedChaptersQuery(id: Long) = """ +fun getMergedChaptersQuery(id: Long) = + """ SELECT ${Chapter.TABLE}.* FROM ( SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt index 5feac16c1..9cacff754 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt @@ -21,10 +21,10 @@ class MangaUrlPutResolver : PutResolver() { } fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() fun mapToContentValues(manga: Manga) = ContentValues(1).apply { put(MangaTable.COL_URL, manga.url) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt index 3bb7297ab..18ac16fb7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt @@ -9,7 +9,8 @@ object MergedTable { const val COL_MANGA_ID = "mangaID" val createTableQuery: String - get() = """CREATE TABLE $TABLE( + get() = + """CREATE TABLE $TABLE( $COL_MERGE_ID INTEGER NOT NULL, $COL_MANGA_ID INTEGER NOT NULL )""" diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index e76c33dff..bcc938a79 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -147,7 +147,7 @@ class ExtensionManager( fun Extension.isBlacklisted( blacklistEnabled: Boolean = - preferences.eh_enableSourceBlacklist().get() + preferences.eh_enableSourceBlacklist().get() ): Boolean { return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt index 8b2384219..6d5e16837 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt @@ -37,7 +37,7 @@ interface LewdSource : CatalogueSource { private fun newMetaInstance() = metaClass.constructors.find { it.parameters.isEmpty() }?.call() - ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") + ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") /** * Parses metadata from the input and then copies it into the manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt index a9357d956..1f022a1fb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt @@ -19,10 +19,12 @@ interface UrlImportableSource : Source { return try { val uri = URI(url) var out = uri.path - if (uri.query != null) + if (uri.query != null) { out += "?" + uri.query - if (uri.fragment != null) + } + if (uri.fragment != null) { out += "#" + uri.fragment + } out } catch (e: URISyntaxException) { url diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt index bf942fdb0..786ae98f5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt @@ -73,16 +73,18 @@ class EHentai( override val metaClass = EHentaiSearchMetadata::class val schema: String - get() = if (prefs.secureEXH().getOrDefault()) + get() = if (prefs.secureEXH().getOrDefault()) { "https" - else + } else { "http" + } val domain: String - get() = if (exh) + get() = if (exh) { "exhentai.org" - else + } else { "e-hentai.org" + } override val baseUrl: String get() = "$schema://$domain" @@ -111,25 +113,27 @@ class EHentai( val favElement = column2.children().find { it.attr("style").startsWith("border-color") } ParsedManga( - fav = FAVORITES_BORDER_HEX_COLORS.indexOf( - favElement?.attr("style")?.substring(14, 17) - ), - manga = Manga.create(id).apply { - // Get title - title = thumbnailElement.attr("title") - url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href")) - // Get image - thumbnail_url = thumbnailElement.attr("src") + fav = FAVORITES_BORDER_HEX_COLORS.indexOf( + favElement?.attr("style")?.substring(14, 17) + ), + manga = Manga.create(id).apply { + // Get title + title = thumbnailElement.attr("title") + url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href")) + // Get image + thumbnail_url = thumbnailElement.attr("src") - // TODO Parse genre + uploader + tags - }) + // TODO Parse genre + uploader + tags + } + ) } val parsedLocation = doc.location().toHttpUrlOrNull() // Add to page if required val hasNextPage = if (parsedLocation == null || - !parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) { + !parsedLocation.queryParameterNames.contains(REVERSE_PARAM) + ) { select("a[onclick=return false]").last()?.let { it.text() == ">" } ?: false @@ -160,7 +164,7 @@ class EHentai( while (true) { val gid = EHentaiSearchMetadata.galleryId(url).toInt() val cachedParent = updateHelper.parentLookupTable.get( - gid + gid ) if (cachedParent == null) { throttleFunc() @@ -175,19 +179,19 @@ class EHentai( if (parentLink != null) { updateHelper.parentLookupTable.put( - gid, - GalleryEntry( - EHentaiSearchMetadata.galleryId(parentLink), - EHentaiSearchMetadata.galleryToken(parentLink) - ) + gid, + GalleryEntry( + EHentaiSearchMetadata.galleryId(parentLink), + EHentaiSearchMetadata.galleryToken(parentLink) + ) ) url = EHentaiSearchMetadata.normalizeUrl(parentLink) } else break } else { XLog.d("Parent cache hit: %s!", gid) url = EHentaiSearchMetadata.idAndTokenToUrl( - cachedParent.gId, - cachedParent.gToken + cachedParent.gId, + cachedParent.gToken ) } } @@ -201,9 +205,11 @@ class EHentai( url = EHentaiSearchMetadata.normalizeUrl(d.location()) name = "v1: " + d.selectFirst("#gn").text() chapter_number = 1f - date_upload = EX_DATE_FORMAT.parse(d.select("#gdd .gdt1").find { el -> - el.text().toLowerCase() == "posted:" - }!!.nextElementSibling().text()).time + date_upload = EX_DATE_FORMAT.parse( + d.select("#gdd .gdt1").find { el -> + el.text().toLowerCase() == "posted:" + }!!.nextElementSibling().text() + ).time } // Build and append the rest of the galleries if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self) @@ -253,27 +259,32 @@ class EHentai( }.sortedBy(Pair::first).map { it.second } } - private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess() - private fun chapterPageRequest(np: String) = exGet(np, null, headers) + private fun chapterPageCall(np: String): Observable { + return client.newCall(chapterPageRequest(np)).asObservableSuccess() + } + private fun chapterPageRequest(np: String): Request { + return exGet(np, null, headers) + } private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let { return if (it.text() == ">") it.attr("href") else null } - override fun popularMangaRequest(page: Int) = if (exh) + override fun popularMangaRequest(page: Int) = if (exh) { latestUpdatesRequest(page) - else + } else { exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists + } // Support direct URL importing override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - urlImportFetchSearchManga(query) { - searchMangaRequestObservable(page, query, filters).flatMap { - client.newCall(it).asObservableSuccess() - }.map { response -> - searchMangaParse(response) - } + urlImportFetchSearchManga(query) { + searchMangaRequestObservable(page, query, filters).flatMap { + client.newCall(it).asObservableSuccess() + }.map { response -> + searchMangaParse(response) } + } private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable { val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon() @@ -287,20 +298,20 @@ class EHentai( // Reverse search results on filter if (filters.any { it is ReverseFilter && it.state }) { return client.newCall(request) - .asObservableSuccess() - .map { - val doc = it.asJsoup() + .asObservableSuccess() + .map { + val doc = it.asJsoup() - val elements = doc.select(".ptt > tbody > tr > td") + val elements = doc.select(".ptt > tbody > tr > td") - val totalElement = elements[elements.size - 2] + val totalElement = elements[elements.size - 2] - val thisPage = totalElement.text().toInt() - (page - 1) + val thisPage = totalElement.text().toInt() - (page - 1) - uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString()) + uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString()) - exGet(uri.toString(), thisPage) - } + exGet(uri.toString(), thisPage) + } } else { return Observable.just(request) } @@ -314,22 +325,28 @@ class EHentai( override fun searchMangaParse(response: Response) = genericMangaParse(response) override fun latestUpdatesParse(response: Response) = genericMangaParse(response) - fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true) = GET(page?.let { - addParam(url, "page", Integer.toString(page - 1)) - } ?: url, additionalHeaders?.let { - val headers = headers.newBuilder() - it.toMultimap().forEach { (t, u) -> - u.forEach { - headers.add(t, it) + fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true): Request { + return GET( + page?.let { + addParam(url, "page", Integer.toString(page - 1)) + } ?: url, + additionalHeaders?.let { + val headers = headers.newBuilder() + it.toMultimap().forEach { (t, u) -> + u.forEach { + headers.add(t, it) + } + } + headers.build() + } ?: headers + ).let { + if (!cache) { + it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build() + } else { + it } } - headers.build() - } ?: headers).let { - if (!cache) - it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build() - else - it - }!! + } /** * Returns an observable with the updated details for a manga. Normally it's not needed to @@ -339,33 +356,37 @@ class EHentai( */ override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableWithAsyncStacktrace() - .flatMap { (stacktrace, response) -> - if (response.isSuccessful) { - // Pull to most recent - val doc = response.asJsoup() - val newerGallery = doc.select("#gnd a").lastOrNull() - val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) { - manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href")) - client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess().map { it.asJsoup() } - } else Observable.just(doc) + .asObservableWithAsyncStacktrace() + .flatMap { (stacktrace, response) -> + if (response.isSuccessful) { + // Pull to most recent + val doc = response.asJsoup() + val newerGallery = doc.select("#gnd a").lastOrNull() + val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) { + manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href")) + client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess().map { it.asJsoup() } + } else Observable.just(doc) - pre.flatMap { - parseToManga(manga, it).andThen(Observable.just(manga.apply { - initialized = true - })) - } + pre.flatMap { + parseToManga(manga, it).andThen( + Observable.just( + manga.apply { + initialized = true + } + ) + ) + } + } else { + response.close() + + if (response.code == 404) { + throw GalleryNotFoundException(stacktrace) } else { - response.close() - - if (response.code == 404) { - throw GalleryNotFoundException(stacktrace) - } else { - throw Exception("HTTP error ${response.code}", stacktrace) - } + throw Exception("HTTP error ${response.code}", stacktrace) } } + } } /** @@ -389,11 +410,11 @@ class EHentai( it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')')) } genre = select(".cs") - .attr("onclick") - .nullIfBlank() - ?.trim() - ?.substringAfterLast('/') - ?.removeSuffix("'") + .attr("onclick") + .nullIfBlank() + ?.trim() + ?.substringAfterLast('/') + ?.removeSuffix("'") uploader = select("#gdn").text().nullIfBlank()?.trim() @@ -404,8 +425,10 @@ class EHentai( val right = rightElement.text().nullIfBlank()?.trim() if (left != null && right != null) { ignore { - when (left.removeSuffix(":") - .toLowerCase()) { + when ( + left.removeSuffix(":") + .toLowerCase() + ) { "posted" -> datePosted = EX_DATE_FORMAT.parse(right).time // Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/ // Example JP gallery: https://exhentai.org/g/1375385/03519d541b/ @@ -428,7 +451,8 @@ class EHentai( lastUpdateCheck = System.currentTimeMillis() if (datePosted != null && - lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) { + lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME + ) { aged = true XLog.d("aged %s - too old", title) } @@ -436,32 +460,35 @@ class EHentai( // Parse ratings ignore { averageRating = select("#rating_label") - .text() - .removePrefix("Average:") - .trim() - .nullIfBlank() - ?.toDouble() + .text() + .removePrefix("Average:") + .trim() + .nullIfBlank() + ?.toDouble() ratingCount = select("#rating_count") - .text() - .trim() - .nullIfBlank() - ?.toInt() + .text() + .trim() + .nullIfBlank() + ?.toInt() } // Parse tags tags.clear() select("#taglist tr").forEach { val namespace = it.select(".tc").text().removeSuffix(":") - tags.addAll(it.select("div").map { element -> - RaisedTag( + tags.addAll( + it.select("div").map { element -> + RaisedTag( namespace, element.text().trim(), - if (element.hasClass("gtl")) + if (element.hasClass("gtl")) { TAG_TYPE_LIGHT - else + } else { TAG_TYPE_NORMAL - ) - }) + } + ) + } + ) } // Add genre as virtual tag @@ -478,8 +505,8 @@ class EHentai( override fun fetchImageUrl(page: Page): Observable { return client.newCall(imageUrlRequest(page)) - .asObservableSuccess() - .map { realImageUrlParse(it, page) } + .asObservableSuccess() + .map { realImageUrlParse(it, page) } } fun realImageUrlParse(response: Response, page: Page): String { @@ -505,9 +532,13 @@ class EHentai( var favNames: List? = null do { - val response2 = client.newCall(exGet(favoriteUrl, + val response2 = client.newCall( + exGet( + favoriteUrl, page = page, - cache = false)).execute() + cache = false + ) + ).execute() val doc = response2.asJsoup() // Parse favorites @@ -515,22 +546,24 @@ class EHentai( result += parsed.first // Parse fav names - if (favNames == null) + if (favNames == null) { favNames = doc.select(".fp:not(.fps)").mapNotNull { it.child(2).text() } - + } // Next page + page++ } while (parsed.second) return Pair(result as List, favNames!!) } - fun spPref() = if (exh) + fun spPref() = if (exh) { prefs.eh_exhSettingsProfile() - else + } else { prefs.eh_ehSettingsProfile() + } fun rawCookies(sp: Int): Map { val cookies: MutableMap = mutableMapOf() @@ -541,16 +574,19 @@ class EHentai( cookies["sp"] = sp.toString() val sessionKey = prefs.eh_settingsKey().getOrDefault() - if (sessionKey != null) + if (sessionKey != null) { cookies["sk"] = sessionKey + } val sessionCookie = prefs.eh_sessionCookie().getOrDefault() - if (sessionCookie != null) + if (sessionCookie != null) { cookies["s"] = sessionCookie + } val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault() - if (hathPerksCookie != null) + if (hathPerksCookie != null) { cookies["hath_perks"] = hathPerksCookie + } } // Session-less extended display mode (for users without ExHentai) @@ -568,51 +604,57 @@ class EHentai( override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!! fun addParam(url: String, param: String, value: String) = Uri.parse(url) - .buildUpon() - .appendQueryParameter(param, value) - .toString() + .buildUpon() + .appendQueryParameter(param, value) + .toString() override val client = network.client.newBuilder() - .cookieJar(CookieJar.NO_COOKIES) - .addInterceptor { chain -> - val newReq = chain - .request() - .newBuilder() - .removeHeader("Cookie") - .addHeader("Cookie", cookiesHeader()) - .build() + .cookieJar(CookieJar.NO_COOKIES) + .addInterceptor { chain -> + val newReq = chain + .request() + .newBuilder() + .removeHeader("Cookie") + .addHeader("Cookie", cookiesHeader()) + .build() - chain.proceed(newReq) - }.build()!! + chain.proceed(newReq) + }.build()!! // Filters override fun getFilterList() = FilterList( - Watched(), - GenreGroup(), - AdvancedGroup(), - ReverseFilter() + Watched(), + GenreGroup(), + AdvancedGroup(), + ReverseFilter() ) class Watched : Filter.CheckBox("Watched List"), UriFilter { override fun addToUri(builder: Uri.Builder) { - if (state) + if (state) { builder.appendPath("watched") + } } } class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false) - class GenreGroup : Filter.Group("Genres", listOf( - GenreOption("Dōjinshi", 2), - GenreOption("Manga", 4), - GenreOption("Artist CG", 8), - GenreOption("Game CG", 16), - GenreOption("Western", 512), - GenreOption("Non-H", 256), - GenreOption("Image Set", 32), - GenreOption("Cosplay", 64), - GenreOption("Asian Porn", 128), - GenreOption("Misc", 1) - )), UriFilter { + class GenreGroup : + Filter.Group( + "Genres", + listOf( + GenreOption("Dōjinshi", 2), + GenreOption("Manga", 4), + GenreOption("Artist CG", 8), + GenreOption("Game CG", 16), + GenreOption("Western", 512), + GenreOption("Non-H", 256), + GenreOption("Image Set", 32), + GenreOption("Cosplay", 64), + GenreOption("Asian Porn", 128), + GenreOption("Misc", 1) + ) + ), + UriFilter { override fun addToUri(builder: Uri.Builder) { val bits = state.fold(0) { acc, genre -> if (!genre.state) acc + genre.genreId else acc @@ -623,8 +665,9 @@ class EHentai( class AdvancedOption(name: String, val param: String, defValue: Boolean = false) : Filter.CheckBox(name, defValue), UriFilter { override fun addToUri(builder: Uri.Builder) { - if (state) + if (state) { builder.appendQueryParameter(param, "on") + } } } @@ -643,13 +686,18 @@ class EHentai( class MinPagesOption : PageOption("Minimum Pages", "f_spf") class MaxPagesOption : PageOption("Maximum Pages", "f_spt") - class RatingOption : Filter.Select("Minimum Rating", arrayOf( - "Any", - "2 stars", - "3 stars", - "4 stars", - "5 stars" - )), UriFilter { + class RatingOption : + Filter.Select( + "Minimum Rating", + arrayOf( + "Any", + "2 stars", + "3 stars", + "4 stars", + "5 stars" + ) + ), + UriFilter { override fun addToUri(builder: Uri.Builder) { if (state > 0) { builder.appendQueryParameter("f_srdd", Integer.toString(state + 1)) @@ -658,7 +706,9 @@ class EHentai( } } - class AdvancedGroup : UriGroup>("Advanced Options", listOf( + class AdvancedGroup : UriGroup>( + "Advanced Options", + listOf( AdvancedOption("Search Gallery Name", "f_sname", true), AdvancedOption("Search Gallery Tags", "f_stags", true), AdvancedOption("Search Gallery Description", "f_sdesc"), @@ -670,24 +720,26 @@ class EHentai( RatingOption(), MinPagesOption(), MaxPagesOption() - )) + ) + ) class ReverseFilter : Filter.CheckBox("Reverse search results") - override val name = if (exh) + override val name = if (exh) { "ExHentai" - else + } else { "E-Hentai" + } class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause) // === URL IMPORT STUFF override val matchingHosts: List = if (exh) listOf( - "exhentai.org" + "exhentai.org" ) else listOf( - "g.e-hentai.org", - "e-hentai.org" + "g.e-hentai.org", + "e-hentai.org" ) override fun mapUrlToMangaUrl(uri: Uri): String? { @@ -717,17 +769,23 @@ class EHentai( val json = JsonObject() json["method"] = "gtoken" json["pagelist"] = JsonArray().apply { - add(JsonArray().apply { - add(gallery.toInt()) - add(pageToken) - add(pageNum.toInt()) - }) + add( + JsonArray().apply { + add(gallery.toInt()) + add(pageToken) + add(pageNum.toInt()) + } + ) } - val outJson = JsonParser.parseString(client.newCall(Request.Builder() - .url(EH_API_BASE) - .post(RequestBody.create(JSON, json.toString())) - .build()).execute().body!!.string()).obj + val outJson = JsonParser.parseString( + client.newCall( + Request.Builder() + .url(EH_API_BASE) + .post(RequestBody.create(JSON, json.toString())) + .build() + ).execute().body!!.string() + ).obj val obj = outJson["tokenlist"].array.first() return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/" @@ -742,16 +800,16 @@ class EHentai( private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!! private val FAVORITES_BORDER_HEX_COLORS = listOf( - "000", - "f00", - "fa0", - "dd0", - "080", - "9f4", - "4bf", - "00f", - "508", - "e8e" + "000", + "f00", + "fa0", + "dd0", + "080", + "9f4", + "4bf", + "00f", + "508", + "e8e" ) fun buildCookies(cookies: Map) = cookies.entries.joinToString(separator = "; ") { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt index 90ba4e2c2..dce5d521c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt @@ -65,7 +65,8 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo private fun tagIndexVersion(): Single { val sCachedTagIndexVersion = cachedTagIndexVersion return if (sCachedTagIndexVersion == null || - tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) { + tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis() + ) { HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext { cachedTagIndexVersion = it tagIndexVersionCacheTime = System.currentTimeMillis() @@ -80,7 +81,8 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo private fun galleryIndexVersion(): Single { val sCachedGalleryIndexVersion = cachedGalleryIndexVersion return if (sCachedGalleryIndexVersion == null || - galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) { + galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis() + ) { HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext { cachedGalleryIndexVersion = it galleryIndexVersionCacheTime = System.currentTimeMillis() @@ -162,9 +164,9 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo * @param page the page number to retrieve. */ override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet( - "$LTN_BASE_URL/popular-all.nozomi", - 100L * (page - 1), - 99L + 100 * (page - 1) + "$LTN_BASE_URL/popular-all.nozomi", + 100L * (page - 1), + 99L + 100 * (page - 1) ) /** @@ -192,7 +194,7 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo // TODO Cache the results coming out of HitomiNozomi val hn = Single.zip(tagIndexVersion(), galleryIndexVersion()) { tv, gv -> tv to gv } - .map { HitomiNozomi(client, it.first, it.second) } + .map { HitomiNozomi(client, it.first, it.second) } var base = if (positive.isEmpty()) { hn.flatMap { n -> n.getGalleryIdsFromNozomi(null, "index", "all").map { n to it.toSet() } } @@ -240,9 +242,9 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo * @param page the page number to retrieve. */ override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet( - "$LTN_BASE_URL/index-all.nozomi", - 100L * (page - 1), - 99L + 100 * (page - 1) + "$LTN_BASE_URL/index-all.nozomi", + 100L * (page - 1), + 99L + 100 * (page - 1) ) /** @@ -254,14 +256,14 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo override fun fetchPopularManga(page: Int): Observable { return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .flatMap { responseToMangas(it) } + .asObservableSuccess() + .flatMap { responseToMangas(it) } } override fun fetchLatestUpdates(page: Int): Observable { return client.newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .flatMap { responseToMangas(it) } + .asObservableSuccess() + .flatMap { responseToMangas(it) } } fun responseToMangas(response: Response): Observable { @@ -270,9 +272,9 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo val end = range.substringBefore('/').substringAfter('-').toLong() val body = response.body!! return parseNozomiPage(body.bytes()) - .map { - MangasPage(it, end < total - 1) - } + .map { + MangasPage(it, end < total - 1) + } } private fun parseNozomiPage(array: ByteArray): Observable> { @@ -285,13 +287,15 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo } private fun nozomiIdsToMangas(ids: List): Single> { - return Single.zip(ids.map { - client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html")) + return Single.zip( + ids.map { + client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html")) .asObservableSuccess() .subscribeOn(Schedulers.io()) // Perform all these requests in parallel .map { parseGalleryBlock(it) } .toSingle() - }) { it.map { m -> m as SManga } } + } + ) { it.map { m -> m as SManga } } } private fun parseGalleryBlock(response: Response): SManga { @@ -318,23 +322,27 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo */ override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply { - initialized = true - })) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()).andThen( + Observable.just( + manga.apply { + initialized = true + } + ) + ) + } } override fun fetchChapterList(manga: SManga): Observable> { return Observable.just( - listOf( - SChapter.create().apply { - url = manga.url - name = "Chapter" - chapter_number = 0.0f - } - ) + listOf( + SChapter.create().apply { + url = manga.url + name = "Chapter" + chapter_number = 0.0f + } + ) ) } @@ -372,9 +380,9 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo val hashPath1 = hash.takeLast(1) val hashPath2 = hash.takeLast(3).take(2) Page( - index, - "", - "https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext" + index, + "", + "https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext" ) } } @@ -396,19 +404,20 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo it[it.lastIndex - 1] } return request.newBuilder() - .header("Referer", "$BASE_URL/reader/$hlId.html") - .build() + .header("Referer", "$BASE_URL/reader/$hlId.html") + .build() } override val matchingHosts = listOf( - "hitomi.la" + "hitomi.la" ) override fun mapUrlToMangaUrl(uri: Uri): String? { val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null - if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader") + if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader") { return null + } return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html" } @@ -419,10 +428,11 @@ class Hitomi : HttpSource(), LewdSource, UrlImpo private val NUMBER_OF_FRONTENDS = 2 private val DATE_FORMAT by lazy { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.US) - else + } else { SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt index 9c04cd7ed..6ef3fe416 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt @@ -80,7 +80,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource().firstOrNull()?.state - ?: defaultSortFilterSelection() + ?: defaultSortFilterSelection() if (sortFilter.index == 1) { if (query.isBlank()) error("You must specify a search query if you wish to sort by popularity!") @@ -89,22 +89,22 @@ class NHentai(context: Context) : HttpSource(), LewdSource 1).toString()) - uri.appendQueryParameter("page", thisPage.toString()) + uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString()) + uri.appendQueryParameter("page", thisPage.toString()) - nhGet(uri.toString(), page) - } + nhGet(uri.toString(), page) + } } uri.appendQueryParameter("page", page.toString()) @@ -134,12 +134,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it).andThen(Observable.just(manga.apply { - initialized = true - })) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it).andThen( + Observable.just( + manga.apply { + initialized = true + } + ) + ) + } } override fun mangaDetailsRequest(manga: SManga) = nhGet(baseUrl + manga.url) @@ -208,31 +212,38 @@ class NHentai(context: Context) : HttpSource(), LewdSource - if (metadata.mediaId == null) emptyList() - else + if (metadata.mediaId == null) { + emptyList() + } else { metadata.pageImageTypes.mapIndexed { index, s -> val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s) Page(index, imageUrl!!, imageUrl) } + } }.toObservable() override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!! @@ -259,9 +270,9 @@ class NHentai(context: Context) : HttpSource(), LewdSource("Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray()) class SortFilter : Filter.Sort( - "Sort", - arrayOf("Date", "Popular"), - defaultSortFilterSelection() + "Sort", + arrayOf("Date", "Popular"), + defaultSortFilterSelection() ) val appName by lazy { @@ -269,14 +280,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource, UrlImportableSource { +class PervEden(override val id: Long, val pvLang: PervEdenLang) : + ParsedHttpSource(), + LewdSource, + UrlImportableSource { /** * The class of the metadata used by this source */ @@ -62,9 +64,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour // Support direct URL importing override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - urlImportFetchSearchManga(query) { - super.fetchSearchManga(page, query, filters) - } + urlImportFetchSearchManga(query) { + super.fetchSearchManga(page, query, filters) + } override fun searchMangaSelector() = "#mangaList > tbody > tr" @@ -79,9 +81,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour override fun searchMangaNextPageSelector() = ".next" override fun popularMangaRequest(page: Int): Request { - val urlLang = if (lang == "en") + val urlLang = if (lang == "en") { "eng" - else "it" + } else { + "it" + } return GET("$baseUrl/$urlLang/") } @@ -129,12 +133,16 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour */ override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply { - initialized = true - })) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()).andThen( + Observable.just( + manga.apply { + initialized = true + } + ) + ) + } } /** @@ -165,8 +173,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour "Alternative name(s)" -> { if (it is TextNode) { val text = it.text().trim() - if (!text.isBlank()) + if (!text.isBlank()) { newAltTitles += text + } } } "Artist" -> { @@ -176,21 +185,24 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour } } "Genres" -> { - if (it is Element && it.tagName() == "a") + if (it is Element && it.tagName() == "a") { tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT) + } } "Type" -> { if (it is TextNode) { val text = it.text().trim() - if (!text.isBlank()) + if (!text.isBlank()) { type = text + } } } "Status" -> { if (it is TextNode) { val text = it.text().trim() - if (!text.isBlank()) + if (!text.isBlank()) { status = text + } } } } @@ -224,10 +236,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour name = "Chapter " + linkElement.getElementsByTag("b").text() ChapterRecognition.parseChapterNumber( - this, - SManga.create().apply { - title = "" - }) + this, + SManga.create().apply { + title = "" + } + ) try { date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim())!!.time @@ -242,37 +255,49 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour override fun imageUrlParse(document: Document) = "http:" + document.getElementById("mainImg").attr("src")!! override fun getFilterList() = FilterList( - AuthorFilter(), - ArtistFilter(), - TypeFilterGroup(), - ReleaseYearGroup(), - StatusFilterGroup() + AuthorFilter(), + ArtistFilter(), + TypeFilterGroup(), + ReleaseYearGroup(), + StatusFilterGroup() ) - class StatusFilterGroup : UriGroup("Status", listOf( + class StatusFilterGroup : UriGroup( + "Status", + listOf( StatusFilter("Ongoing", 1), StatusFilter("Completed", 2), StatusFilter("Suspended", 0) - )) + ) + ) class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter { override fun addToUri(builder: Uri.Builder) { - if (state) + if (state) { builder.appendQueryParameter("status", id.toString()) + } } } // Explicit type arg for listOf() to workaround this: KT-16570 - class ReleaseYearGroup : UriGroup>("Release Year", listOf( + class ReleaseYearGroup : UriGroup>( + "Release Year", + listOf( ReleaseYearRangeFilter(), ReleaseYearYearFilter() - )) + ) + ) - class ReleaseYearRangeFilter : Filter.Select("Range", arrayOf( - "on", - "after", - "before" - )), UriFilter { + class ReleaseYearRangeFilter : + Filter.Select( + "Range", + arrayOf( + "on", + "after", + "before" + ) + ), + UriFilter { override fun addToUri(builder: Uri.Builder) { builder.appendQueryParameter("releasedType", state.toString()) } @@ -296,18 +321,22 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour } } - class TypeFilterGroup : UriGroup("Type", listOf( + class TypeFilterGroup : UriGroup( + "Type", + listOf( TypeFilter("Japanese Manga", 0), TypeFilter("Korean Manhwa", 1), TypeFilter("Chinese Manhua", 2), TypeFilter("Comic", 3), TypeFilter("Doujinshi", 4) - )) + ) + ) class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter { override fun addToUri(builder: Uri.Builder) { - if (state) + if (state) { builder.appendQueryParameter("type", id.toString()) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt index 8704fd5b5..57d950ea1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt @@ -41,9 +41,10 @@ import rx.schedulers.Schedulers typealias SiteMap = NakedTrie -class EightMuses : HttpSource(), - LewdSource, - UrlImportableSource { +class EightMuses : + HttpSource(), + LewdSource, + UrlImportableSource { override val id = EIGHTMUSES_SOURCE_ID /** @@ -74,10 +75,10 @@ class EightMuses : HttpSource(), private suspend fun obtainSiteMap() = siteMapCache.obtain { withContext(Dispatchers.IO) { val result = client.newCall(eightMusesGet("$baseUrl/sitemap/1.xml")) - .asObservableSuccess() - .toSingle() - .await(Schedulers.io()) - .body!!.string() + .asObservableSuccess() + .toSingle() + .await(Schedulers.io()) + .body!!.string() val parsed = Jsoup.parse(result) @@ -93,10 +94,10 @@ class EightMuses : HttpSource(), override fun headersBuilder(): Headers.Builder { return Headers.Builder() - .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;") - .add("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") - .add("Referer", "https://www.8muses.com") - .add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;") + .add("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") + .add("Referer", "https://www.8muses.com") + .add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") } private fun eightMusesGet(url: String): Request { @@ -129,11 +130,11 @@ class EightMuses : HttpSource(), override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val urlBuilder = if (!query.isBlank()) { "$baseUrl/search".toHttpUrlOrNull()!! - .newBuilder() - .addQueryParameter("q", query) + .newBuilder() + .addQueryParameter("q", query) } else { "$baseUrl/comics".toHttpUrlOrNull()!! - .newBuilder() + .newBuilder() } urlBuilder.addQueryParameter("page", page.toString()) @@ -182,12 +183,14 @@ class EightMuses : HttpSource(), private fun fetchListing(request: Request, dig: Boolean): Observable { return client.newCall(request) - .asObservableSuccess() - .flatMapSingle { response -> - RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) { + .asObservableSuccess() + .flatMapSingle { response -> + RxJavaInterop.toV1Single( + GlobalScope.async(Dispatchers.IO) { parseResultsPage(response, dig) - }.asSingle(GlobalScope.coroutineContext)) - } + }.asSingle(GlobalScope.coroutineContext) + ) + } } private suspend fun parseResultsPage(response: Response, dig: Boolean): MangasPage { @@ -197,32 +200,32 @@ class EightMuses : HttpSource(), val onLastPage = doc.selectFirst(".current:nth-last-child(2)") != null return MangasPage( - if (dig) { - contents.albums.flatMap { - val href = it.attr("href") - val splitHref = href.split('/') - obtainSiteMap().subMap(href).filter { - it.key.split('/').size - splitHref.size == 1 - }.map { (key, _) -> - SManga.create().apply { - url = key - - title = key.substringAfterLast('/').replace('-', ' ') - } - } - } - } else { - contents.albums.map { + if (dig) { + contents.albums.flatMap { + val href = it.attr("href") + val splitHref = href.split('/') + obtainSiteMap().subMap(href).filter { + it.key.split('/').size - splitHref.size == 1 + }.map { (key, _) -> SManga.create().apply { - url = it.attr("href") + url = key - title = it.select(".title-text").text() - - thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src") + title = key.substringAfterLast('/').replace('-', ' ') } } - }, - !onLastPage + } + } else { + contents.albums.map { + SManga.create().apply { + url = it.attr("href") + + title = it.select(".title-text").text() + + thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src") + } + } + }, + !onLastPage ) } @@ -243,10 +246,10 @@ class EightMuses : HttpSource(), */ override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) + } } /** @@ -259,9 +262,11 @@ class EightMuses : HttpSource(), } override fun fetchChapterList(manga: SManga): Observable> { - return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) { - fetchAndParseChapterList("", manga.url) - }.asSingle(GlobalScope.coroutineContext)).toObservable() + return RxJavaInterop.toV1Single( + GlobalScope.async(Dispatchers.IO) { + fetchAndParseChapterList("", manga.url) + }.asSingle(GlobalScope.coroutineContext) + ).toObservable() } private suspend fun fetchAndParseChapterList(prefix: String, url: String): List { @@ -309,9 +314,9 @@ class EightMuses : HttpSource(), val contents = parseSelf(response.asJsoup()) return contents.images.mapIndexed { index, element -> Page( - index, - element.attr("href"), - "$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9) + index, + element.attr("href"), + "$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9) ) } } @@ -325,30 +330,30 @@ class EightMuses : HttpSource(), title = breadcrumbs.selectFirst("li:nth-last-child(1) > a").text() thumbnailUrl = parseSelf(input).let { it.albums + it.images }.firstOrNull() - ?.selectFirst(".lazyload") - ?.attr("data-src")?.let { - baseUrl + it - } + ?.selectFirst(".lazyload") + ?.attr("data-src")?.let { + baseUrl + it + } tags.clear() tags += RaisedTag( - EightMusesSearchMetadata.ARTIST_NAMESPACE, - breadcrumbs.selectFirst("li:nth-child(2) > a").text(), - EightMusesSearchMetadata.TAG_TYPE_DEFAULT + EightMusesSearchMetadata.ARTIST_NAMESPACE, + breadcrumbs.selectFirst("li:nth-child(2) > a").text(), + EightMusesSearchMetadata.TAG_TYPE_DEFAULT ) tags += input.select(".album-tags a").map { RaisedTag( - EightMusesSearchMetadata.TAGS_NAMESPACE, - it.text(), - EightMusesSearchMetadata.TAG_TYPE_DEFAULT + EightMusesSearchMetadata.TAGS_NAMESPACE, + it.text(), + EightMusesSearchMetadata.TAG_TYPE_DEFAULT ) } } } class SortFilter : Filter.Select( - "Sort", - SORT_OPTIONS.map { it.second }.toTypedArray() + "Sort", + SORT_OPTIONS.map { it.second }.toTypedArray() ) { fun addToUri(url: HttpUrl.Builder) { url.addQueryParameter("sort", SORT_OPTIONS[state].first) @@ -357,16 +362,16 @@ class EightMuses : HttpSource(), companion object { // private val SORT_OPTIONS = listOf( - "" to "Views", - "like" to "Likes", - "date" to "Date", - "az" to "A-Z" + "" to "Views", + "like" to "Likes", + "date" to "Date", + "az" to "A-Z" ) } } override fun getFilterList() = FilterList( - SortFilter() + SortFilter() ) /** @@ -379,8 +384,8 @@ class EightMuses : HttpSource(), } override val matchingHosts = listOf( - "www.8muses.com", - "8muses.com" + "www.8muses.com", + "8muses.com" ) override fun mapUrlToMangaUrl(uri: Uri): String? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt index 4d9e9762b..414747474 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt @@ -62,15 +62,15 @@ class HBrowse : HttpSource(), LewdSource, UrlIm override val id: Long = HBROWSE_SOURCE_ID override fun headersBuilder() = Headers.Builder() - .add("Cookie", BASE_COOKIES) + .add("Cookie", BASE_COOKIES) private val clientWithoutCookies = client.newBuilder() - .cookieJar(CookieJar.NO_COOKIES) - .build() + .cookieJar(CookieJar.NO_COOKIES) + .build() private val nonRedirectingClientWithoutCookies = clientWithoutCookies.newBuilder() - .followRedirects(false) - .build() + .followRedirects(false) + .build() private val searchEngine = SearchEngine() @@ -96,8 +96,8 @@ class HBrowse : HttpSource(), LewdSource, UrlIm val hasNextPage = doc.selectFirst("#main > p > a[title~=jump]:nth-last-child(1)") != null return MangasPage( - manga, - hasNextPage + manga, + hasNextPage ) } @@ -132,129 +132,138 @@ class HBrowse : HttpSource(), LewdSource, UrlIm 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 { - return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) { - val modeFilter = filters.filterIsInstance().firstOrNull() - val sortFilter = filters.filterIsInstance().firstOrNull() + return RxJavaInterop.toV1Single( + GlobalScope.async(Dispatchers.IO) { + val modeFilter = filters.filterIsInstance().firstOrNull() + val sortFilter = filters.filterIsInstance().firstOrNull() - var base: String? = null - var isSortFilter = false - // - var tagQuery: List>? = null + var base: String? = null + var isSortFilter = false + // + var tagQuery: List>? = null - if (sortFilter != null) { - sortFilter.state?.let { state -> - if (query.isNotBlank()) { - throw IllegalArgumentException("Cannot use sorting while text/tag search is active!") - } - - isSortFilter = true - base = "/browse/title/${SortFilter.SORT_OPTIONS[state.index].first}/${if (state.ascending) "ASC" else "DESC"}" - } - } - - if (base == null) { - base = if (modeFilter != null && modeFilter.state == 1) { - tagQuery = searchEngine.parseQuery(query, false).map { - when (it) { - is Text -> { - var minDist = Int.MAX_VALUE.toDouble() - // ns, value - var minContent: Pair = "" to "" - for (ns in ALL_TAGS) { - val (v, d) = ns.value.nearest(it.rawTextOnly(), minDist) - if (d < minDist) { - minDist = d - minContent = ns.key to v - } - } - minContent - } - is Namespace -> { - // Map ns aliases - val mappedNs = NS_MAPPINGS[it.namespace] ?: it.namespace - - var key = mappedNs - if (!ALL_TAGS.containsKey(key)) key = ALL_TAGS.keys.sorted().nearest(mappedNs).first - - // Find nearest NS - val nsContents = ALL_TAGS[key] - - key to nsContents!!.nearest(it.tag?.rawTextOnly() ?: "").first - } - else -> error("Unknown type!") - }.let { p -> - Triple(p.first, p.second, it.excluded) + if (sortFilter != null) { + sortFilter.state?.let { state -> + if (query.isNotBlank()) { + throw IllegalArgumentException("Cannot use sorting while text/tag search is active!") } + + isSortFilter = true + base = "/browse/title/${SortFilter.SORT_OPTIONS[state.index].first}/${if (state.ascending) "ASC" else "DESC"}" } - - "/result" - } else { - "/search" } - } - base += "/$page" + if (base == null) { + base = if (modeFilter != null && modeFilter.state == 1) { + tagQuery = searchEngine.parseQuery(query, false).map { + when (it) { + is Text -> { + var minDist = Int.MAX_VALUE.toDouble() + // ns, value + var minContent: Pair = "" to "" + for (ns in ALL_TAGS) { + val (v, d) = ns.value.nearest(it.rawTextOnly(), minDist) + if (d < minDist) { + minDist = d + minContent = ns.key to v + } + } + minContent + } + is Namespace -> { + // Map ns aliases + val mappedNs = NS_MAPPINGS[it.namespace] ?: it.namespace - if (isSortFilter) { - parseListing(client.newCall(GET(baseUrl + base, headers)) - .asObservableSuccess() - .toSingle() - .await(Schedulers.io())) - } else { - val body = if (tagQuery != null) { - FormBody.Builder() + var key = mappedNs + if (!ALL_TAGS.containsKey(key)) key = ALL_TAGS.keys.sorted().nearest(mappedNs).first + + // Find nearest NS + val nsContents = ALL_TAGS[key] + + key to nsContents!!.nearest(it.tag?.rawTextOnly() ?: "").first + } + else -> error("Unknown type!") + }.let { p -> + Triple(p.first, p.second, it.excluded) + } + } + + "/result" + } else { + "/search" + } + } + + base += "/$page" + + if (isSortFilter) { + parseListing( + client.newCall(GET(baseUrl + base, headers)) + .asObservableSuccess() + .toSingle() + .await(Schedulers.io()) + ) + } else { + val body = if (tagQuery != null) { + FormBody.Builder() .add("type", "advance") .apply { tagQuery.forEach { add(it.first + "_" + it.second, if (it.third) "n" else "y") } } - } else { - FormBody.Builder() + } else { + FormBody.Builder() .add("type", "search") .add("needle", query) - } - val processRequest = POST( + } + val processRequest = POST( "$baseUrl/content/process.php", headers, body = body.build() - ) - val processResponse = nonRedirectingClientWithoutCookies.newCall(processRequest) + ) + val processResponse = nonRedirectingClientWithoutCookies.newCall(processRequest) .asObservable() .toSingle() .await(Schedulers.io()) - if (!processResponse.isRedirect) - throw IllegalStateException("Unexpected process response code!") + if (!processResponse.isRedirect) { + throw IllegalStateException("Unexpected process response code!") + } - val sessId = processResponse.headers("Set-Cookie").find { - it.startsWith("PHPSESSID") - } ?: throw IllegalStateException("Missing server session cookie!") + val sessId = processResponse.headers("Set-Cookie").find { + it.startsWith("PHPSESSID") + } ?: throw IllegalStateException("Missing server session cookie!") - val response = clientWithoutCookies.newCall(GET(baseUrl + base, - headersBuilder() + val response = clientWithoutCookies.newCall( + GET( + baseUrl + base, + headersBuilder() .set("Cookie", BASE_COOKIES + " " + sessId.substringBefore(';')) - .build())) + .build() + ) + ) .asObservableSuccess() .toSingle() .await(Schedulers.io()) - val doc = response.asJsoup() - val manga = doc.select(".browseDescription").map { - SManga.create().apply { - val first = it.child(0) - url = first.attr("href") - title = first.attr("title").substringAfter('\'').removeSuffix("'").replace('_', ' ') - thumbnail_url = HBrowseSearchMetadata.guessThumbnailUrl(url.substring(1)) + val doc = response.asJsoup() + val manga = doc.select(".browseDescription").map { + SManga.create().apply { + val first = it.child(0) + url = first.attr("href") + title = first.attr("title").substringAfter('\'').removeSuffix("'").replace('_', ' ') + thumbnail_url = HBrowseSearchMetadata.guessThumbnailUrl(url.substring(1)) + } } - } - val hasNextPage = doc.selectFirst("#main > p > a[title~=jump]:nth-last-child(1)") != null - MangasPage( + val hasNextPage = doc.selectFirst("#main > p > a[title~=jump]:nth-last-child(1)") != null + MangasPage( manga, hasNextPage - ) - } - }.asSingle(GlobalScope.coroutineContext)).toObservable() + ) + } + }.asSingle(GlobalScope.coroutineContext) + ).toObservable() } // Collection must be sorted and cannot be sorted @@ -320,9 +329,9 @@ class HBrowse : HttpSource(), LewdSource, UrlIm else -> { v.getElementsByTag("a").forEach { tags += RaisedTag( - lowercaseNs, - it.text(), - HBrowseSearchMetadata.TAG_TYPE_DEFAULT + lowercaseNs, + it.text(), + HBrowseSearchMetadata.TAG_TYPE_DEFAULT ) } } @@ -339,10 +348,10 @@ class HBrowse : HttpSource(), LewdSource, UrlIm */ override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) + } } /** @@ -380,16 +389,16 @@ class HBrowse : HttpSource(), LewdSource, UrlIm val scripts = doc.getElementsByTag("script").map { it.data() } for (script in scripts) { val totalPages = TOTAL_PAGES_REGEX.find(script)?.groupValues?.getOrNull(1)?.toIntOrNull() - ?: continue + ?: continue val pageList = PAGE_LIST_REGEX.find(script)?.groupValues?.getOrNull(1) ?: continue return JsonParser.parseString(pageList).array.take(totalPages).map { it.string }.mapIndexed { index, pageName -> Page( - index, - pageName, - "$baseUrl/${basePath.joinToString("/")}/$pageName" + index, + pageName, + "$baseUrl/${basePath.joinToString("/")}/$pageName" ) } } @@ -397,47 +406,54 @@ class HBrowse : HttpSource(), LewdSource, UrlIm return emptyList() } - class HelpFilter : Filter.HelpDialog("Usage instructions", markdown = """ - ### Modes - There are three available filter modes: - - Text search - - Tag search - - Sort mode - - You can only use a single mode at a time. Switch between the text and tag search modes using the dropdown menu. Switch to sorting mode by selecting a sorting option. - - ### Text search - Search for galleries by title, artist or origin. - - ### Tag search - Search for galleries by tag (e.g. search for a specific genre, type, setting, etc). Uses nhentai/e-hentai syntax. Refer to the "Search" section on [this page](https://nhentai.net/info/) for more information. - - ### Sort mode - 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 - """.trimIndent() + "\n$TAGS_AS_MARKDOWN") + class HelpFilter : Filter.HelpDialog( + "Usage instructions", + markdown = + """ + ### Modes + There are three available filter modes: + - Text search + - Tag search + - Sort mode + + You can only use a single mode at a time. Switch between the text and tag search modes using the dropdown menu. Switch to sorting mode by selecting a sorting option. + + ### Text search + Search for galleries by title, artist or origin. + + ### Tag search + Search for galleries by tag (e.g. search for a specific genre, type, setting, etc). Uses nhentai/e-hentai syntax. Refer to the "Search" section on [this page](https://nhentai.net/info/) for more information. + + ### Sort mode + 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 + """.trimIndent() + "\n$TAGS_AS_MARKDOWN" + ) - class ModeFilter : Filter.Select("Mode", arrayOf( + class ModeFilter : Filter.Select( + "Mode", + arrayOf( "Text search", "Tag search" - )) + ) + ) class SortFilter : Filter.Sort("Sort", SORT_OPTIONS.map { it.second }.toTypedArray()) { companion object { // internal to display val SORT_OPTIONS = listOf( - "length" to "Length", - "date" to "Date added", - "rank" to "Rank" + "length" to "Length", + "date" to "Date added", + "rank" to "Rank" ) } } override fun getFilterList() = FilterList( - HelpFilter(), - ModeFilter(), - SortFilter() + HelpFilter(), + ModeFilter(), + SortFilter() ) /** @@ -450,8 +466,8 @@ class HBrowse : HttpSource(), LewdSource, UrlIm } override val matchingHosts = listOf( - "www.hbrowse.com", - "hbrowse.com" + "www.hbrowse.com", + "hbrowse.com" ) override fun mapUrlToMangaUrl(uri: Uri): String? { @@ -465,495 +481,495 @@ class HBrowse : HttpSource(), LewdSource, UrlIm private const val BASE_COOKIES = "thumbnails=1;" private val NS_MAPPINGS = mapOf( - "set" to "setting", - "loc" to "setting", - "location" to "setting", - "fet" to "fetish", - "relation" to "relationship", - "male" to "malebody", - "female" to "femalebody", - "pos" to "position" + "set" to "setting", + "loc" to "setting", + "location" to "setting", + "fet" to "fetish", + "relation" to "relationship", + "male" to "malebody", + "female" to "femalebody", + "pos" to "position" ) private val ALL_TAGS = mapOf( - "genre" to listOf( - "action", - "adventure", - "anime", - "bizarre", - "comedy", - "drama", - "fantasy", - "gore", - "historic", - "horror", - "medieval", - "modern", - "myth", - "psychological", - "romance", - "school_life", - "scifi", - "supernatural", - "video_game", - "visual_novel" - ), - "type" to listOf( - "anthology", - "bestiality", - "dandere", - "deredere", - "deviant", - "fully_colored", - "furry", - "futanari", - "gender_bender", - "guro", - "harem", - "incest", - "kuudere", - "lolicon", - "long_story", - "netorare", - "non-con", - "partly_colored", - "reverse_harem", - "ryona", - "short_story", - "shotacon", - "transgender", - "tsundere", - "uncensored", - "vanilla", - "yandere", - "yaoi", - "yuri" - ), - "setting" to listOf( - "amusement_park", - "attic", - "automobile", - "balcony", - "basement", - "bath", - "beach", - "bedroom", - "cabin", - "castle", - "cave", - "church", - "classroom", - "deck", - "dining_room", - "doctors", - "dojo", - "doorway", - "dream", - "dressing_room", - "dungeon", - "elevator", - "festival", - "gym", - "haunted_building", - "hospital", - "hotel", - "hot_springs", - "kitchen", - "laboratory", - "library", - "living_room", - "locker_room", - "mansion", - "office", - "other", - "outdoor", - "outer_space", - "park", - "pool", - "prison", - "public", - "restaurant", - "restroom", - "roof", - "sauna", - "school", - "school_nurses_office", - "shower", - "shrine", - "storage_room", - "store", - "street", - "teachers_lounge", - "theater", - "tight_space", - "toilet", - "train", - "transit", - "virtual_reality", - "warehouse", - "wilderness" - ), - "fetish" to listOf( - "androphobia", - "apron", - "assertive_girl", - "bikini", - "bloomers", - "breast_expansion", - "business_suit", - "chastity_device", - "chinese_dress", - "christmas", - "collar", - "corset", - "cosplay_(female)", - "cosplay_(male)", - "crossdressing_(female)", - "crossdressing_(male)", - "eye_patch", - "food", - "giantess", - "glasses", - "gothic_lolita", - "gyaru", - "gynophobia", - "high_heels", - "hot_pants", - "impregnation", - "kemonomimi", - "kimono", - "knee_high_socks", - "lab_coat", - "latex", - "leotard", - "lingerie", - "maid_outfit", - "mother_and_daughter", - "none", - "nonhuman_girl", - "olfactophilia", - "pregnant", - "rich_girl", - "school_swimsuit", - "shy_girl", - "sisters", - "sleeping_girl", - "sporty", - "stockings", - "strapon", - "student_uniform", - "swimsuit", - "tanned", - "tattoo", - "time_stop", - "twins_(coed)", - "twins_(female)", - "twins_(male)", - "uniform", - "wedding_dress" - ), - "role" to listOf( - "alien", - "android", - "angel", - "athlete", - "bride", - "bunnygirl", - "cheerleader", - "delinquent", - "demon", - "doctor", - "dominatrix", - "escort", - "foreigner", - "ghost", - "housewife", - "idol", - "magical_girl", - "maid", - "mamono", - "massagist", - "miko", - "mythical_being", - "neet", - "nekomimi", - "newlywed", - "ninja", - "normal", - "nun", - "nurse", - "office_lady", - "other", - "police", - "priest", - "princess", - "queen", - "school_nurse", - "scientist", - "sorcerer", - "student", - "succubus", - "teacher", - "tomboy", - "tutor", - "waitress", - "warrior", - "witch" - ), - "relationship" to listOf( - "acquaintance", - "anothers_daughter", - "anothers_girlfriend", - "anothers_mother", - "anothers_sister", - "anothers_wife", - "aunt", - "babysitter", - "childhood_friend", - "classmate", - "cousin", - "customer", - "daughter", - "daughter-in-law", - "employee", - "employer", - "enemy", - "fiance", - "friend", - "friends_daughter", - "friends_girlfriend", - "friends_mother", - "friends_sister", - "friends_wife", - "girlfriend", - "landlord", - "manager", - "master", - "mother", - "mother-in-law", - "neighbor", - "niece", - "none", - "older_sister", - "patient", - "pet", - "physician", - "relative", - "relatives_friend", - "relatives_girlfriend", - "relatives_wife", - "servant", - "server", - "sister-in-law", - "slave", - "stepdaughter", - "stepmother", - "stepsister", - "stranger", - "student", - "teacher", - "tutee", - "tutor", - "twin", - "underclassman", - "upperclassman", - "wife", - "workmate", - "younger_sister" - ), - "maleBody" to listOf( - "adult", - "animal", - "animal_ears", - "bald", - "beard", - "dark_skin", - "elderly", - "exaggerated_penis", - "fat", - "furry", - "goatee", - "hairy", - "half_animal", - "horns", - "large_penis", - "long_hair", - "middle_age", - "monster", - "muscular", - "mustache", - "none", - "short", - "short_hair", - "skinny", - "small_penis", - "tail", - "tall", - "tanned", - "tan_line", - "teenager", - "wings", - "young" - ), - "femaleBody" to listOf( - "adult", - "animal_ears", - "bald", - "big_butt", - "chubby", - "dark_skin", - "elderly", - "elf_ears", - "exaggerated_breasts", - "fat", - "furry", - "hairy", - "hair_bun", - "half_animal", - "halo", - "hime_cut", - "horns", - "large_breasts", - "long_hair", - "middle_age", - "monster_girl", - "muscular", - "none", - "pigtails", - "ponytail", - "short", - "short_hair", - "skinny", - "small_breasts", - "tail", - "tall", - "tanned", - "tan_line", - "teenager", - "twintails", - "wings", - "young" - ), - "grouping" to listOf( - "foursome_(1_female)", - "foursome_(1_male)", - "foursome_(mixed)", - "foursome_(only_female)", - "one_on_one", - "one_on_one_(2_females)", - "one_on_one_(2_males)", - "orgy_(1_female)", - "orgy_(1_male)", - "orgy_(mainly_female)", - "orgy_(mainly_male)", - "orgy_(mixed)", - "orgy_(only_female)", - "orgy_(only_male)", - "solo_(female)", - "solo_(male)", - "threesome_(1_female)", - "threesome_(1_male)", - "threesome_(only_female)", - "threesome_(only_male)" - ), - "scene" to listOf( - "adultery", - "ahegao", - "anal_(female)", - "anal_(male)", - "aphrodisiac", - "armpit_sex", - "asphyxiation", - "blackmail", - "blowjob", - "bondage", - "breast_feeding", - "breast_sucking", - "bukkake", - "cheating_(female)", - "cheating_(male)", - "chikan", - "clothed_sex", - "consensual", - "cunnilingus", - "defloration", - "discipline", - "dominance", - "double_penetration", - "drunk", - "enema", - "exhibitionism", - "facesitting", - "fingering_(female)", - "fingering_(male)", - "fisting", - "footjob", - "grinding", - "groping", - "handjob", - "humiliation", - "hypnosis", - "intercrural", - "interracial_sex", - "interspecies_sex", - "lactation", - "lotion", - "masochism", - "masturbation", - "mind_break", - "nonhuman", - "orgy", - "paizuri", - "phone_sex", - "props", - "rape", - "reverse_rape", - "rimjob", - "sadism", - "scat", - "sex_toys", - "spanking", - "squirt", - "submission", - "sumata", - "swingers", - "tentacles", - "voyeurism", - "watersports", - "x-ray_blowjob", - "x-ray_sex" - ), - "position" to listOf( - "69", - "acrobat", - "arch", - "bodyguard", - "butterfly", - "cowgirl", - "dancer", - "deck_chair", - "deep_stick", - "doggy", - "drill", - "ex_sex", - "jockey", - "lap_dance", - "leg_glider", - "lotus", - "mastery", - "missionary", - "none", - "other", - "pile_driver", - "prison_guard", - "reverse_piggyback", - "rodeo", - "spoons", - "standing", - "teaspoons", - "unusual", - "victory" - ) + "genre" to listOf( + "action", + "adventure", + "anime", + "bizarre", + "comedy", + "drama", + "fantasy", + "gore", + "historic", + "horror", + "medieval", + "modern", + "myth", + "psychological", + "romance", + "school_life", + "scifi", + "supernatural", + "video_game", + "visual_novel" + ), + "type" to listOf( + "anthology", + "bestiality", + "dandere", + "deredere", + "deviant", + "fully_colored", + "furry", + "futanari", + "gender_bender", + "guro", + "harem", + "incest", + "kuudere", + "lolicon", + "long_story", + "netorare", + "non-con", + "partly_colored", + "reverse_harem", + "ryona", + "short_story", + "shotacon", + "transgender", + "tsundere", + "uncensored", + "vanilla", + "yandere", + "yaoi", + "yuri" + ), + "setting" to listOf( + "amusement_park", + "attic", + "automobile", + "balcony", + "basement", + "bath", + "beach", + "bedroom", + "cabin", + "castle", + "cave", + "church", + "classroom", + "deck", + "dining_room", + "doctors", + "dojo", + "doorway", + "dream", + "dressing_room", + "dungeon", + "elevator", + "festival", + "gym", + "haunted_building", + "hospital", + "hotel", + "hot_springs", + "kitchen", + "laboratory", + "library", + "living_room", + "locker_room", + "mansion", + "office", + "other", + "outdoor", + "outer_space", + "park", + "pool", + "prison", + "public", + "restaurant", + "restroom", + "roof", + "sauna", + "school", + "school_nurses_office", + "shower", + "shrine", + "storage_room", + "store", + "street", + "teachers_lounge", + "theater", + "tight_space", + "toilet", + "train", + "transit", + "virtual_reality", + "warehouse", + "wilderness" + ), + "fetish" to listOf( + "androphobia", + "apron", + "assertive_girl", + "bikini", + "bloomers", + "breast_expansion", + "business_suit", + "chastity_device", + "chinese_dress", + "christmas", + "collar", + "corset", + "cosplay_(female)", + "cosplay_(male)", + "crossdressing_(female)", + "crossdressing_(male)", + "eye_patch", + "food", + "giantess", + "glasses", + "gothic_lolita", + "gyaru", + "gynophobia", + "high_heels", + "hot_pants", + "impregnation", + "kemonomimi", + "kimono", + "knee_high_socks", + "lab_coat", + "latex", + "leotard", + "lingerie", + "maid_outfit", + "mother_and_daughter", + "none", + "nonhuman_girl", + "olfactophilia", + "pregnant", + "rich_girl", + "school_swimsuit", + "shy_girl", + "sisters", + "sleeping_girl", + "sporty", + "stockings", + "strapon", + "student_uniform", + "swimsuit", + "tanned", + "tattoo", + "time_stop", + "twins_(coed)", + "twins_(female)", + "twins_(male)", + "uniform", + "wedding_dress" + ), + "role" to listOf( + "alien", + "android", + "angel", + "athlete", + "bride", + "bunnygirl", + "cheerleader", + "delinquent", + "demon", + "doctor", + "dominatrix", + "escort", + "foreigner", + "ghost", + "housewife", + "idol", + "magical_girl", + "maid", + "mamono", + "massagist", + "miko", + "mythical_being", + "neet", + "nekomimi", + "newlywed", + "ninja", + "normal", + "nun", + "nurse", + "office_lady", + "other", + "police", + "priest", + "princess", + "queen", + "school_nurse", + "scientist", + "sorcerer", + "student", + "succubus", + "teacher", + "tomboy", + "tutor", + "waitress", + "warrior", + "witch" + ), + "relationship" to listOf( + "acquaintance", + "anothers_daughter", + "anothers_girlfriend", + "anothers_mother", + "anothers_sister", + "anothers_wife", + "aunt", + "babysitter", + "childhood_friend", + "classmate", + "cousin", + "customer", + "daughter", + "daughter-in-law", + "employee", + "employer", + "enemy", + "fiance", + "friend", + "friends_daughter", + "friends_girlfriend", + "friends_mother", + "friends_sister", + "friends_wife", + "girlfriend", + "landlord", + "manager", + "master", + "mother", + "mother-in-law", + "neighbor", + "niece", + "none", + "older_sister", + "patient", + "pet", + "physician", + "relative", + "relatives_friend", + "relatives_girlfriend", + "relatives_wife", + "servant", + "server", + "sister-in-law", + "slave", + "stepdaughter", + "stepmother", + "stepsister", + "stranger", + "student", + "teacher", + "tutee", + "tutor", + "twin", + "underclassman", + "upperclassman", + "wife", + "workmate", + "younger_sister" + ), + "maleBody" to listOf( + "adult", + "animal", + "animal_ears", + "bald", + "beard", + "dark_skin", + "elderly", + "exaggerated_penis", + "fat", + "furry", + "goatee", + "hairy", + "half_animal", + "horns", + "large_penis", + "long_hair", + "middle_age", + "monster", + "muscular", + "mustache", + "none", + "short", + "short_hair", + "skinny", + "small_penis", + "tail", + "tall", + "tanned", + "tan_line", + "teenager", + "wings", + "young" + ), + "femaleBody" to listOf( + "adult", + "animal_ears", + "bald", + "big_butt", + "chubby", + "dark_skin", + "elderly", + "elf_ears", + "exaggerated_breasts", + "fat", + "furry", + "hairy", + "hair_bun", + "half_animal", + "halo", + "hime_cut", + "horns", + "large_breasts", + "long_hair", + "middle_age", + "monster_girl", + "muscular", + "none", + "pigtails", + "ponytail", + "short", + "short_hair", + "skinny", + "small_breasts", + "tail", + "tall", + "tanned", + "tan_line", + "teenager", + "twintails", + "wings", + "young" + ), + "grouping" to listOf( + "foursome_(1_female)", + "foursome_(1_male)", + "foursome_(mixed)", + "foursome_(only_female)", + "one_on_one", + "one_on_one_(2_females)", + "one_on_one_(2_males)", + "orgy_(1_female)", + "orgy_(1_male)", + "orgy_(mainly_female)", + "orgy_(mainly_male)", + "orgy_(mixed)", + "orgy_(only_female)", + "orgy_(only_male)", + "solo_(female)", + "solo_(male)", + "threesome_(1_female)", + "threesome_(1_male)", + "threesome_(only_female)", + "threesome_(only_male)" + ), + "scene" to listOf( + "adultery", + "ahegao", + "anal_(female)", + "anal_(male)", + "aphrodisiac", + "armpit_sex", + "asphyxiation", + "blackmail", + "blowjob", + "bondage", + "breast_feeding", + "breast_sucking", + "bukkake", + "cheating_(female)", + "cheating_(male)", + "chikan", + "clothed_sex", + "consensual", + "cunnilingus", + "defloration", + "discipline", + "dominance", + "double_penetration", + "drunk", + "enema", + "exhibitionism", + "facesitting", + "fingering_(female)", + "fingering_(male)", + "fisting", + "footjob", + "grinding", + "groping", + "handjob", + "humiliation", + "hypnosis", + "intercrural", + "interracial_sex", + "interspecies_sex", + "lactation", + "lotion", + "masochism", + "masturbation", + "mind_break", + "nonhuman", + "orgy", + "paizuri", + "phone_sex", + "props", + "rape", + "reverse_rape", + "rimjob", + "sadism", + "scat", + "sex_toys", + "spanking", + "squirt", + "submission", + "sumata", + "swingers", + "tentacles", + "voyeurism", + "watersports", + "x-ray_blowjob", + "x-ray_sex" + ), + "position" to listOf( + "69", + "acrobat", + "arch", + "bodyguard", + "butterfly", + "cowgirl", + "dancer", + "deck_chair", + "deep_stick", + "doggy", + "drill", + "ex_sex", + "jockey", + "lap_dance", + "leg_glider", + "lotus", + "mastery", + "missionary", + "none", + "other", + "pile_driver", + "prison_guard", + "reverse_piggyback", + "rodeo", + "spoons", + "standing", + "teaspoons", + "unusual", + "victory" + ) ).mapValues { it.value.sorted() } private val TAGS_AS_MARKDOWN = ALL_TAGS.map { (ns, values) -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HentaiCafe.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HentaiCafe.kt index 61efde8da..c624fc6e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HentaiCafe.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HentaiCafe.kt @@ -19,8 +19,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.jsoup.nodes.Document import rx.Observable -class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate), - LewdSource, UrlImportableSource { +class HentaiCafe(delegate: HttpSource) : + DelegatedHttpSource(delegate), + LewdSource, + UrlImportableSource { /** * An ISO 639-1 compliant language code (two letters in lower case). */ @@ -32,18 +34,22 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate), // Support direct URL importing override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - urlImportFetchSearchManga(query) { - super.fetchSearchManga(page, query, filters) - } + urlImportFetchSearchManga(query) { + super.fetchSearchManga(page, query, filters) + } override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply { - initialized = true - })) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()).andThen( + Observable.just( + manga.apply { + initialized = true + } + ) + ) + } } /** @@ -57,8 +63,8 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate), thumbnailUrl = contentElement.child(0).child(0).attr("src") fun filterableTagsOfType(type: String) = contentElement.select("a") - .filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") } - .map { it.text() } + .filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") } + .map { it.text() } tags.clear() tags += filterableTagsOfType("tag").map { @@ -78,29 +84,30 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate), override fun fetchChapterList(manga: SManga) = getOrLoadMetadata(manga.id) { client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { it.asJsoup() } - .toSingle() + .asObservableSuccess() + .map { it.asJsoup() } + .toSingle() }.map { listOf( - SChapter.create().apply { - setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/") - name = "Chapter" - chapter_number = 0.0f - } + SChapter.create().apply { + setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/") + name = "Chapter" + chapter_number = 0.0f + } ) }.toObservable() override val matchingHosts = listOf( - "hentai.cafe" + "hentai.cafe" ) override fun mapUrlToMangaUrl(uri: Uri): String? { val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null - return if (lcFirstPathSegment == "manga") + return if (lcFirstPathSegment == "manga") { "https://hentai.cafe/${uri.pathSegments[2]}" - else + } else { "https://hentai.cafe/$lcFirstPathSegment" + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt index f8d44474a..b6a1cc5d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt @@ -18,8 +18,10 @@ import exh.util.urlImportFetchSearchManga import org.jsoup.nodes.Document import rx.Observable -class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate), - LewdSource, UrlImportableSource { +class Pururin(delegate: HttpSource) : + DelegatedHttpSource(delegate), + LewdSource, + UrlImportableSource { /** * An ISO 639-1 compliant language code (two letters in lower case). */ @@ -43,11 +45,11 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate), override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()) - .andThen(Observable.just(manga)) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()) + .andThen(Observable.just(manga)) + } } override fun parseIntoMetadata(metadata: PururinSearchMetadata, input: Document) { @@ -87,9 +89,9 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate), value.select("a").forEach { link -> val searchUrl = Uri.parse(link.attr("href")) tags += RaisedTag( - searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2], - searchUrl.lastPathSegment!!.substringBefore("."), - PururinSearchMetadata.TAG_TYPE_DEFAULT + searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2], + searchUrl.lastPathSegment!!.substringBefore("."), + PururinSearchMetadata.TAG_TYPE_DEFAULT ) } } @@ -99,8 +101,8 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate), } override val matchingHosts = listOf( - "pururin.io", - "www.pururin.io" + "pururin.io", + "www.pururin.io" ) override fun mapUrlToMangaUrl(uri: Uri): String? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt index 6c9d30fe9..b61a0fe2b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt @@ -19,30 +19,33 @@ import java.util.Locale import org.jsoup.nodes.Document import rx.Observable -class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate), - LewdSource, UrlImportableSource { +class Tsumino(delegate: HttpSource) : + DelegatedHttpSource(delegate), + LewdSource, + UrlImportableSource { override val metaClass = TsuminoSearchMetadata::class override val lang = "en" // Support direct URL importing override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - urlImportFetchSearchManga(query) { - super.fetchSearchManga(page, query, filters) - } + urlImportFetchSearchManga(query) { + super.fetchSearchManga(page, query, filters) + } override fun mapUrlToMangaUrl(uri: Uri): String? { val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null - if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry") + if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry") { return null + } return "https://tsumino.com/Book/Info/${uri.lastPathSegment}" } override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .flatMap { - parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) - } + .asObservableSuccess() + .flatMap { + parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) + } } override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) { @@ -106,16 +109,18 @@ class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate), character = newCharacter input.getElementById("Tag")?.children()?.let { - tags.addAll(it.map { - RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT) - }) + tags.addAll( + it.map { + RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT) + } + ) } } } override val matchingHosts = listOf( - "www.tsumino.com", - "tsumino.com" + "www.tsumino.com", + "tsumino.com" ) companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt index 31f94abd0..4b448377b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt @@ -131,10 +131,14 @@ class SourceController : // Open the catalogue view. openCatalogue(source, BrowseSourceController(source)) } - Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply { - putLong(SmartSearchController.ARG_SOURCE_ID, source.id) - putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig) - }).withFadeTransaction()) + Mode.SMART_SEARCH -> router.pushController( + SmartSearchController( + Bundle().apply { + putLong(SmartSearchController.ARG_SOURCE_ID, source.id) + putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig) + } + ).withFadeTransaction() + ) } return false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index 32c0cf400..2432aa3f6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -413,9 +413,9 @@ open class BrowseSourcePresenter( } val newSerialized = searches.map { "${source.id}:" + jsonObject( - "name" to it.name, - "query" to it.query, - "filters" to filterSerializer.serialize(it.filterList) + "name" to it.name, + "query" to it.query, + "filters" to filterSerializer.serialize(it.filterList) ).toString() } prefs.eh_savedSearches().set((otherSerialized + newSerialized).toSet()) @@ -430,9 +430,11 @@ open class BrowseSourcePresenter( val content = JsonParser.parseString(it.substringAfter(':')).obj val originalFilters = source.getFilterList() filterSerializer.deserialize(originalFilters, content["filters"].array) - EXHSavedSearch(content["name"].string, - content["query"].string, - originalFilters) + EXHSavedSearch( + content["name"].string, + content["query"].string, + originalFilters + ) } catch (t: RuntimeException) { // Load failed Timber.e(t, "Failed to load saved search!") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 8446c4ae5..35bb74b76 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -393,7 +393,6 @@ class LibraryPresenter( manga: Manga, replace: Boolean ) { - val flags = preferences.migrateFlags().get() val migrateChapters = MigrationFlags.hasChapters(flags) val migrateCategories = MigrationFlags.hasCategories(flags) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index c25eea983..483cbde53 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -181,29 +181,34 @@ class MangaInfoPresenter( suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga { val originalManga = db.getManga(originalMangaId).await() - ?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId") + ?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId") val toInsert = if (originalManga.source == MERGED_SOURCE_ID) { originalManga.apply { val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children - if (originalChildren.any { it.source == manga.source && it.url == manga.url }) + if (originalChildren.any { it.source == manga.source && it.url == manga.url }) { throw IllegalArgumentException("This manga is already merged with the current manga!") + } - url = MergedSource.MangaConfig(originalChildren + MergedSource.MangaSource( + url = MergedSource.MangaConfig( + originalChildren + MergedSource.MangaSource( manga.source, manga.url - )).writeAsUrl(gson) + ) + ).writeAsUrl(gson) } } else { - val newMangaConfig = MergedSource.MangaConfig(listOf( + val newMangaConfig = MergedSource.MangaConfig( + listOf( MergedSource.MangaSource( - originalManga.source, - originalManga.url + originalManga.source, + originalManga.url ), MergedSource.MangaSource( - manga.source, - manga.url + manga.source, + manga.url ) - )) + ) + ) Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply { copyFrom(originalManga) favorite = true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt index 0dc900673..f12724d79 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt @@ -23,17 +23,22 @@ class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) override fun onCreateDialog(savedViewState: Bundle?): Dialog { val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga - val confirmString = applicationContext?.resources?.getQuantityString(confirmRes, mangaSet, - mangaSet, ( + val confirmString = applicationContext?.resources?.getQuantityString( + confirmRes, mangaSet, + mangaSet, + ( if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped) - else "")) ?: "" + else "" + ) + ) ?: "" return MaterialDialog(activity!!) .message(text = confirmString) .positiveButton(if (copy) R.string.copy else R.string.migrate) { - if (copy) + if (copy) { (targetController as? MigrationListController)?.copyMangas() - else + } else { (targetController as? MigrationListController)?.migrateMangas() + } } .negativeButton(android.R.string.no) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt index 39097017a..fd230d27f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt @@ -31,10 +31,12 @@ class MigrationBottomSheetDialog( activity: Activity, theme: Int, private val listener: - StartMigrationListener + StartMigrationListener ) : - BottomSheetDialog(activity, - theme) { + BottomSheetDialog( + activity, + theme + ) { /** * Preferences helper. */ @@ -47,8 +49,9 @@ class MigrationBottomSheetDialog( // scroll.addView(view) setContentView(view) - if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) + if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) { sourceGroup.orientation = LinearLayout.HORIZONTAL + } window?.setBackgroundDrawable(null) } @@ -63,8 +66,10 @@ class MigrationBottomSheetDialog( fab.setOnClickListener { preferences.skipPreMigration().set(skip_step.isChecked) listener.startMigration( - if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank()) - extra_search_param_text.text.toString() else null) + if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank()) { + extra_search_param_text.text.toString() + } else null + ) dismiss() } } @@ -96,9 +101,12 @@ class MigrationBottomSheetDialog( skip_step.isChecked = preferences.skipPreMigration().get() skip_step.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) - (listener as? Controller)?.activity?.toast(R.string.pre_migration_skip_toast, - Toast.LENGTH_LONG) + if (isChecked) { + (listener as? Controller)?.activity?.toast( + R.string.pre_migration_skip_toast, + Toast.LENGTH_LONG + ) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceAdapter.kt index 7056d18da..39821522f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceAdapter.kt @@ -9,16 +9,21 @@ class MigrationSourceAdapter( var items: List, val controllerPre: PreMigrationController ) : FlexibleAdapter( - items, - controllerPre, - true + items, + controllerPre, + true ) { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putParcelableArrayList(SELECTED_SOURCES_KEY, ArrayList(currentItems.map { - it.asParcelable() - })) + outState.putParcelableArrayList( + SELECTED_SOURCES_KEY, + ArrayList( + currentItems.map { + it.asParcelable() + } + ) + ) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt index de66151e4..a9c95ac55 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt @@ -66,8 +66,8 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : val source = sourceManager.get(si.sourceId) as? HttpSource ?: return null return MigrationSourceItem( - source, - si.sourceEnabled + source, + si.sourceEnabled ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt index a0acdea2b..9ca83e9ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt @@ -27,8 +27,11 @@ import exh.util.updateLayoutParams import exh.util.updatePaddingRelative import uy.kohesive.injekt.injectLazy -class PreMigrationController(bundle: Bundle? = null) : BaseController(bundle), FlexibleAdapter -.OnItemClickListener, StartMigrationListener { +class PreMigrationController(bundle: Bundle? = null) : + BaseController(bundle), + FlexibleAdapter + .OnItemClickListener, + StartMigrationListener { private val sourceManager: SourceManager by injectLazy() private val prefs: PreferencesHelper by injectLazy() @@ -69,8 +72,10 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController): PreMigrationController { - return PreMigrationController(Bundle().apply { - putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray()) - }) + return PreMigrationController( + Bundle().apply { + putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray()) + } + ) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt index 1f8ee6d57..c3a06ed76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt @@ -56,8 +56,10 @@ import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.injectLazy -class MigrationListController(bundle: Bundle? = null) : BaseController(bundle), - MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope { +class MigrationListController(bundle: Bundle? = null) : + BaseController(bundle), + MigrationProcessAdapter.MigrationProcessInterface, + CoroutineScope { init { setHasOptionsMenu(true) @@ -93,7 +95,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController() } ?: emptyList() @@ -313,7 +315,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController { launchUI { @@ -488,9 +489,11 @@ class MigrationListController(bundle: Bundle? = null) : BaseController 0f) { - manga_last_chapter_label.text = context.getString(R.string.latest_, - DecimalFormat("#.#").format(latestChapter)) + manga_last_chapter_label.text = context.getString( + R.string.latest_, + DecimalFormat("#.#").format(latestChapter) + ) } else { - manga_last_chapter_label.text = context.getString(R.string.latest_, - context.getString(R.string.unknown)) + manga_last_chapter_label.text = context.getString( + R.string.latest_, + context.getString(R.string.unknown) + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt index ead03de67..8364e9987 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt @@ -63,13 +63,13 @@ class SettingsEhController : SettingsController() { private fun Preference<*>.reconfigure(): Boolean { // Listen for change commit asObservable() - .skip(1) // Skip first as it is emitted immediately - .take(1) // Only listen for first commit - .observeOn(AndroidSchedulers.mainThread()) - .subscribeUntilDestroy { - // Only listen for first change commit - WarnConfigureDialogController.uploadSettings(router) - } + .skip(1) // Skip first as it is emitted immediately + .take(1) // Only listen for first commit + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + // Only listen for first change commit + WarnConfigureDialogController.uploadSettings(router) + } // Always return true to save changes return true @@ -85,12 +85,12 @@ class SettingsEhController : SettingsController() { isPersistent = false defaultValue = false preferences.enableExhentai() - .asObservable() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeUntilDestroy { - isChecked = it - } + .asObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + isChecked = it + } onChange { newVal -> newVal as Boolean @@ -98,9 +98,11 @@ class SettingsEhController : SettingsController() { preferences.enableExhentai().set(false) true } else { - router.pushController(RouterTransaction.with(LoginController()) + router.pushController( + RouterTransaction.with(LoginController()) .pushChangeHandler(FadeChangeHandler()) - .popChangeHandler(FadeChangeHandler())) + .popChangeHandler(FadeChangeHandler()) + ) false } } @@ -148,20 +150,20 @@ class SettingsEhController : SettingsController() { summary = "The quality of the downloaded images" title = "Image quality" entries = arrayOf( - "Auto", - "2400x", - "1600x", - "1280x", - "980x", - "780x" + "Auto", + "2400x", + "1600x", + "1280x", + "980x", + "780x" ) entryValues = arrayOf( - "auto", - "ovrs_2400", - "ovrs_1600", - "high", - "med", - "low" + "auto", + "ovrs_2400", + "ovrs_1600", + "high", + "med", + "low" ) onChange { preferences.imageQuality().reconfigure() } @@ -202,21 +204,21 @@ class SettingsEhController : SettingsController() { onClick { activity?.let { activity -> MaterialDialog(activity) - .title(R.string.eh_force_sync_reset_title) - .message(R.string.eh_force_sync_reset_message) - .positiveButton(android.R.string.yes) { - LocalFavoritesStorage().apply { - getRealm().use { - it.trans { - clearSnapshots(it) - } + .title(R.string.eh_force_sync_reset_title) + .message(R.string.eh_force_sync_reset_message) + .positiveButton(android.R.string.yes) { + LocalFavoritesStorage().apply { + getRealm().use { + it.trans { + clearSnapshots(it) } } - activity.toast("Sync state reset", Toast.LENGTH_LONG) } - .negativeButton(android.R.string.no) - .cancelable(false) - .show() + activity.toast("Sync state reset", Toast.LENGTH_LONG) + } + .negativeButton(android.R.string.no) + .cancelable(false) + .show() } } } @@ -311,18 +313,18 @@ class SettingsEhController : SettingsController() { } """ - $statsText + $statsText - Galleries that were checked in the last: - - hour: ${metaInRelativeDuration(1.hours)} - - 6 hours: ${metaInRelativeDuration(6.hours)} - - 12 hours: ${metaInRelativeDuration(12.hours)} - - day: ${metaInRelativeDuration(1.days)} - - 2 days: ${metaInRelativeDuration(2.days)} - - week: ${metaInRelativeDuration(7.days)} - - month: ${metaInRelativeDuration(30.days)} - - year: ${metaInRelativeDuration(365.days)} - """.trimIndent() + Galleries that were checked in the last: + - hour: ${metaInRelativeDuration(1.hours)} + - 6 hours: ${metaInRelativeDuration(6.hours)} + - 12 hours: ${metaInRelativeDuration(12.hours)} + - day: ${metaInRelativeDuration(1.days)} + - 2 days: ${metaInRelativeDuration(2.days)} + - week: ${metaInRelativeDuration(7.days)} + - month: ${metaInRelativeDuration(30.days)} + - year: ${metaInRelativeDuration(365.days)} + """.trimIndent() } finally { progress.dismiss() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index ba08bace3..5eecaa14e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -75,10 +75,12 @@ class SettingsLibraryController : SettingsController() { intListPreference { key = Keys.eh_library_rounded_corners title = "Rounded Corner Radius" - entriesRes = arrayOf(R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1, + entriesRes = arrayOf( + R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1, R.string.eh_rounded_corner_2, R.string.eh_rounded_corner_3, R.string.eh_rounded_corner_4, R.string.eh_rounded_corner_5, R.string.eh_rounded_corner_6, R.string.eh_rounded_corner_7, - R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10) + R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10 + ) entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10") defaultValue = "4" summaryRes = R.string.eh_rounded_corners_desc @@ -211,7 +213,8 @@ class SettingsLibraryController : SettingsController() { } } if (preferences.skipPreMigration().get() || preferences.migrationSources() - .getOrDefault().isNotEmpty()) { + .getOrDefault().isNotEmpty() + ) { switchPreference { key = Keys.skipPreMigration titleRes = R.string.pref_skip_pre_migration diff --git a/app/src/main/java/exh/EHSourceHelpers.kt b/app/src/main/java/exh/EHSourceHelpers.kt index 430e72954..d38d5ce23 100755 --- a/app/src/main/java/exh/EHSourceHelpers.kt +++ b/app/src/main/java/exh/EHSourceHelpers.kt @@ -26,19 +26,19 @@ const val HBROWSE_SOURCE_ID = LEWD_SOURCE_SERIES + 12 const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69 private val DELEGATED_LEWD_SOURCES = listOf( - HentaiCafe::class, - Pururin::class, - Tsumino::class + HentaiCafe::class, + Pururin::class, + Tsumino::class ) val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf( - EH_SOURCE_ID, - EXH_SOURCE_ID, - NHENTAI_SOURCE_ID, - HENTAI_CAFE_SOURCE_ID, - TSUMINO_SOURCE_ID, - HITOMI_SOURCE_ID, - PURURIN_SOURCE_ID + EH_SOURCE_ID, + EXH_SOURCE_ID, + NHENTAI_SOURCE_ID, + HENTAI_CAFE_SOURCE_ID, + TSUMINO_SOURCE_ID, + HITOMI_SOURCE_ID, + PURURIN_SOURCE_ID ) private inline fun delegatedSourceId(): Long { @@ -54,6 +54,6 @@ private val lewdDelegatedSourceIds = SourceManager.DELEGATED_SOURCES.filter { // This method MUST be fast! fun isLewdSource(source: Long) = source in 6900..6999 || - lewdDelegatedSourceIds.binarySearch(source) >= 0 + lewdDelegatedSourceIds.binarySearch(source) >= 0 fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index f3aa42d2d..04aab3718 100644 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -45,35 +45,41 @@ object EXHMigrations { if (oldVersion < 1) { db.inTransaction { // Migrate HentaiCafe source IDs - db.lowLevel().executeSQL(RawQuery.builder() - .query(""" - UPDATE ${MangaTable.TABLE} - SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID - WHERE ${MangaTable.COL_SOURCE} = 6908 - """.trimIndent()) + db.lowLevel().executeSQL( + RawQuery.builder() + .query( + """ + UPDATE ${MangaTable.TABLE} + SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID + WHERE ${MangaTable.COL_SOURCE} = 6908 + """.trimIndent() + ) .affectsTables(MangaTable.TABLE) - .build()) + .build() + ) // Migrate nhentai URLs val nhentaiManga = db.db.get() - .listOfObjects(Manga::class.java) - .withQuery(Query.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID") - .build()) - .prepare() - .executeAsBlocking() + .listOfObjects(Manga::class.java) + .withQuery( + Query.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID") + .build() + ) + .prepare() + .executeAsBlocking() nhentaiManga.forEach { it.url = getUrlWithoutDomain(it.url) } db.db.put() - .objects(nhentaiManga) - // Extremely slow without the resolver :/ - .withPutResolver(MangaUrlPutResolver()) - .prepare() - .executeAsBlocking() + .objects(nhentaiManga) + // Extremely slow without the resolver :/ + .withPutResolver(MangaUrlPutResolver()) + .prepare() + .executeAsBlocking() } } @@ -85,14 +91,18 @@ object EXHMigrations { if (oldVersion < 8405) { db.inTransaction { // Migrate HBrowse source IDs - db.lowLevel().executeSQL(RawQuery.builder() - .query(""" - UPDATE ${MangaTable.TABLE} - SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID - WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222 - """.trimIndent()) + db.lowLevel().executeSQL( + RawQuery.builder() + .query( + """ + UPDATE ${MangaTable.TABLE} + SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID + WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222 + """.trimIndent() + ) .affectsTables(MangaTable.TABLE) - .build()) + .build() + ) } // Cancel old scheduler jobs with old ids @@ -101,14 +111,18 @@ object EXHMigrations { if (oldVersion < 8408) { db.inTransaction { // Migrate Tsumino source IDs - db.lowLevel().executeSQL(RawQuery.builder() - .query(""" - UPDATE ${MangaTable.TABLE} - SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID - WHERE ${MangaTable.COL_SOURCE} = 6909 - """.trimIndent()) + db.lowLevel().executeSQL( + RawQuery.builder() + .query( + """ + UPDATE ${MangaTable.TABLE} + SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID + WHERE ${MangaTable.COL_SOURCE} = 6909 + """.trimIndent() + ) .affectsTables(MangaTable.TABLE) - .build()) + .build() + ) } } if (oldVersion < 8409) { @@ -214,10 +228,12 @@ object EXHMigrations { return try { val uri = URI(orig) var out = uri.path - if (uri.query != null) + if (uri.query != null) { out += "?" + uri.query - if (uri.fragment != null) + } + if (uri.fragment != null) { out += "#" + uri.fragment + } out } catch (e: URISyntaxException) { orig diff --git a/app/src/main/java/exh/GalleryAdder.kt b/app/src/main/java/exh/GalleryAdder.kt index ba9756115..c5663dcfd 100755 --- a/app/src/main/java/exh/GalleryAdder.kt +++ b/app/src/main/java/exh/GalleryAdder.kt @@ -37,15 +37,15 @@ class GalleryAdder { } } else { sourceManager.getVisibleCatalogueSources() - .filterIsInstance() - .find { - try { - it.matchesUri(uri) - } catch (e: Exception) { - XLog.e("Source URI match check error!", e) - false - } - } ?: return GalleryAddEvent.Fail.UnknownType(url) + .filterIsInstance() + .find { + try { + it.matchesUri(uri) + } catch (e: Exception) { + XLog.e("Source URI match check error!", e) + false + } + } ?: return GalleryAddEvent.Fail.UnknownType(url) } // Map URL to manga URL @@ -66,10 +66,10 @@ class GalleryAdder { // Use manga in DB if possible, otherwise, make a new manga val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking() - ?: Manga.create(source.id).apply { - this.url = cleanedUrl - title = realUrl - } + ?: Manga.create(source.id).apply { + this.url = cleanedUrl + title = realUrl + } // Insert created manga if not in DB before fetching details // This allows us to keep the metadata when fetching details @@ -111,8 +111,10 @@ class GalleryAdder { return GalleryAddEvent.Fail.NotFound(url) } - return GalleryAddEvent.Fail.Error(url, - ((e.message ?: "Unknown error!") + " (Gallery: $url)").trim()) + return GalleryAddEvent.Fail.Error( + url, + ((e.message ?: "Unknown error!") + " (Gallery: $url)").trim() + ) } } } @@ -141,6 +143,6 @@ sealed class GalleryAddEvent { ) : Fail() class NotFound(galleryUrl: String) : - Error(galleryUrl, "Gallery does not exist: $galleryUrl") + Error(galleryUrl, "Gallery does not exist: $galleryUrl") } } diff --git a/app/src/main/java/exh/debug/DebugFunctions.kt b/app/src/main/java/exh/debug/DebugFunctions.kt index 63780ff02..5a063c8c7 100644 --- a/app/src/main/java/exh/debug/DebugFunctions.kt +++ b/app/src/main/java/exh/debug/DebugFunctions.kt @@ -29,7 +29,7 @@ object DebugFunctions { val sourceManager: SourceManager by injectLazy() fun forceUpgradeMigration() { - prefs.eh_lastVersionCode().set(0) + prefs.eh_lastVersionCode().set(0) EXHMigrations.upgrade(prefs) } @@ -38,8 +38,9 @@ object DebugFunctions { val metadataManga = db.getFavoriteMangaWithMetadata().await() 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 + } manga }.toList() @@ -56,13 +57,17 @@ object DebugFunctions { fun addAllMangaInDatabaseToLibrary() { db.inTransaction { - db.lowLevel().executeSQL(RawQuery.builder() - .query(""" - UPDATE ${MangaTable.TABLE} - SET ${MangaTable.COL_FAVORITE} = 1 - """.trimIndent()) + db.lowLevel().executeSQL( + RawQuery.builder() + .query( + """ + UPDATE ${MangaTable.TABLE} + SET ${MangaTable.COL_FAVORITE} = 1 + """.trimIndent() + ) .affectsTables(MangaTable.TABLE) - .build()) + .build() + ) } } @@ -98,25 +103,29 @@ object DebugFunctions { fun listScheduledJobs() = app.jobScheduler.allPendingJobs.map { j -> """ - { - info: ${j.id}, - isPeriod: ${j.isPeriodic}, - isPersisted: ${j.isPersisted}, - intervalMillis: ${j.intervalMillis}, - } + { + info: ${j.id}, + isPeriod: ${j.isPeriodic}, + isPersisted: ${j.isPersisted}, + intervalMillis: ${j.intervalMillis}, + } """.trimIndent() }.joinToString(",\n") fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll() private fun convertSources(from: Long, to: Long) { - db.lowLevel().executeSQL(RawQuery.builder() - .query(""" - UPDATE ${MangaTable.TABLE} - SET ${MangaTable.COL_SOURCE} = $to - WHERE ${MangaTable.COL_SOURCE} = $from - """.trimIndent()) + db.lowLevel().executeSQL( + RawQuery.builder() + .query( + """ + UPDATE ${MangaTable.TABLE} + SET ${MangaTable.COL_SOURCE} = $to + WHERE ${MangaTable.COL_SOURCE} = $from + """.trimIndent() + ) .affectsTables(MangaTable.TABLE) - .build()) + .build() + ) } } diff --git a/app/src/main/java/exh/debug/SettingsDebugController.kt b/app/src/main/java/exh/debug/SettingsDebugController.kt index 1a96399ed..4cdd5eb39 100644 --- a/app/src/main/java/exh/debug/SettingsDebugController.kt +++ b/app/src/main/java/exh/debug/SettingsDebugController.kt @@ -45,11 +45,11 @@ class SettingsDebugController : SettingsController() { val result = it.call(DebugFunctions) view.text = "Function returned result:\n\n$result" MaterialDialog(context) - .customView(view = hView, scrollable = true) + .customView(view = hView, scrollable = true) } catch (t: Throwable) { view.text = "Function threw exception:\n\n${Log.getStackTraceString(t)}" MaterialDialog(context) - .customView(view = hView, scrollable = true) + .customView(view = hView, scrollable = true) }.show() } } diff --git a/app/src/main/java/exh/eh/EHentaiThrottleManager.kt b/app/src/main/java/exh/eh/EHentaiThrottleManager.kt index 9f4a13062..278fa62eb 100644 --- a/app/src/main/java/exh/eh/EHentaiThrottleManager.kt +++ b/app/src/main/java/exh/eh/EHentaiThrottleManager.kt @@ -12,11 +12,13 @@ class EHentaiThrottleManager( // Throttle requests if necessary val now = System.currentTimeMillis() val timeDiff = now - lastThrottleTime - if (timeDiff < throttleTime) + if (timeDiff < throttleTime) { Thread.sleep(throttleTime - timeDiff) + } - if (throttleTime < max) + if (throttleTime < max) { throttleTime += inc + } lastThrottleTime = System.currentTimeMillis() } diff --git a/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt b/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt index 493de299f..9c5e8ed5e 100644 --- a/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt +++ b/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt @@ -17,10 +17,10 @@ data class ChapterChain(val manga: Manga, val chapters: List) class EHentaiUpdateHelper(context: Context) { val parentLookupTable = - MemAutoFlushingLookupTable( - File(context.filesDir, "exh-plt.maftable"), - GalleryEntry.Serializer() - ) + MemAutoFlushingLookupTable( + File(context.filesDir, "exh-plt.maftable"), + GalleryEntry.Serializer() + ) private val db: DatabaseHelper by injectLazy() /** @@ -30,22 +30,24 @@ class EHentaiUpdateHelper(context: Context) { */ fun findAcceptedRootAndDiscardOthers(sourceId: Long, chapters: List): Single, Boolean>> { // Find other chains - val chainsObservable = Observable.merge(chapters.map { chapter -> - db.getChapters(chapter.url).asRxSingle().toObservable() - }).toList().map { allChapters -> + val chainsObservable = Observable.merge( + chapters.map { chapter -> + db.getChapters(chapter.url).asRxSingle().toObservable() + } + ).toList().map { allChapters -> allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct() }.flatMap { mangaIds -> Observable.merge( - mangaIds.map { mangaId -> - Single.zip( - db.getManga(mangaId).asRxSingle(), - db.getChaptersByMangaId(mangaId).asRxSingle() - ) { manga, chapters -> - ChapterChain(manga, chapters) - }.toObservable().filter { - it.manga.source == sourceId - } + mangaIds.map { mangaId -> + Single.zip( + db.getManga(mangaId).asRxSingle(), + db.getChaptersByMangaId(mangaId).asRxSingle() + ) { manga, chapters -> + ChapterChain(manga, chapters) + }.toObservable().filter { + it.manga.source == sourceId } + } ) }.toList() @@ -66,65 +68,66 @@ class EHentaiUpdateHelper(context: Context) { // Copy chain chapters to curChapters val newChapters = toDiscard - .flatMap { chain -> - val meta by lazy { - db.getFlatMetadataForManga(chain.manga.id!!) - .executeAsBlocking() - ?.raise() - } - - chain.chapters.map { chapter -> - // Convert old style chapters to new style chapters if possible - if (chapter.date_upload <= 0 && - meta?.datePosted != null && - meta?.title != null) { - chapter.name = meta!!.title!! - chapter.date_upload = meta!!.datePosted!! - } - chapter - } + .flatMap { chain -> + val meta by lazy { + db.getFlatMetadataForManga(chain.manga.id!!) + .executeAsBlocking() + ?.raise() } - .fold(accepted.chapters) { curChapters, chapter -> - val existing = curChapters.find { it.url == chapter.url } - val newLastPageRead = chainsAsChapters.maxBy { it.last_page_read }?.last_page_read - - if (existing != null) { - existing.read = existing.read || chapter.read - existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read) - if (newLastPageRead != null && existing.last_page_read <= 0) { - existing.last_page_read = newLastPageRead - } - existing.bookmark = existing.bookmark || chapter.bookmark - curChapters - } else if (chapter.date_upload > 0) { // Ignore chapters using the old system - new = true - curChapters + ChapterImpl().apply { - manga_id = accepted.manga.id - url = chapter.url - name = chapter.name - read = chapter.read - bookmark = chapter.bookmark - - last_page_read = chapter.last_page_read - if (newLastPageRead != null && last_page_read <= 0) { - last_page_read = newLastPageRead - } - - date_fetch = chapter.date_fetch - date_upload = chapter.date_upload - } - } else curChapters - } - .filter { it.date_upload > 0 } // Ignore chapters using the old system (filter after to prevent dupes from insert) - .sortedBy { it.date_upload } - .apply { - mapIndexed { index, chapter -> - chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ") - chapter.chapter_number = index + 1f - chapter.source_order = lastIndex - index + chain.chapters.map { chapter -> + // Convert old style chapters to new style chapters if possible + if (chapter.date_upload <= 0 && + meta?.datePosted != null && + meta?.title != null + ) { + chapter.name = meta!!.title!! + chapter.date_upload = meta!!.datePosted!! } + chapter } + } + .fold(accepted.chapters) { curChapters, chapter -> + val existing = curChapters.find { it.url == chapter.url } + + val newLastPageRead = chainsAsChapters.maxBy { it.last_page_read }?.last_page_read + + if (existing != null) { + existing.read = existing.read || chapter.read + existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read) + if (newLastPageRead != null && existing.last_page_read <= 0) { + existing.last_page_read = newLastPageRead + } + existing.bookmark = existing.bookmark || chapter.bookmark + curChapters + } else if (chapter.date_upload > 0) { // Ignore chapters using the old system + new = true + curChapters + ChapterImpl().apply { + manga_id = accepted.manga.id + url = chapter.url + name = chapter.name + read = chapter.read + bookmark = chapter.bookmark + + last_page_read = chapter.last_page_read + if (newLastPageRead != null && last_page_read <= 0) { + last_page_read = newLastPageRead + } + + date_fetch = chapter.date_fetch + date_upload = chapter.date_upload + } + } else curChapters + } + .filter { it.date_upload > 0 } // Ignore chapters using the old system (filter after to prevent dupes from insert) + .sortedBy { it.date_upload } + .apply { + mapIndexed { index, chapter -> + chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ") + chapter.chapter_number = index + 1f + chapter.source_order = lastIndex - index + } + } toDiscard.forEach { it.manga.favorite = false } accepted.manga.favorite = true @@ -165,8 +168,8 @@ data class GalleryEntry(val gId: String, val gToken: String) { override fun read(string: String): GalleryEntry { val colonIndex = string.indexOf(':') return GalleryEntry( - string.substring(0, colonIndex), - string.substring(colonIndex + 1, string.length) + string.substring(0, colonIndex), + string.substring(colonIndex + 1, string.length) ) } } diff --git a/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt b/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt index 42dcfc0f0..f3e260146 100644 --- a/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt +++ b/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt @@ -137,17 +137,19 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { logger.d("Filtering manga and raising metadata...") val curTime = System.currentTimeMillis() 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 + } val meta = db.getFlatMetadataForManga(manga.id!!).asRxSingle().await() - ?: return@mapNotNull null + ?: return@mapNotNull null val raisedMeta = meta.raise() // 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 + } val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minBy { it.date_upload @@ -172,13 +174,15 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { break } - logger.d("Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...", - index, - manga.id, - meta.gId, - meta.gToken, - failuresThisIteration, - modifiedThisIteration.size) + logger.d( + "Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...", + index, + manga.id, + meta.gId, + meta.gToken, + failuresThisIteration, + modifiedThisIteration.size + ) if (manga.id in modifiedThisIteration) { // We already processed this manga! @@ -194,32 +198,37 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { failuresThisIteration++ logger.e("> Network error while updating gallery!", e) - logger.e("> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)", - manga.id, - meta.gId, - meta.gToken, - failuresThisIteration) + logger.e( + "> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)", + manga.id, + meta.gId, + meta.gToken, + failuresThisIteration + ) } continue } if (chapters.isEmpty()) { - logger.e("No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!", - manga.id, - meta.gId, - meta.gToken, - failuresThisIteration) + logger.e( + "No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!", + manga.id, + meta.gId, + meta.gToken, + failuresThisIteration + ) continue } // Find accepted root and discard others val (acceptedRoot, discardedRoots, hasNew) = - updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await() + updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await() 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 } @@ -229,13 +238,13 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { } } finally { prefs.eh_autoUpdateStats().set( - gson.toJson( - EHentaiUpdaterStats( - startTime, - allMeta.size, - updatedThisIteration - ) + gson.toJson( + EHentaiUpdaterStats( + startTime, + allMeta.size, + updatedThisIteration ) + ) ) if (updatedManga.isNotEmpty()) { @@ -247,7 +256,7 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { // New, current suspend fun updateEntryAndGetChapters(manga: Manga): Pair, List> { val source = sourceManager.get(manga.source) as? EHentai - ?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!")) + ?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!")) try { val updatedManga = source.fetchMangaDetails(manga).toSingle().await(Schedulers.io()) @@ -288,8 +297,10 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder { return JobInfo.Builder( - if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST - else JOB_ID_UPDATE_BACKGROUND, componentName()) + if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST + else JOB_ID_UPDATE_BACKGROUND, + componentName() + ) } private fun Context.periodicBackgroundJobInfo( @@ -298,29 +309,32 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { requireUnmetered: Boolean ): JobInfo { return baseBackgroundJobInfo(false) - .setPeriodic(period) - .setPersisted(true) - .setRequiredNetworkType( - if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED - else JobInfo.NETWORK_TYPE_ANY) - .apply { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - setRequiresBatteryNotLow(true) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - setEstimatedNetworkBytes(15000L * UPDATES_PER_ITERATION, - 1000L * UPDATES_PER_ITERATION) - } + .setPeriodic(period) + .setPersisted(true) + .setRequiredNetworkType( + if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED + else JobInfo.NETWORK_TYPE_ANY + ) + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setRequiresBatteryNotLow(true) } - .setRequiresCharging(requireCharging) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + setEstimatedNetworkBytes( + 15000L * UPDATES_PER_ITERATION, + 1000L * UPDATES_PER_ITERATION + ) + } + } + .setRequiresCharging(requireCharging) // .setRequiresDeviceIdle(true) Job never seems to run with this - .build() + .build() } private fun Context.testBackgroundJobInfo(): JobInfo { return baseBackgroundJobInfo(true) - .setOverrideDeadline(1) - .build() + .setOverrideDeadline(1) + .build() } fun launchBackgroundTest(context: Context) { @@ -343,9 +357,9 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { val wifiRestriction = "wifi" in restrictions val jobInfo = context.periodicBackgroundJobInfo( - interval.hours.inMilliseconds.longValue, - acRestriction, - wifiRestriction + interval.hours.inMilliseconds.longValue, + acRestriction, + wifiRestriction ) if (context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) { diff --git a/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt b/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt index c63cfa4e8..1889e63cc 100644 --- a/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt +++ b/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt @@ -10,15 +10,16 @@ class FavoritesIntroDialog { private val prefs: PreferencesHelper by injectLazy() fun show(context: Context) = MaterialDialog(context) - .title(text = "IMPORTANT FAVORITES SYNC NOTES") - .message(text = HtmlCompat.fromHtml(FAVORITES_INTRO_TEXT, HtmlCompat.FROM_HTML_MODE_LEGACY)) - .positiveButton(android.R.string.ok) { - prefs.eh_showSyncIntro().set(false) - } - .cancelable(false) - .show() + .title(text = "IMPORTANT FAVORITES SYNC NOTES") + .message(text = HtmlCompat.fromHtml(FAVORITES_INTRO_TEXT, HtmlCompat.FROM_HTML_MODE_LEGACY)) + .positiveButton(android.R.string.ok) { + prefs.eh_showSyncIntro().set(false) + } + .cancelable(false) + .show() - private val FAVORITES_INTRO_TEXT = """ + private val FAVORITES_INTRO_TEXT = + """ 1. Changes to category names in the app are NOT synced! Please change the category names on ExHentai instead. The category names will be copied from the ExHentai servers every sync.

2. The favorite categories on ExHentai correspond to the first 10 categories in the app (excluding the 'Default' category). Galleries in other categories will NOT be synced! @@ -30,5 +31,5 @@ class FavoritesIntroDialog { 5. Do NOT put favorites in multiple categories (the app supports this). This can confuse the sync algorithm as ExHentai only allows each favorite to be in one category.

This dialog will only popup once. You can read these notes again by going to 'Settings > E-Hentai > Show favorites sync notes'. -""".trimIndent() + """.trimIndent() } diff --git a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt index dd190b2c9..04b2b8ff6 100644 --- a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt +++ b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt @@ -39,7 +39,7 @@ class FavoritesSyncHelper(val context: Context) { private val exh by lazy { Injekt.get().get(EXH_SOURCE_ID) as? EHentai - ?: EHentai(0, true, context) + ?: EHentai(0, true, context) } private val storage = LocalFavoritesStorage() @@ -82,8 +82,10 @@ class FavoritesSyncHelper(val context: Context) { if (it.id in seenManga) { val inCategories = db.getCategoriesForManga(it).executeAsBlocking() - status.onNext(FavoritesSyncStatus.BadLibraryState - .MangaInMultipleCategories(it, inCategories)) + status.onNext( + FavoritesSyncStatus.BadLibraryState + .MangaInMultipleCategories(it, inCategories) + ) logger.w("Manga %s is in multiple categories!", it.id) return } else { @@ -107,13 +109,17 @@ class FavoritesSyncHelper(val context: Context) { // Take wake + wifi locks ignore { wakeLock?.release() } wakeLock = ignore { - context.powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "teh:ExhFavoritesSyncWakelock") + context.powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "teh:ExhFavoritesSyncWakelock" + ) } ignore { wifiLock?.release() } wifiLock = ignore { - context.wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, - "teh:ExhFavoritesSyncWifi") + context.wifiManager.createWifiLock( + WifiManager.WIFI_MODE_FULL, + "teh:ExhFavoritesSyncWifi" + ) } // Do not update galleries while syncing favorites @@ -137,8 +143,9 @@ class FavoritesSyncHelper(val context: Context) { // Apply change sets applyChangeSetToLocal(errorList, remoteChanges) - if (localChanges != null) + if (localChanges != null) { applyChangeSetToRemote(errorList, localChanges) + } status.onNext(FavoritesSyncStatus.Processing("Cleaning up")) storage.snapshotEntries(realm) @@ -173,10 +180,11 @@ class FavoritesSyncHelper(val context: Context) { EHentaiUpdateWorker.scheduleBackground(context) } - if (errorList.isEmpty()) + if (errorList.isEmpty()) { status.onNext(FavoritesSyncStatus.Idle()) - else + } else { status.onNext(FavoritesSyncStatus.CompleteWithErrors(errorList)) + } } private fun applyRemoteCategories(errorList: MutableList, categories: List) { @@ -217,22 +225,25 @@ class FavoritesSyncHelper(val context: Context) { } // Only insert categories if changed - if (changed) + if (changed) { db.insertCategories(newLocalCategories).executeAsBlocking() + } } private fun addGalleryRemote(errorList: MutableList, gallery: FavoriteEntry) { val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav" val request = Request.Builder() - .url(url) - .post(FormBody.Builder() - .add("favcat", gallery.category.toString()) - .add("favnote", "") - .add("apply", "Add to Favorites") - .add("update", "1") - .build()) - .build() + .url(url) + .post( + FormBody.Builder() + .add("favcat", gallery.category.toString()) + .add("favnote", "") + .add("apply", "Add to Favorites") + .add("update", "1") + .build() + ) + .build() if (!explicitlyRetryExhRequest(10, request)) { val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!" @@ -271,8 +282,8 @@ class FavoritesSyncHelper(val context: Context) { status.onNext(FavoritesSyncStatus.Processing("Removing ${changeSet.removed.size} galleries from remote server")) val formBody = FormBody.Builder() - .add("ddact", "delete") - .add("apply", "Apply") + .add("ddact", "delete") + .add("apply", "Apply") // Add change set to form changeSet.removed.forEach { @@ -280,9 +291,9 @@ class FavoritesSyncHelper(val context: Context) { } val request = Request.Builder() - .url("https://exhentai.org/favorites.php") - .post(formBody.build()) - .build() + .url("https://exhentai.org/favorites.php") + .post(formBody.build()) + .build() if (!explicitlyRetryExhRequest(10, request)) { val errorString = "Unable to delete galleries from the remote servers!" @@ -299,8 +310,12 @@ class FavoritesSyncHelper(val context: Context) { // Apply additions throttleManager.resetThrottle() changeSet.added.forEachIndexed { index, it -> - status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to remote server", - needWarnThrottle())) + status.onNext( + FavoritesSyncStatus.Processing( + "Adding gallery ${index + 1} of ${changeSet.added.size} to remote server", + needWarnThrottle() + ) + ) throttleManager.throttle() @@ -317,8 +332,10 @@ class FavoritesSyncHelper(val context: Context) { val url = it.getUrl() // Consider both EX and EH sources - listOf(db.getManga(url, EXH_SOURCE_ID), - db.getManga(url, EH_SOURCE_ID)).forEach { + listOf( + db.getManga(url, EXH_SOURCE_ID), + db.getManga(url, EH_SOURCE_ID) + ).forEach { val manga = it.executeAsBlocking() if (manga?.favorite == true) { @@ -340,16 +357,22 @@ class FavoritesSyncHelper(val context: Context) { // Apply additions throttleManager.resetThrottle() changeSet.added.forEachIndexed { index, it -> - status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to local library", - needWarnThrottle())) + status.onNext( + FavoritesSyncStatus.Processing( + "Adding gallery ${index + 1} of ${changeSet.added.size} to local library", + needWarnThrottle() + ) + ) throttleManager.throttle() // Import using gallery adder - val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}", - true, - exh, - throttleManager::throttle) + val result = galleryAdder.addGallery( + "${exh.baseUrl}${it.getUrl()}", + true, + exh, + throttleManager::throttle + ) if (result is GalleryAddEvent.Fail) { if (result is GalleryAddEvent.Fail.NotFound) { @@ -370,8 +393,10 @@ class FavoritesSyncHelper(val context: Context) { throw IgnoredException() } } else if (result is GalleryAddEvent.Success) { - insertedMangaCategories += MangaCategory.create(result.manga, - categories[it.category]) to result.manga + insertedMangaCategories += MangaCategory.create( + result.manga, + categories[it.category] + ) to result.manga } } @@ -379,12 +404,12 @@ class FavoritesSyncHelper(val context: Context) { insertedMangaCategories.chunked(10).map { Pair(it.map { it.first }, it.map { it.second }) }.forEach { - db.setMangaCategories(it.first, it.second) - } + db.setMangaCategories(it.first, it.second) + } } fun needWarnThrottle() = - throttleManager.throttleTime >= THROTTLE_WARN + throttleManager.throttleTime >= THROTTLE_WARN class IgnoredException : RuntimeException() @@ -401,12 +426,15 @@ sealed class FavoritesSyncStatus(val message: String) { val manga: Manga, val categories: List ) : - 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 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." - else - message) + 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." + } else { + message + } + ) class CompleteWithErrors(messages: List) : FavoritesSyncStatus(messages.joinToString("\n")) } diff --git a/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt b/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt index 9a97d56d5..706468b60 100644 --- a/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt +++ b/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt @@ -14,41 +14,46 @@ class LocalFavoritesStorage { private val db: DatabaseHelper by injectLazy() private val realmConfig = RealmConfiguration.Builder() - .name("fav-sync") - .deleteRealmIfMigrationNeeded() - .build() + .name("fav-sync") + .deleteRealmIfMigrationNeeded() + .build() fun getRealm() = Realm.getInstance(realmConfig) fun getChangedDbEntries(realm: Realm) = - getChangedEntries(realm, + getChangedEntries( + realm, parseToFavoriteEntries( - loadDbCategories( - db.getFavoriteMangas() - .executeAsBlocking() - .asSequence() - ) + loadDbCategories( + db.getFavoriteMangas() + .executeAsBlocking() + .asSequence() + ) ) - ) + ) fun getChangedRemoteEntries(realm: Realm, entries: List) = - getChangedEntries(realm, + getChangedEntries( + realm, parseToFavoriteEntries( - entries.asSequence().map { - Pair(it.fav, it.manga.apply { + entries.asSequence().map { + Pair( + it.fav, + it.manga.apply { favorite = true - }) - } + } + ) + } ) - ) + ) fun snapshotEntries(realm: Realm) { val dbMangas = parseToFavoriteEntries( - loadDbCategories( - db.getFavoriteMangas() - .executeAsBlocking() - .asSequence() - ) + loadDbCategories( + db.getFavoriteMangas() + .executeAsBlocking() + .asSequence() + ) ) // Delete old snapshot @@ -70,29 +75,29 @@ class LocalFavoritesStorage { } val removed = realm.where(FavoriteEntry::class.java) - .findAll() - .filter { - queryListForEntry(terminated, it) == null - }.map { - realm.copyFromRealm(it) - } + .findAll() + .filter { + queryListForEntry(terminated, it) == null + }.map { + realm.copyFromRealm(it) + } return ChangeSet(added, removed) } private fun Realm.queryRealmForEntry(entry: FavoriteEntry) = - where(FavoriteEntry::class.java) + where(FavoriteEntry::class.java) .equalTo(FavoriteEntry::gid.name, entry.gid) .equalTo(FavoriteEntry::token.name, entry.token) .equalTo(FavoriteEntry::category.name, entry.category) .findFirst() private fun queryListForEntry(list: List, entry: FavoriteEntry) = - list.find { - it.gid == entry.gid && + list.find { + it.gid == entry.gid && it.token == entry.token && it.category == entry.category - } + } private fun loadDbCategories(manga: Sequence): Sequence> { val dbCategories = db.getCategories().executeAsBlocking() @@ -100,28 +105,34 @@ class LocalFavoritesStorage { return manga.filter(this::validateDbManga).mapNotNull { val category = db.getCategoriesForManga(it).executeAsBlocking() - Pair(dbCategories.indexOf(category.firstOrNull() - ?: return@mapNotNull null), it) + Pair( + dbCategories.indexOf( + category.firstOrNull() + ?: return@mapNotNull null + ), + it + ) } } private fun parseToFavoriteEntries(manga: Sequence>) = - manga.filter { - validateDbManga(it.second) - }.mapNotNull { - FavoriteEntry().apply { - title = it.second.title - gid = EHentaiSearchMetadata.galleryId(it.second.url) - token = EHentaiSearchMetadata.galleryToken(it.second.url) - category = it.first + manga.filter { + validateDbManga(it.second) + }.mapNotNull { + FavoriteEntry().apply { + title = it.second.title + gid = EHentaiSearchMetadata.galleryId(it.second.url) + token = EHentaiSearchMetadata.galleryToken(it.second.url) + category = it.first - if (this.category > MAX_CATEGORIES) - return@mapNotNull null + if (this.category > MAX_CATEGORIES) { + return@mapNotNull null } } + } 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) companion object { const val MAX_CATEGORIES = 9 diff --git a/app/src/main/java/exh/hitomi/HitomiNozomi.kt b/app/src/main/java/exh/hitomi/HitomiNozomi.kt index 505ca0e4e..90b728907 100644 --- a/app/src/main/java/exh/hitomi/HitomiNozomi.kt +++ b/app/src/main/java/exh/hitomi/HitomiNozomi.kt @@ -71,39 +71,43 @@ class HitomiNozomi( } private fun getGalleryIdsFromData(data: DataPair?): Single> { - if (data == null) + if (data == null) { return Single.just(emptyList()) + } val url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.data" val (offset, length) = data - if (length > 100000000 || length <= 0) + if (length > 100000000 || length <= 0) { return Single.just(emptyList()) + } return client.newCall(rangedGet(url, offset, offset + length - 1)) - .asObservable() - .map { - it.body?.bytes() ?: ByteArray(0) + .asObservable() + .map { + it.body?.bytes() ?: ByteArray(0) + } + .onErrorReturn { ByteArray(0) } + .map { inbuf -> + if (inbuf.isEmpty()) { + return@map emptyList() } - .onErrorReturn { ByteArray(0) } - .map { inbuf -> - if (inbuf.isEmpty()) - return@map emptyList() - val view = ByteCursor(inbuf) - val numberOfGalleryIds = view.nextInt() + val view = ByteCursor(inbuf) + val numberOfGalleryIds = view.nextInt() - val expectedLength = numberOfGalleryIds * 4 + 4 + val expectedLength = numberOfGalleryIds * 4 + 4 - if (numberOfGalleryIds > 10000000 || - numberOfGalleryIds <= 0 || - inbuf.size != expectedLength) { - return@map emptyList() - } + if (numberOfGalleryIds > 10000000 || + numberOfGalleryIds <= 0 || + inbuf.size != expectedLength + ) { + return@map emptyList() + } - (1..numberOfGalleryIds).map { - view.nextInt() - } - }.toSingle() + (1..numberOfGalleryIds).map { + view.nextInt() + } + }.toSingle() } private fun BSearch(field: String, key: ByteArray, node: Node?): Single { @@ -112,10 +116,11 @@ class HitomiNozomi( for (i in 0 until top) { val dv1i = dv1[i].toInt() and 0xFF val dv2i = dv2[i].toInt() and 0xFF - if (dv1i < dv2i) + if (dv1i < dv2i) { return -1 - else if (dv1i > dv2i) + } else if (dv1i > dv2i) { return 1 + } } return 0 } @@ -185,16 +190,16 @@ class HitomiNozomi( } return client.newCall(rangedGet(url, address, address + MAX_NODE_SIZE - 1)) - .asObservableSuccess() - .map { - it.body?.bytes() ?: ByteArray(0) - } - .onErrorReturn { ByteArray(0) } - .map { nodedata -> - if (nodedata.isNotEmpty()) { - decodeNode(nodedata) - } else null - }.toSingle() + .asObservableSuccess() + .map { + it.body?.bytes() ?: ByteArray(0) + } + .onErrorReturn { ByteArray(0) } + .map { nodedata -> + if (nodedata.isNotEmpty()) { + decodeNode(nodedata) + } else null + }.toSingle() } fun getGalleryIdsFromNozomi(area: String?, tag: String, language: String): Single> { @@ -203,17 +208,19 @@ class HitomiNozomi( nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION" } - return client.newCall(Request.Builder() + return client.newCall( + Request.Builder() .url(nozomiAddress) - .build()) - .asObservableSuccess() - .map { resp -> - val body = resp.body!!.bytes() - val cursor = ByteCursor(body) - (1..body.size / 4).map { - cursor.nextInt() - } - }.toSingle() + .build() + ) + .asObservableSuccess() + .map { resp -> + val body = resp.body!!.bytes() + val cursor = ByteCursor(body) + (1..body.size / 4).map { + cursor.nextInt() + } + }.toSingle() } private fun hashTerm(query: String): HashedTerm { @@ -233,15 +240,18 @@ class HitomiNozomi( private val HASH_CHARSET = Charsets.UTF_8 fun rangedGet(url: String, rangeBegin: Long, rangeEnd: Long?): Request { - return GET(url, Headers.Builder() + return GET( + url, + Headers.Builder() .add("Range", "bytes=$rangeBegin-${rangeEnd ?: ""}") - .build()) + .build() + ) } fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable { return httpClient.newCall(GET("$LTN_BASE_URL/$name/version?_=${System.currentTimeMillis()}")) - .asObservableSuccess() - .map { it.body!!.string().toLong() } + .asObservableSuccess() + .map { it.body!!.string().toLong() } } } } diff --git a/app/src/main/java/exh/log/EHDebugModeOverlay.kt b/app/src/main/java/exh/log/EHDebugModeOverlay.kt index d421a8019..91ede5246 100644 --- a/app/src/main/java/exh/log/EHDebugModeOverlay.kt +++ b/app/src/main/java/exh/log/EHDebugModeOverlay.kt @@ -32,8 +32,8 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule(n override fun createView(root: ViewGroup, textColor: Int, textSize: Float, textAlpha: Float): View { val view = LinearLayout(root.context) view.layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT ) view.setPadding(4.dpToPx, 0, 4.dpToPx, 4.dpToPx) val textView = TextView(view.context) @@ -42,15 +42,16 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule(n textView.alpha = textAlpha textView.text = HtmlCompat.fromHtml(buildInfo(), HtmlCompat.FROM_HTML_MODE_LEGACY) textView.layoutParams = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT ) view.addView(textView) this.textView = textView return view } - fun buildInfo() = """ + fun buildInfo() = + """ ===[ ${context.getString(R.string.app_name)} ]===
Build type: ${BuildConfig.BUILD_TYPE}
Debug mode: ${BuildConfig.DEBUG.asEnabledString()}
@@ -58,7 +59,7 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule(n Commit SHA: ${BuildConfig.COMMIT_SHA}
Log level: ${EHLogLevel.currentLogLevel.name.toLowerCase()}
Source blacklist: ${prefs.eh_enableSourceBlacklist().get().asEnabledString()} - """.trimIndent() + """.trimIndent() private fun Boolean.asEnabledString() = if (this) "enabled" else "disabled" } diff --git a/app/src/main/java/exh/log/EHLogLevel.kt b/app/src/main/java/exh/log/EHLogLevel.kt index 1c9213361..cab571749 100644 --- a/app/src/main/java/exh/log/EHLogLevel.kt +++ b/app/src/main/java/exh/log/EHLogLevel.kt @@ -16,7 +16,7 @@ enum class EHLogLevel(val description: String) { fun init(context: Context) { curLogLevel = PreferenceManager.getDefaultSharedPreferences(context) - .getInt(PreferenceKeys.eh_logLevel, 0) + .getInt(PreferenceKeys.eh_logLevel, 0) } fun shouldLog(requiredLogLevel: EHLogLevel): Boolean { diff --git a/app/src/main/java/exh/metadata/MetadataUtil.kt b/app/src/main/java/exh/metadata/MetadataUtil.kt index a4fd81e6d..83d8608a6 100755 --- a/app/src/main/java/exh/metadata/MetadataUtil.kt +++ b/app/src/main/java/exh/metadata/MetadataUtil.kt @@ -35,31 +35,32 @@ fun parseHumanReadableByteCount(arg0: String): Double? { return null } -fun String?.nullIfBlank(): String? = if (isNullOrBlank()) +fun String?.nullIfBlank(): String? = if (isNullOrBlank()) { null -else +} else { this +} fun Set>.forEach(action: (K, V) -> Unit) { forEach { action(it.key, it.value) } } val ONGOING_SUFFIX = arrayOf( - "[ongoing]", - "(ongoing)", - "{ongoing}", - "", - "ongoing", - "[incomplete]", - "(incomplete)", - "{incomplete}", - "", - "incomplete", - "[wip]", - "(wip)", - "{wip}", - "", - "wip" + "[ongoing]", + "(ongoing)", + "{ongoing}", + "", + "ongoing", + "[incomplete]", + "(incomplete)", + "{incomplete}", + "", + "incomplete", + "[wip]", + "(wip)", + "{wip}", + "", + "wip" ) val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) diff --git a/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt index 053938f65..663afe4a6 100644 --- a/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt @@ -50,10 +50,11 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { thumbnailUrl?.let { manga.thumbnail_url = it } // No title bug? - val titleObj = if (Injekt.get().useJapaneseTitle().getOrDefault()) + val titleObj = if (Injekt.get().useJapaneseTitle().getOrDefault()) { altTitle ?: title - else + } else { title + } titleObj?.let { manga.title = it } // Set artist (if we can find one) @@ -102,8 +103,8 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -117,24 +118,25 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { private const val EH_ARTIST_NAMESPACE = "artist" private fun splitGalleryUrl(url: String) = - url.let { - // Only parse URL if is full URL - val pathSegments = if (it.startsWith("http")) - Uri.parse(it).pathSegments - else - it.split('/') - pathSegments.filterNot(String::isNullOrBlank) - } + url.let { + // Only parse URL if is full URL + val pathSegments = if (it.startsWith("http")) { + Uri.parse(it).pathSegments + } else { + it.split('/') + } + pathSegments.filterNot(String::isNullOrBlank) + } fun galleryId(url: String) = splitGalleryUrl(url)[1] fun galleryToken(url: String) = - splitGalleryUrl(url)[2] + splitGalleryUrl(url)[2] fun normalizeUrl(url: String) = - idAndTokenToUrl(galleryId(url), galleryToken(url)) + idAndTokenToUrl(galleryId(url), galleryToken(url)) fun idAndTokenToUrl(id: String, token: String) = - "/g/$id/$token/?nw=always" + "/g/$id/$token/?nw=always" } } diff --git a/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt index 68749333c..1102f9451 100644 --- a/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt @@ -32,8 +32,8 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { diff --git a/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt index 0083bfb70..d1ec0677d 100644 --- a/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt @@ -34,8 +34,8 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { diff --git a/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt index 0d8c2f9ab..598b057a0 100644 --- a/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt @@ -31,15 +31,15 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() { manga.status = SManga.UNKNOWN val detailsDesc = "Title: $title\n" + - "Artist: $artist\n" + "Artist: $artist\n" val tagsDesc = tagsToDescription() manga.genre = tagsToGenreString() manga.description = listOf(detailsDesc, tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -50,6 +50,6 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() { const val BASE_URL = "https://hentai.cafe" fun hcIdFromUrl(url: String) = - url.split("/").last { it.isNotBlank() } + url.split("/").last { it.isNotBlank() } } } diff --git a/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt index 7f9b1603d..ab4d24e2a 100644 --- a/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt @@ -62,11 +62,13 @@ class HitomiSearchMetadata : RaisedSearchMetadata() { detailsDesc += "Language: ${it.capitalize()}\n" } - if (series.isNotEmpty()) + if (series.isNotEmpty()) { detailsDesc += "Series: ${series.joinToString()}\n" + } - if (characters.isNotEmpty()) + if (characters.isNotEmpty()) { detailsDesc += "Characters: ${characters.joinToString()}\n" + } uploadDate?.let { detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n" @@ -80,8 +82,8 @@ class HitomiSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -93,9 +95,9 @@ class HitomiSearchMetadata : RaisedSearchMetadata() { const val BASE_URL = "https://hitomi.la" fun hlIdFromUrl(url: String) = - url.split('/').last().split('-').last().substringBeforeLast('.') + url.split('/').last().split('-').last().substringBeforeLast('.') fun urlFromHlId(id: String) = - "$BASE_URL/galleries/$id.html" + "$BASE_URL/galleries/$id.html" } } diff --git a/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt index 8288484f2..8c9b47d24 100644 --- a/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt @@ -44,9 +44,11 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { if (mediaId != null) { val hqThumbs = Injekt.get().eh_nh_useHighQualityThumbs().getOrDefault() 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" - else "thumb"}.$it" + } else { + "thumb" + }}.$it" } } @@ -91,8 +93,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -108,14 +110,14 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { private const val NHENTAI_CATEGORIES_NAMESPACE = "category" fun typeToExtension(t: String?) = - when (t) { - "p" -> "png" - "j" -> "jpg" - else -> null - } + when (t) { + "p" -> "png" + "j" -> "jpg" + else -> null + } fun nhUrlToId(url: String) = - url.split("/").last { it.isNotBlank() }.toLong() + url.split("/").last { it.isNotBlank() }.toLong() fun nhIdToPath(id: Long) = "/g/$id/" } diff --git a/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt index 1223cd553..bbeb8b07b 100644 --- a/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt @@ -41,11 +41,12 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { manga.title = it titleDesc += "Title: $it\n" } - if (altTitles.isNotEmpty()) + if (altTitles.isNotEmpty()) { titleDesc += "Alternate Titles: \n" + altTitles - .joinToString(separator = "\n", postfix = "\n") { - "▪ $it" - } + .joinToString(separator = "\n", postfix = "\n") { + "▪ $it" + } + } val detailsDesc = StringBuilder() artist?.let { @@ -76,8 +77,8 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -87,9 +88,9 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { const val TAG_TYPE_DEFAULT = 0 private fun splitGalleryUrl(url: String) = - url.let { - Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank) - } + url.let { + Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank) + } fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last() } @@ -102,7 +103,7 @@ enum class PervEdenLang(val id: Long) { companion object { fun source(id: Long) = - values().find { it.id == id } + values().find { it.id == id } ?: throw IllegalArgumentException("Unknown source ID: $id!") } } diff --git a/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt index 930465c08..55c970ebf 100644 --- a/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt @@ -55,8 +55,8 @@ class PururinSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { diff --git a/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt index 68683a858..4d4a3af86 100644 --- a/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt @@ -65,8 +65,8 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -77,7 +77,7 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { val BASE_URL = "https://www.tsumino.com" fun tmIdFromUrl(url: String) = - Uri.parse(url).lastPathSegment + Uri.parse(url).lastPathSegment fun mangaUrlFromId(id: String) = "/Book/Info/$id" diff --git a/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt b/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt index b8aec0370..42c97129f 100644 --- a/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt @@ -18,9 +18,9 @@ data class FlatMetadata( fun raise(clazz: KClass) = RaisedSearchMetadata.raiseFlattenGson - .fromJson(metadata.extra, clazz.java).apply { - fillBaseFields(this@FlatMetadata) - } + .fromJson(metadata.extra, clazz.java).apply { + fillBaseFields(this@FlatMetadata) + } } fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation { diff --git a/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt index f9ee13659..ba8134d77 100644 --- a/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt @@ -36,29 +36,29 @@ abstract class RaisedSearchMetadata { abstract fun copyTo(manga: SManga) fun tagsToGenreString() = - tags.filter { it.type != TAG_TYPE_VIRTUAL } + tags.filter { it.type != TAG_TYPE_VIRTUAL } .joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name } fun tagsToDescription() = - StringBuilder("Tags:\n").apply { - // BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags' - val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy { - it.namespace - }.entries + StringBuilder("Tags:\n").apply { + // BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags' + val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy { + it.namespace + }.entries - groupedTags.forEach { namespace, tags -> - if (tags.isNotEmpty()) { - val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" }) - if (namespace != null) { - this += "▪ " - this += namespace - this += ": " + groupedTags.forEach { namespace, tags -> + if (tags.isNotEmpty()) { + val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" }) + if (namespace != null) { + this += "▪ " + this += namespace + this += ": " + } + this += joinedTags + this += "\n" } - this += joinedTags - this += "\n" } } - } fun List.ofNamespace(ns: String): List { return filter { it.namespace == ns } @@ -76,23 +76,23 @@ abstract class RaisedSearchMetadata { indexedExtra, 0 ), - tags.map { - SearchTag( - null, - mangaId, - it.namespace, - it.name, - it.type - ) - }, - titles.map { - SearchTitle( - null, - mangaId, - it.title, - it.type - ) - } + tags.map { + SearchTag( + null, + mangaId, + it.namespace, + it.name, + it.type + ) + }, + titles.map { + SearchTitle( + null, + mangaId, + it.title, + it.type + ) + } ) } @@ -126,7 +126,7 @@ abstract class RaisedSearchMetadata { * @return the property value. */ override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) = - thisRef.getTitleOfType(type) + thisRef.getTitleOfType(type) /** * Sets the value of the property for the given object. @@ -135,7 +135,7 @@ abstract class RaisedSearchMetadata { * @param value the value to set. */ override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) = - thisRef.replaceTitleOfType(type, value) + thisRef.replaceTitleOfType(type, value) } } } diff --git a/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt b/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt index 7eaf97e30..ff4fdf99f 100755 --- a/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt +++ b/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt @@ -18,22 +18,22 @@ import exh.metadata.sql.tables.SearchTagTable.COL_TYPE import exh.metadata.sql.tables.SearchTagTable.TABLE class SearchTagTypeMapping : SQLiteTypeMapping( - SearchTagPutResolver(), - SearchTagGetResolver(), - SearchTagDeleteResolver() + SearchTagPutResolver(), + SearchTagGetResolver(), + SearchTagDeleteResolver() ) class SearchTagPutResolver : DefaultPutResolver() { override fun mapToInsertQuery(obj: SearchTag) = InsertQuery.builder() - .table(TABLE) - .build() + .table(TABLE) + .build() override fun mapToUpdateQuery(obj: SearchTag) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() override fun mapToContentValues(obj: SearchTag) = ContentValues(5).apply { put(COL_ID, obj.id) @@ -47,19 +47,19 @@ class SearchTagPutResolver : DefaultPutResolver() { class SearchTagGetResolver : DefaultGetResolver() { override fun mapFromCursor(cursor: Cursor): SearchTag = SearchTag( - id = cursor.getLong(cursor.getColumnIndex(COL_ID)), - mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), - namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)), - name = cursor.getString(cursor.getColumnIndex(COL_NAME)), - type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) + id = cursor.getLong(cursor.getColumnIndex(COL_ID)), + mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), + namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)), + name = cursor.getString(cursor.getColumnIndex(COL_NAME)), + type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) ) } class SearchTagDeleteResolver : DefaultDeleteResolver() { override fun mapToDeleteQuery(obj: SearchTag) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt b/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt index 3065d273c..a1f5d0ba2 100755 --- a/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt +++ b/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt @@ -17,22 +17,22 @@ import exh.metadata.sql.tables.SearchTitleTable.COL_TYPE import exh.metadata.sql.tables.SearchTitleTable.TABLE class SearchTitleTypeMapping : SQLiteTypeMapping( - SearchTitlePutResolver(), - SearchTitleGetResolver(), - SearchTitleDeleteResolver() + SearchTitlePutResolver(), + SearchTitleGetResolver(), + SearchTitleDeleteResolver() ) class SearchTitlePutResolver : DefaultPutResolver() { override fun mapToInsertQuery(obj: SearchTitle) = InsertQuery.builder() - .table(TABLE) - .build() + .table(TABLE) + .build() override fun mapToUpdateQuery(obj: SearchTitle) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() override fun mapToContentValues(obj: SearchTitle) = ContentValues(4).apply { put(COL_ID, obj.id) @@ -45,18 +45,18 @@ class SearchTitlePutResolver : DefaultPutResolver() { class SearchTitleGetResolver : DefaultGetResolver() { override fun mapFromCursor(cursor: Cursor): SearchTitle = SearchTitle( - id = cursor.getLong(cursor.getColumnIndex(COL_ID)), - mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), - title = cursor.getString(cursor.getColumnIndex(COL_TITLE)), - type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) + id = cursor.getLong(cursor.getColumnIndex(COL_ID)), + mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), + title = cursor.getString(cursor.getColumnIndex(COL_TITLE)), + type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) ) } class SearchTitleDeleteResolver : DefaultDeleteResolver() { override fun mapToDeleteQuery(obj: SearchTitle) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/exh/metadata/sql/models/SearchTag.kt b/app/src/main/java/exh/metadata/sql/models/SearchTag.kt index 324d1ecd0..c52a36633 100644 --- a/app/src/main/java/exh/metadata/sql/models/SearchTag.kt +++ b/app/src/main/java/exh/metadata/sql/models/SearchTag.kt @@ -1,18 +1,18 @@ package exh.metadata.sql.models data class SearchTag( - // Tag identifier, unique + // Tag identifier, unique val id: Long?, - // Metadata this tag is attached to + // Metadata this tag is attached to val mangaId: Long, - // Tag namespace + // Tag namespace val namespace: String?, - // Tag name + // Tag name val name: String, - // Tag type + // Tag type val type: Int ) diff --git a/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt b/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt index ff43b0a68..c212cde0f 100644 --- a/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt +++ b/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt @@ -1,15 +1,15 @@ package exh.metadata.sql.models data class SearchTitle( - // Title identifier, unique + // Title identifier, unique val id: Long?, - // Metadata this title is attached to + // Metadata this title is attached to val mangaId: Long, - // Title + // Title val title: String, - // Title type, useful for distinguishing between main/alt titles + // Title type, useful for distinguishing between main/alt titles val type: Int ) diff --git a/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt b/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt index b681ceab2..11a12e2b9 100755 --- a/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt +++ b/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt @@ -9,21 +9,25 @@ import exh.metadata.sql.tables.SearchTagTable interface SearchTagQueries : DbProvider { fun getSearchTagsForManga(mangaId: Long) = db.get() - .listOfObjects(SearchTag::class.java) - .withQuery(Query.builder() - .table(SearchTagTable.TABLE) - .where("${SearchTagTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build()) - .prepare() + .listOfObjects(SearchTag::class.java) + .withQuery( + Query.builder() + .table(SearchTagTable.TABLE) + .where("${SearchTagTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() fun deleteSearchTagsForManga(mangaId: Long) = db.delete() - .byQuery(DeleteQuery.builder() - .table(SearchTagTable.TABLE) - .where("${SearchTagTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build()) - .prepare() + .byQuery( + DeleteQuery.builder() + .table(SearchTagTable.TABLE) + .where("${SearchTagTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare() @@ -31,10 +35,12 @@ interface SearchTagQueries : DbProvider { 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) - .build()) - .prepare() + .build() + ) + .prepare() fun setSearchTagsForManga(mangaId: Long, tags: List) { db.inTransaction { diff --git a/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt b/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt index 30108faaa..29ba48e16 100755 --- a/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt +++ b/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt @@ -9,21 +9,25 @@ import exh.metadata.sql.tables.SearchTitleTable interface SearchTitleQueries : DbProvider { fun getSearchTitlesForManga(mangaId: Long) = db.get() - .listOfObjects(SearchTitle::class.java) - .withQuery(Query.builder() - .table(SearchTitleTable.TABLE) - .where("${SearchTitleTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build()) - .prepare() + .listOfObjects(SearchTitle::class.java) + .withQuery( + Query.builder() + .table(SearchTitleTable.TABLE) + .where("${SearchTitleTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() fun deleteSearchTitlesForManga(mangaId: Long) = db.delete() - .byQuery(DeleteQuery.builder() - .table(SearchTitleTable.TABLE) - .where("${SearchTitleTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build()) - .prepare() + .byQuery( + DeleteQuery.builder() + .table(SearchTitleTable.TABLE) + .where("${SearchTitleTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare() @@ -31,10 +35,12 @@ interface SearchTitleQueries : DbProvider { 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) - .build()) - .prepare() + .build() + ) + .prepare() fun setSearchTitlesForManga(mangaId: Long, titles: List) { db.inTransaction { diff --git a/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt b/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt index efbaecd1a..434f52f65 100755 --- a/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt +++ b/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt @@ -17,7 +17,8 @@ object SearchMetadataTable { // Insane foreign, primary key to avoid touch manga table val createTableQuery: String - get() = """CREATE TABLE $TABLE( + get() = + """CREATE TABLE $TABLE( $COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY, $COL_UPLOADER TEXT, $COL_EXTRA TEXT NOT NULL, diff --git a/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt b/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt index 81be76583..a9b3e72c3 100755 --- a/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt +++ b/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt @@ -16,7 +16,8 @@ object SearchTagTable { const val COL_TYPE = "type" val createTableQuery: String - get() = """CREATE TABLE $TABLE( + get() = + """CREATE TABLE $TABLE( $COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_MANGA_ID INTEGER NOT NULL, $COL_NAMESPACE TEXT, diff --git a/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt b/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt index d72cb9b8c..48b71e422 100755 --- a/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt +++ b/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt @@ -14,7 +14,8 @@ object SearchTitleTable { const val COL_TYPE = "type" val createTableQuery: String - get() = """CREATE TABLE $TABLE( + get() = + """CREATE TABLE $TABLE( $COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_MANGA_ID INTEGER NOT NULL, $COL_TITLE TEXT NOT NULL, diff --git a/app/src/main/java/exh/patch/MangaDexLogin.kt b/app/src/main/java/exh/patch/MangaDexLogin.kt index f863bc798..3221295a9 100644 --- a/app/src/main/java/exh/patch/MangaDexLogin.kt +++ b/app/src/main/java/exh/patch/MangaDexLogin.kt @@ -9,13 +9,14 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -private val HIDE_SCRIPT = """ - document.querySelector("#forgot_button").style.visibility = "hidden"; - document.querySelector("#signup_button").style.visibility = "hidden"; - document.querySelector("#announcement").style.visibility = "hidden"; - document.querySelector("nav").style.visibility = "hidden"; - document.querySelector("footer").style.visibility = "hidden"; - """.trimIndent() +private val HIDE_SCRIPT = + """ + document.querySelector("#forgot_button").style.visibility = "hidden"; + document.querySelector("#signup_button").style.visibility = "hidden"; + document.querySelector("#announcement").style.visibility = "hidden"; + document.querySelector("nav").style.visibility = "hidden"; + document.querySelector("footer").style.visibility = "hidden"; + """.trimIndent() private fun verifyComplete(url: String): Boolean { return url.toHttpUrlOrNull()?.let { parsed -> @@ -28,14 +29,14 @@ val MANGADEX_LOGIN_PATCH: EHInterceptor = { request, response, sourceId -> response.interceptAsHtml { doc -> if (doc.title().trim().equals("Login - MangaDex", true)) { BrowserActionActivity.launchAction( - Injekt.get(), - ::verifyComplete, - HIDE_SCRIPT, - "https://mangadex.org/login", - "Login", - (Injekt.get().get(sourceId) as? HttpSource)?.headers?.toMultimap()?.mapValues { - it.value.joinToString(",") - } ?: emptyMap() + Injekt.get(), + ::verifyComplete, + HIDE_SCRIPT, + "https://mangadex.org/login", + "Login", + (Injekt.get().get(sourceId) as? HttpSource)?.headers?.toMultimap()?.mapValues { + it.value.joinToString(",") + } ?: emptyMap() ) } } @@ -43,43 +44,43 @@ val MANGADEX_LOGIN_PATCH: EHInterceptor = { request, response, sourceId -> } val MANGADEX_SOURCE_IDS = listOf( - 2499283573021220255, - 8033579885162383068, - 1952071260038453057, - 2098905203823335614, - 5098537545549490547, - 4505830566611664829, - 9194073792736219759, - 6400665728063187402, - 4938773340256184018, - 5860541308324630662, - 5189216366882819742, - 2655149515337070132, - 1145824452519314725, - 3846770256925560569, - 3807502156582598786, - 4284949320785450865, - 5463447640980279236, - 8578871918181236609, - 6750440049024086587, - 3339599426223341161, - 5148895169070562838, - 1493666528525752601, - 1713554459881080228, - 4150470519566206911, - 1347402746269051958, - 3578612018159256808, - 425785191804166217, - 8254121249433835847, - 3260701926561129943, - 1411768577036936240, - 3285208643537017688, - 737986167355114438, - 1471784905273036181, - 5967745367608513818, - 3781216447842245147, - 4774459486579224459, - 4710920497926776490, - 5779037855201976894 + 2499283573021220255, + 8033579885162383068, + 1952071260038453057, + 2098905203823335614, + 5098537545549490547, + 4505830566611664829, + 9194073792736219759, + 6400665728063187402, + 4938773340256184018, + 5860541308324630662, + 5189216366882819742, + 2655149515337070132, + 1145824452519314725, + 3846770256925560569, + 3807502156582598786, + 4284949320785450865, + 5463447640980279236, + 8578871918181236609, + 6750440049024086587, + 3339599426223341161, + 5148895169070562838, + 1493666528525752601, + 1713554459881080228, + 4150470519566206911, + 1347402746269051958, + 3578612018159256808, + 425785191804166217, + 8254121249433835847, + 3260701926561129943, + 1411768577036936240, + 3285208643537017688, + 737986167355114438, + 1471784905273036181, + 5967745367608513818, + 3781216447842245147, + 4774459486579224459, + 4710920497926776490, + 5779037855201976894 ) const val MANGADEX_DOMAIN = "mangadex.org" diff --git a/app/src/main/java/exh/patch/NetworkPatches.kt b/app/src/main/java/exh/patch/NetworkPatches.kt index 2b263f97c..6d3a7fe56 100644 --- a/app/src/main/java/exh/patch/NetworkPatches.kt +++ b/app/src/main/java/exh/patch/NetworkPatches.kt @@ -16,8 +16,10 @@ fun OkHttpClient.Builder.injectPatches(sourceIdProducer: () -> Long): OkHttpClie } fun findAndApplyPatches(sourceId: Long): EHInterceptor { - return ((EH_INTERCEPTORS[sourceId] ?: emptyList()) + - (EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList())).merge() + return ( + (EH_INTERCEPTORS[sourceId] ?: emptyList()) + + (EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList()) + ).merge() } fun List.merge(): EHInterceptor { @@ -30,12 +32,12 @@ fun List.merge(): EHInterceptor { private const val EH_UNIVERSAL_INTERCEPTOR = -1L private val EH_INTERCEPTORS: Map> = mapOf( - EH_UNIVERSAL_INTERCEPTOR to listOf( - CAPTCHA_DETECTION_PATCH // Auto captcha detection - ), + EH_UNIVERSAL_INTERCEPTOR to listOf( + CAPTCHA_DETECTION_PATCH // Auto captcha detection + ), - // MangaDex login support - *MANGADEX_SOURCE_IDS.map { id -> - id to listOf(MANGADEX_LOGIN_PATCH) - }.toTypedArray() + // MangaDex login support + *MANGADEX_SOURCE_IDS.map { id -> + id to listOf(MANGADEX_LOGIN_PATCH) + }.toTypedArray() ) diff --git a/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt b/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt index 87cc19016..ffc4293e2 100644 --- a/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt +++ b/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt @@ -13,9 +13,9 @@ val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId -> if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) { // Found it, allow the user to solve this thing BrowserActionActivity.launchUniversal( - Injekt.get(), - sourceId, - request.url.toString() + Injekt.get(), + sourceId, + request.url.toString() ) } } diff --git a/app/src/main/java/exh/search/SearchEngine.kt b/app/src/main/java/exh/search/SearchEngine.kt index 8299d6628..ff723676f 100755 --- a/app/src/main/java/exh/search/SearchEngine.kt +++ b/app/src/main/java/exh/search/SearchEngine.kt @@ -12,10 +12,11 @@ class SearchEngine { component: Text? ): Pair>? { val maybeLenientComponent = component?.let { - if (!it.exact) - it.asLenientTagQueries() - else - listOf(it.asQuery()) + if (!it.exact) { + it.asLenientTagQueries() + } else { + listOf(it.asQuery()) + } } val componentTagQuery = maybeLenientComponent?.let { val params = mutableListOf() @@ -25,11 +26,12 @@ class SearchEngine { }.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params } return if (namespace != null) { - var query = """ + var query = + """ (SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE} WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL AND ${SearchTagTable.COL_NAMESPACE} LIKE ? - """.trimIndent() + """.trimIndent() val params = mutableListOf(escapeLike(namespace)) if (componentTagQuery != null) { query += "\n AND ${componentTagQuery.first}" @@ -39,18 +41,20 @@ class SearchEngine { "$query)" to params } else if (component != null) { // Match title + tags - val tagQuery = """ + val tagQuery = + """ SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE} 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} WHERE ${SearchTitleTable.COL_TITLE} LIKE ? - """.trimIndent() to listOf(component.asLenientTitleQuery()) + """.trimIndent() to listOf(component.asLenientTitleQuery()) "(${tagQuery.first} UNION ${titleQuery.first})".trimIndent() to - (tagQuery.second + titleQuery.second) + (tagQuery.second + titleQuery.second) } else null } @@ -86,22 +90,25 @@ class SearchEngine { } val completeParams = mutableListOf() - var baseQuery = """ + var baseQuery = + """ SELECT ${SearchMetadataTable.COL_MANGA_ID} FROM ${SearchMetadataTable.TABLE} meta - """.trimIndent() + """.trimIndent() include.forEachIndexed { index, pair -> - baseQuery += "\n" + (""" + baseQuery += "\n" + ( + """ INNER JOIN ${pair.first} i$index ON i$index.$COL_MANGA_ID = meta.${SearchMetadataTable.COL_MANGA_ID} - """.trimIndent()) + """.trimIndent() + ) completeParams += pair.second } exclude.forEach { wheres += """ - (meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first}) + (meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first}) """.trimIndent() whereParams += it.second } @@ -196,8 +203,8 @@ class SearchEngine { fun escapeLike(string: String): String { return string.replace("\\", "\\\\") - .replace("_", "\\_") - .replace("%", "\\%") + .replace("_", "\\_") + .replace("%", "\\%") } } } diff --git a/app/src/main/java/exh/search/Text.kt b/app/src/main/java/exh/search/Text.kt index 2c0abd845..eccf75547 100755 --- a/app/src/main/java/exh/search/Text.kt +++ b/app/src/main/java/exh/search/Text.kt @@ -28,13 +28,13 @@ class Text : QueryComponent() { fun asLenientTagQueries(): List { if (lenientTagQueries == null) { lenientTagQueries = listOf( - // Match beginning of tag - rBaseBuilder().append("%").toString(), - // Tag word matcher (that matches multiple words) - // Can't make it match a single word in Realm :( - StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(), - StringBuilder(" ").append(rBaseBuilder()).toString(), - rBaseBuilder().append(" ").toString() + // Match beginning of tag + rBaseBuilder().append("%").toString(), + // Tag word matcher (that matches multiple words) + // Can't make it match a single word in Realm :( + StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(), + StringBuilder(" ").append(rBaseBuilder()).toString(), + rBaseBuilder().append(" ").toString() ) } return lenientTagQueries!! @@ -52,11 +52,11 @@ class Text : QueryComponent() { return builder } - fun rawTextOnly() = if (rawText != null) + fun rawTextOnly() = if (rawText != null) { rawText!! - else { + } else { rawText = components - .joinToString(separator = "", transform = { it.rawText }) + .joinToString(separator = "", transform = { it.rawText }) rawText!! } diff --git a/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt b/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt index 8e6002a68..017598e9e 100644 --- a/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt +++ b/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt @@ -62,8 +62,9 @@ class SmartSearchEngine( } else title 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)) + } searchResults.mangas.map { val normalizedDistance = normalizedLevenshtein.similarity(title, it.title) diff --git a/app/src/main/java/exh/source/BlacklistedSources.kt b/app/src/main/java/exh/source/BlacklistedSources.kt index c346fc9c0..38e6aa57d 100644 --- a/app/src/main/java/exh/source/BlacklistedSources.kt +++ b/app/src/main/java/exh/source/BlacklistedSources.kt @@ -7,38 +7,38 @@ object BlacklistedSources { val PERVEDEN_EN_EXT_SOURCES = listOf(4673633799850248749) val PERVEDEN_IT_EXT_SOURCES = listOf(1433898225963724122) val EHENTAI_EXT_SOURCES = listOf( - 8100626124886895451, - 57122881048805941, - 4678440076103929247, - 1876021963378735852, - 3955189842350477641, - 4348288691341764259, - 773611868725221145, - 5759417018342755550, - 825187715438990384, - 6116711405602166104, - 7151438547982231541, - 2171445159732592630, - 3032959619549451093, - 5980349886941016589, - 6073266008352078708, - 5499077866612745456, - 6140480779421365791 + 8100626124886895451, + 57122881048805941, + 4678440076103929247, + 1876021963378735852, + 3955189842350477641, + 4348288691341764259, + 773611868725221145, + 5759417018342755550, + 825187715438990384, + 6116711405602166104, + 7151438547982231541, + 2171445159732592630, + 3032959619549451093, + 5980349886941016589, + 6073266008352078708, + 5499077866612745456, + 6140480779421365791 ) val BLACKLISTED_EXT_SOURCES = NHENTAI_EXT_SOURCES + - PERVEDEN_EN_EXT_SOURCES + - PERVEDEN_IT_EXT_SOURCES + - EHENTAI_EXT_SOURCES + PERVEDEN_EN_EXT_SOURCES + + PERVEDEN_IT_EXT_SOURCES + + EHENTAI_EXT_SOURCES val BLACKLISTED_EXTENSIONS = listOf( - "eu.kanade.tachiyomi.extension.all.ehentai", - "eu.kanade.tachiyomi.extension.all.nhentai", - "eu.kanade.tachiyomi.extension.en.perveden", - "eu.kanade.tachiyomi.extension.it.perveden" + "eu.kanade.tachiyomi.extension.all.ehentai", + "eu.kanade.tachiyomi.extension.all.nhentai", + "eu.kanade.tachiyomi.extension.en.perveden", + "eu.kanade.tachiyomi.extension.it.perveden" ) val HIDDEN_SOURCES = listOf( - MERGED_SOURCE_ID + MERGED_SOURCE_ID ) } diff --git a/app/src/main/java/exh/source/DelegatedHttpSource.kt b/app/src/main/java/exh/source/DelegatedHttpSource.kt index 9213e1c17..490fdcd8d 100644 --- a/app/src/main/java/exh/source/DelegatedHttpSource.kt +++ b/app/src/main/java/exh/source/DelegatedHttpSource.kt @@ -18,7 +18,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param page the page number to retrieve. */ override fun popularMangaRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. @@ -26,7 +26,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun popularMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Returns the request for the search manga given the page. @@ -36,7 +36,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param filters the list of filters to apply. */ override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. @@ -44,7 +44,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun searchMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Returns the request for latest manga given the page. @@ -52,7 +52,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param page the page number to retrieve. */ override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. @@ -60,7 +60,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun latestUpdatesParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the details of a manga. @@ -68,7 +68,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun mangaDetailsParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of chapters. @@ -76,7 +76,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun chapterListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of pages. @@ -84,7 +84,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun pageListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the absolute url to the source image. @@ -92,7 +92,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param response the response from the site. */ override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Base url of the website without the trailing slash, like: http://mysite.com @@ -240,7 +240,8 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { private fun ensureDelegateCompatible() { 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})!") } } diff --git a/app/src/main/java/exh/source/EnhancedHttpSource.kt b/app/src/main/java/exh/source/EnhancedHttpSource.kt index c7467acc3..ba294b076 100644 --- a/app/src/main/java/exh/source/EnhancedHttpSource.kt +++ b/app/src/main/java/exh/source/EnhancedHttpSource.kt @@ -23,7 +23,7 @@ class EnhancedHttpSource( * @param page the page number to retrieve. */ override fun popularMangaRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. @@ -31,7 +31,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun popularMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Returns the request for the search manga given the page. @@ -41,7 +41,7 @@ class EnhancedHttpSource( * @param filters the list of filters to apply. */ override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. @@ -49,7 +49,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun searchMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Returns the request for latest manga given the page. @@ -57,7 +57,7 @@ class EnhancedHttpSource( * @param page the page number to retrieve. */ override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. @@ -65,7 +65,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun latestUpdatesParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the details of a manga. @@ -73,7 +73,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun mangaDetailsParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of chapters. @@ -81,7 +81,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun chapterListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of pages. @@ -89,7 +89,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun pageListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the absolute url to the source image. @@ -97,7 +97,7 @@ class EnhancedHttpSource( * @param response the response from the site. */ override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + throw UnsupportedOperationException("Should never be called!") /** * Base url of the website without the trailing slash, like: http://mysite.com @@ -153,7 +153,7 @@ class EnhancedHttpSource( * @param filters the list of filters to apply. */ override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - source().fetchSearchManga(page, query, filters) + source().fetchSearchManga(page, query, filters) /** * Returns an observable containing a page with a list of latest manga updates. @@ -209,7 +209,7 @@ class EnhancedHttpSource( * @param manga the manga of the chapter. */ override fun prepareNewChapter(chapter: SChapter, manga: SManga) = - source().prepareNewChapter(chapter, manga) + source().prepareNewChapter(chapter, manga) /** * Returns the list of filters for the source. diff --git a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt index 3eec944e8..5bc9a8f6b 100644 --- a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt +++ b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt @@ -14,7 +14,7 @@ class ConfiguringDialogController : DialogController() { private var materialDialog: MaterialDialog? = null override fun onCreateDialog(savedViewState: Bundle?): Dialog { - if (savedViewState == null) + if (savedViewState == null) { thread { try { EHConfigurator().configureAll() @@ -25,10 +25,10 @@ class ConfiguringDialogController : DialogController() { activity?.let { it.runOnUiThread { MaterialDialog(it) - .title(text = "Configuration failed!") - .message(text = "An error occurred during the configuration process: " + e.message) - .positiveButton(android.R.string.ok) - .show() + .title(text = "Configuration failed!") + .message(text = "An error occurred during the configuration process: " + e.message) + .positiveButton(android.R.string.ok) + .show() } } Timber.e(e, "Configuration error!") @@ -37,14 +37,15 @@ class ConfiguringDialogController : DialogController() { finish() } } + } return MaterialDialog(activity!!) - .title(text = "Uploading settings to server") - .message(text = "Please wait, this may take some time...") - .cancelable(false) - .also { - materialDialog = it - } + .title(text = "Uploading settings to server") + .message(text = "Please wait, this may take some time...") + .cancelable(false) + .also { + materialDialog = it + } } override fun onDestroyView(view: View) { diff --git a/app/src/main/java/exh/uconfig/EHConfigurator.kt b/app/src/main/java/exh/uconfig/EHConfigurator.kt index 2eea81516..c5ec98e25 100644 --- a/app/src/main/java/exh/uconfig/EHConfigurator.kt +++ b/app/src/main/java/exh/uconfig/EHConfigurator.kt @@ -18,11 +18,11 @@ class EHConfigurator { private val sources: SourceManager by injectLazy() private val configuratorClient = OkHttpClient.Builder() - .maybeInjectEHLogger() - .build() + .maybeInjectEHLogger() + .build() private fun EHentai.requestWithCreds(sp: Int = 1) = Request.Builder() - .addHeader("Cookie", cookiesHeader(sp)) + .addHeader("Cookie", cookiesHeader(sp)) private fun EHentai.execProfileActions( action: String, @@ -30,15 +30,19 @@ class EHConfigurator { set: String, sp: Int ) = - configuratorClient.newCall(requestWithCreds(sp) + configuratorClient.newCall( + requestWithCreds(sp) .url(uconfigUrl) - .post(FormBody.Builder() + .post( + FormBody.Builder() .add("profile_action", action) .add("profile_name", name) .add("profile_set", set) - .build()) - .build()) - .execute() + .build() + ) + .build() + ) + .execute() private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL @@ -47,10 +51,12 @@ class EHConfigurator { val exhSource = sources.get(EXH_SOURCE_ID) as EHentai // Get hath perks - val perksPage = configuratorClient.newCall(ehSource.requestWithCreds() + val perksPage = configuratorClient.newCall( + ehSource.requestWithCreds() .url(HATH_PERKS_URL) - .build()) - .execute().asJsoup() + .build() + ) + .execute().asJsoup() val hathPerks = EHHathPerksResponse() @@ -97,24 +103,29 @@ class EHConfigurator { } // 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!") - + } // Create profile in available slot + val slot = availableProfiles.first() - val response = source.execProfileActions("create", - PROFILE_NAME, - slot.toString(), - 1) + val response = source.execProfileActions( + "create", + PROFILE_NAME, + slot.toString(), + 1 + ) // Build new profile val form = EhUConfigBuilder().build(hathPerks) // Send new profile to server - configuratorClient.newCall(source.requestWithCreds(sp = slot) + configuratorClient.newCall( + source.requestWithCreds(sp = slot) .url(source.uconfigUrl) .post(form) - .build()).execute() + .build() + ).execute() // Persist slot + sk source.spPref().set(slot) @@ -129,12 +140,15 @@ class EHConfigurator { it.startsWith("hath_perks=") }?.removePrefix("hath_perks=")?.substringBefore(';') - if (keyCookie != null) + if (keyCookie != null) { prefs.eh_settingsKey().set(keyCookie) - if (sessionCookie != null) + } + if (sessionCookie != null) { prefs.eh_sessionCookie().set(sessionCookie) - if (hathPerksCookie != null) + } + if (hathPerksCookie != null) { prefs.eh_hathPerksCookies().set(hathPerksCookie) + } } companion object { diff --git a/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt b/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt index 6c2567093..36cd18ea3 100644 --- a/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt +++ b/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt @@ -11,9 +11,11 @@ class EhUConfigBuilder { fun build(hathPerks: EHHathPerksResponse): FormBody { val configItems = mutableListOf() - configItems += when (prefs.imageQuality() + configItems += when ( + prefs.imageQuality() .getOrDefault() - .toLowerCase()) { + .toLowerCase() + ) { "ovrs_2400" -> Entry.ImageSize.`2400` "ovrs_1600" -> Entry.ImageSize.`1600` "high" -> Entry.ImageSize.`1280` @@ -23,20 +25,23 @@ class EhUConfigBuilder { else -> Entry.ImageSize.AUTO } - configItems += if (prefs.useHentaiAtHome().getOrDefault()) + configItems += if (prefs.useHentaiAtHome().getOrDefault()) { Entry.UseHentaiAtHome.YES - else + } else { Entry.UseHentaiAtHome.NO + } - configItems += if (prefs.useJapaneseTitle().getOrDefault()) + configItems += if (prefs.useJapaneseTitle().getOrDefault()) { Entry.TitleDisplayLanguage.JAPANESE - else + } else { Entry.TitleDisplayLanguage.DEFAULT + } - configItems += if (prefs.eh_useOriginalImages().getOrDefault()) + configItems += if (prefs.eh_useOriginalImages().getOrDefault()) { Entry.UseOriginalImages.YES - else + } else { Entry.UseOriginalImages.NO + } configItems += when { hathPerks.allThumbs -> Entry.ThumbnailRows.`40` diff --git a/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt b/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt index 7029e0c90..8dcbd9ab4 100644 --- a/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt +++ b/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt @@ -14,25 +14,29 @@ class WarnConfigureDialogController : DialogController() { private val prefs: PreferencesHelper by injectLazy() override fun onCreateDialog(savedViewState: Bundle?): Dialog { return MaterialDialog(activity!!) - .title(text = "Settings profile note") - .message(text = """ + .title(text = "Settings profile note") + .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. If you have no idea what settings profiles are, then it probably doesn't matter, just hit 'OK'. - """.trimIndent()) - .positiveButton(android.R.string.ok) { - prefs.eh_showSettingsUploadWarning().set(false) - ConfiguringDialogController().showDialog(router) - } - .cancelable(false) + """.trimIndent() + ) + .positiveButton(android.R.string.ok) { + prefs.eh_showSettingsUploadWarning().set(false) + ConfiguringDialogController().showDialog(router) + } + .cancelable(false) } companion object { fun uploadSettings(router: Router) { - if (Injekt.get().eh_showSettingsUploadWarning().get()) + if (Injekt.get().eh_showSettingsUploadWarning().get()) { WarnConfigureDialogController().showDialog(router) - else + } else { ConfiguringDialogController().showDialog(router) + } } } } diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddController.kt b/app/src/main/java/exh/ui/batchadd/BatchAddController.kt index 12bd832be..88604522f 100755 --- a/app/src/main/java/exh/ui/batchadd/BatchAddController.kt +++ b/app/src/main/java/exh/ui/batchadd/BatchAddController.kt @@ -49,66 +49,68 @@ class BatchAddController : NucleusController - // Show hide dismiss button - binding.progressDismissBtn.visibility = - if (progress == total) - View.VISIBLE - else View.GONE + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + progressSubscriptions.clear() + if (it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) { + showProgress(this) + progressSubscriptions += presenter.progressRelay + .observeOn(AndroidSchedulers.mainThread()) + .combineLatest(presenter.progressTotalRelay) { progress, total -> + // Show hide dismiss button + binding.progressDismissBtn.visibility = + if (progress == total) { + View.VISIBLE + } else { + View.GONE + } - formatProgress(progress, total) - }.subscribeUntilDestroy { + formatProgress(progress, total) + }.subscribeUntilDestroy { binding.progressText.text = it } - progressSubscriptions += presenter.progressTotalRelay - .observeOn(AndroidSchedulers.mainThread()) - .subscribeUntilDestroy { - binding.progressBar.max = it - } - - progressSubscriptions += presenter.progressRelay - .observeOn(AndroidSchedulers.mainThread()) - .subscribeUntilDestroy { - binding.progressBar.progress = it - } - - presenter.eventRelay - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribeUntilDestroy { - binding.progressLog.append("$it\n") - }?.let { - progressSubscriptions += it + progressSubscriptions += presenter.progressTotalRelay + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + binding.progressBar.max = it } - } else if (it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) { - hideProgress(this) - presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE) + + progressSubscriptions += presenter.progressRelay + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + binding.progressBar.progress = it + } + + presenter.eventRelay + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribeUntilDestroy { + binding.progressLog.append("$it\n") + }?.let { + progressSubscriptions += it } + } else if (it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) { + hideProgress(this) + presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE) } + } } } private val View.progressViews get() = listOf( - binding.progressTitleView, - binding.progressLogWrapper, - binding.progressBar, - binding.progressText, - binding.progressDismissBtn + binding.progressTitleView, + binding.progressLogWrapper, + binding.progressBar, + binding.progressText, + binding.progressDismissBtn ) private val View.inputViews get() = listOf( - binding.inputTitleView, - binding.galleriesBox, - binding.btnAddGalleries + binding.inputTitleView, + binding.galleriesBox, + binding.btnAddGalleries ) private var List.visibility: Int @@ -144,12 +146,12 @@ class BatchAddController : NucleusController materialDialog.dismiss() } - .cancelable(true) - .cancelOnTouchOutside(true) - .show() + .title(text = "No galleries to add!") + .message(text = "You must specify at least one gallery to add!") + .positiveButton(android.R.string.ok) { materialDialog -> materialDialog.dismiss() } + .cancelable(true) + .cancelOnTouchOutside(true) + .show() } } } diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt b/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt index 241b143c0..e69778eb6 100644 --- a/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt +++ b/app/src/main/java/exh/ui/batchadd/BatchAddPresenter.kt @@ -40,10 +40,14 @@ class BatchAddPresenter : BasePresenter() { failed.add(s) } progressRelay.call(i + 1) - eventRelay?.call((when (result) { - is GalleryAddEvent.Success -> "[OK]" - is GalleryAddEvent.Fail -> "[ERROR]" - }) + " " + result.logMessage) + eventRelay?.call( + ( + when (result) { + is GalleryAddEvent.Success -> "[OK]" + is GalleryAddEvent.Fail -> "[ERROR]" + } + ) + " " + result.logMessage + ) } // Show report diff --git a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt index 5741275cb..386efa38b 100644 --- a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt @@ -26,9 +26,9 @@ class AutoSolvingWebViewClient( val doc = response.asJsoup() doc.body().appendChild(Element("script").appendChild(DataNode(CROSS_WINDOW_SCRIPT_INNER))) return WebResourceResponse( - "text/html", - "UTF-8", - doc.toString().byteInputStream(Charset.forName("UTF-8")).buffered() + "text/html", + "UTF-8", + doc.toString().byteInputStream(Charset.forName("UTF-8")).buffered() ) } return super.shouldInterceptRequest(view, request) diff --git a/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt b/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt index 57a5484f7..bed8f1c1a 100644 --- a/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt @@ -14,8 +14,9 @@ open class BasicWebViewClient( if (verifyComplete(url)) { activity.finish() } else { - if (injectScript != null) + if (injectScript != null) { view.evaluateJavascript("(function() {$injectScript})();", null) + } } } } diff --git a/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt b/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt index 006ae249e..aaa551980 100644 --- a/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt +++ b/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt @@ -62,24 +62,27 @@ class BrowserActionActivity : AppCompatActivity() { val originalSource = if (sourceId != -1L) sourceManager.get(sourceId) else null val source = if (originalSource != null) { originalSource as? ActionCompletionVerifier - ?: run { - (originalSource as? HttpSource)?.let { - NoopActionCompletionVerifier(it) - } + ?: run { + (originalSource as? HttpSource)?.let { + NoopActionCompletionVerifier(it) } + } } else null - val headers = ((source as? HttpSource)?.headers?.toMultimap()?.mapValues { - it.value.joinToString(",") - } ?: emptyMap()) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap ?: emptyMap()) + val headers = ( + (source as? HttpSource)?.headers?.toMultimap()?.mapValues { + it.value.joinToString(",") + } ?: emptyMap() + ) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap ?: emptyMap()) val cookies: HashMap? = - intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap + intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap val script: String? = intent.getStringExtra(SCRIPT_EXTRA) val url: String? = intent.getStringExtra(URL_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!! } 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()) { // Fetch auto-solve credentials early for speed - credentialsObservable = httpClient.newCall(Request.Builder() - // Rob demo credentials - .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials") - .build()) + credentialsObservable = httpClient.newCall( + Request.Builder() + // Rob demo credentials + .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials") + .build() + ) .asObservableSuccess() .subscribeOn(Schedulers.io()) .map { @@ -176,12 +181,12 @@ class BrowserActionActivity : AppCompatActivity() { runOnUiThread { webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE, null) MaterialDialog(this) - .title(text = "Captcha solve failure") - .message(text = "Failed to auto-solve the captcha!") - .cancelable(true) - .cancelOnTouchOutside(true) - .positiveButton(android.R.string.ok) - .show() + .title(text = "Captcha solve failure") + .message(text = "Failed to auto-solve the captcha!") + .cancelable(true) + .cancelOnTouchOutside(true) + .positiveButton(android.R.string.ok) + .show() } } @@ -192,13 +197,19 @@ class BrowserActionActivity : AppCompatActivity() { when (stage) { STAGE_CHECKBOX -> { if (result!!.toBoolean()) { - webview.postDelayed({ - getAudioButtonLocation(loopId) - }, 250) + webview.postDelayed( + { + getAudioButtonLocation(loopId) + }, + 250 + ) } else { - webview.postDelayed({ - doStageCheckbox(loopId) - }, 250) + webview.postDelayed( + { + doStageCheckbox(loopId) + }, + 250 + ) } } STAGE_GET_AUDIO_BTN_LOCATION -> { @@ -216,31 +227,43 @@ class BrowserActionActivity : AppCompatActivity() { doStageDownloadAudio(loopId) } } else { - webview.postDelayed({ - getAudioButtonLocation(loopId) - }, 250) + webview.postDelayed( + { + getAudioButtonLocation(loopId) + }, + 250 + ) } } STAGE_DOWNLOAD_AUDIO -> { if (result != null) { Timber.d("Got audio URL: $result") performRecognize(result) - .observeOn(Schedulers.io()) - .subscribe({ + .observeOn(Schedulers.io()) + .subscribe( + { Timber.d("Got audio transcript: $it") webview.post { - typeResult(loopId, it!! + typeResult( + loopId, + it!! .replace(TRANSCRIPT_CLEANER_REGEX, "") .replace(SPACE_DEDUPE_REGEX, " ") - .trim()) + .trim() + ) } - }, { + }, + { captchaSolveFail() - }) + } + ) } else { - webview.postDelayed({ - doStageDownloadAudio(loopId) - }, 250) + webview.postDelayed( + { + doStageDownloadAudio(loopId) + }, + 250 + ) } } STAGE_TYPE_RESULT -> { @@ -256,27 +279,37 @@ class BrowserActionActivity : AppCompatActivity() { fun performRecognize(url: String): Single { return credentialsObservable.flatMap { token -> - httpClient.newCall(Request.Builder() + httpClient.newCall( + Request.Builder() .url(url) - .build()).asObservableSuccess().map { + .build() + ).asObservableSuccess().map { token to it } }.flatMap { (token, response) -> val audioFile = response.body!!.bytes() - httpClient.newCall(Request.Builder() - .url("https://stream.watsonplatform.net/speech-to-text/api/v1/recognize".toHttpUrlOrNull()!! + httpClient.newCall( + Request.Builder() + .url( + "https://stream.watsonplatform.net/speech-to-text/api/v1/recognize".toHttpUrlOrNull()!! .newBuilder() .addQueryParameter("watson-token", token) - .build()) - .post(MultipartBody.Builder() + .build() + ) + .post( + MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("jsonDescription", RECOGNIZE_JSON) - .addFormDataPart("audio.mp3", - "audio.mp3", - RequestBody.create("audio/mp3".toMediaTypeOrNull(), audioFile)) - .build()) - .build()).asObservableSuccess() + .addFormDataPart( + "audio.mp3", + "audio.mp3", + RequestBody.create("audio/mp3".toMediaTypeOrNull(), audioFile) + ) + .build() + ) + .build() + ).asObservableSuccess() }.map { response -> JsonParser.parseString(response.body!!.string())["results"][0]["alternatives"][0]["transcript"].string.trim() }.toSingle() @@ -285,7 +318,8 @@ class BrowserActionActivity : AppCompatActivity() { fun doStageCheckbox(loopId: String) { if (loopId != currentLoopId) return - webview.evaluateJavascript(""" + webview.evaluateJavascript( + """ (function() { $CROSS_WINDOW_SCRIPT_OUTER @@ -307,11 +341,14 @@ class BrowserActionActivity : AppCompatActivity() { exh.callback("false", '$loopId', $STAGE_CHECKBOX); } })(); - """.trimIndent().replace("\n", ""), null) + """.trimIndent().replace("\n", ""), + null + ) } fun getAudioButtonLocation(loopId: String) { - webview.evaluateJavascript(""" + webview.evaluateJavascript( + """ (function() { $CROSS_WINDOW_SCRIPT_OUTER @@ -339,11 +376,14 @@ class BrowserActionActivity : AppCompatActivity() { exh.callback(null, '$loopId', $STAGE_GET_AUDIO_BTN_LOCATION); } })(); - """.trimIndent().replace("\n", ""), null) + """.trimIndent().replace("\n", ""), + null + ) } fun doStageDownloadAudio(loopId: String) { - webview.evaluateJavascript(""" + webview.evaluateJavascript( + """ (function() { $CROSS_WINDOW_SCRIPT_OUTER @@ -364,11 +404,14 @@ class BrowserActionActivity : AppCompatActivity() { exh.callback(null, '$loopId', $STAGE_DOWNLOAD_AUDIO); } })(); - """.trimIndent().replace("\n", ""), null) + """.trimIndent().replace("\n", ""), + null + ) } fun typeResult(loopId: String, result: String) { - webview.evaluateJavascript(""" + webview.evaluateJavascript( + """ (function() { $CROSS_WINDOW_SCRIPT_OUTER @@ -392,7 +435,9 @@ class BrowserActionActivity : AppCompatActivity() { exh.callback("false", '$loopId', $STAGE_TYPE_RESULT); } })(); - """.trimIndent().replace("\n", ""), null) + """.trimIndent().replace("\n", ""), + null + ) } fun beginSolveLoop() { @@ -419,12 +464,16 @@ class BrowserActionActivity : AppCompatActivity() { } else { val savedStrictValidationStartTime = strictValidationStartTime if (savedStrictValidationStartTime != null && - System.currentTimeMillis() > savedStrictValidationStartTime) { + System.currentTimeMillis() > savedStrictValidationStartTime + ) { captchaSolveFail() } else { - webview.postDelayed({ - runValidateCaptcha(loopId) - }, 250) + webview.postDelayed( + { + runValidateCaptcha(loopId) + }, + 250 + ) } } } @@ -432,7 +481,8 @@ class BrowserActionActivity : AppCompatActivity() { fun runValidateCaptcha(loopId: String) { if (loopId != validateCurrentLoopId) return - webview.evaluateJavascript(""" + webview.evaluateJavascript( + """ (function() { $CROSS_WINDOW_SCRIPT_OUTER @@ -453,7 +503,9 @@ class BrowserActionActivity : AppCompatActivity() { exh.validateCaptchaCallback(false, '$loopId'); } })(); - """.trimIndent().replace("\n", ""), null) + """.trimIndent().replace("\n", ""), + null + ) } fun beginValidateCaptchaLoop() { @@ -502,7 +554,8 @@ class BrowserActionActivity : AppCompatActivity() { const val STAGE_DOWNLOAD_AUDIO = 2 const val STAGE_TYPE_RESULT = 3 - val CROSS_WINDOW_SCRIPT_OUTER = """ + val CROSS_WINDOW_SCRIPT_OUTER = + """ function cwmExec(element, code, cb) { console.log(">>> [CWM-Outer] Running: " + code); let runId = Math.random(); @@ -523,9 +576,10 @@ class BrowserActionActivity : AppCompatActivity() { let runRequest = { id: runId, code: code }; element.contentWindow.postMessage("exh-" + JSON.stringify(runRequest), "*"); } - """.trimIndent().replace("\n", "") + """.trimIndent().replace("\n", "") - val CROSS_WINDOW_SCRIPT_INNER = """ + val CROSS_WINDOW_SCRIPT_INNER = + """ window.addEventListener('message', function(event) { if(typeof event.data === "string" && event.data.startsWith("exh-")) { let request = JSON.parse(event.data.substring(4)); @@ -538,9 +592,10 @@ class BrowserActionActivity : AppCompatActivity() { }, false); console.log(">>> [CWM-Inner] Loaded!"); alert("exh-"); - """.trimIndent() + """.trimIndent() - val SOLVE_UI_SCRIPT_SHOW = """ + val SOLVE_UI_SCRIPT_SHOW = + """ (function() { let exh_overlay = document.createElement("div"); exh_overlay.id = "exh_overlay"; @@ -568,18 +623,20 @@ class BrowserActionActivity : AppCompatActivity() { exh_otext.textContent = "Solving captcha..." document.body.appendChild(exh_otext); })(); - """.trimIndent() + """.trimIndent() - val SOLVE_UI_SCRIPT_HIDE = """ + val SOLVE_UI_SCRIPT_HIDE = + """ (function() { let exh_overlay = document.getElementById("exh_overlay"); let exh_otext = document.getElementById("exh_otext"); if(exh_overlay != null) exh_overlay.remove(); if(exh_otext != null) exh_otext.remove(); })(); - """.trimIndent() + """.trimIndent() - val RECOGNIZE_JSON = """ + val RECOGNIZE_JSON = + """ { "part_content_type": "audio/mp3", "keywords": [], @@ -596,15 +653,15 @@ class BrowserActionActivity : AppCompatActivity() { "customGrammarWords": [], "action": "recognize" } - """.trimIndent() + """.trimIndent() val TRANSCRIPT_CLEANER_REGEX = Regex("[^0-9a-zA-Z_ -]") val SPACE_DEDUPE_REGEX = Regex(" +") private fun baseIntent(context: Context) = - Intent(context, BrowserActionActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } + Intent(context, BrowserActionActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } fun launchCaptcha( context: Context, @@ -689,8 +746,9 @@ class BrowserActionActivity : AppCompatActivity() { } } -class NoopActionCompletionVerifier(private val source: HttpSource) : DelegatedHttpSource(source), - ActionCompletionVerifier { +class NoopActionCompletionVerifier(private val source: HttpSource) : + DelegatedHttpSource(source), + ActionCompletionVerifier { override val versionId get() = source.versionId override val lang: String get() = source.lang diff --git a/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt index 9c2a5a5e7..192fa95a8 100644 --- a/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt @@ -37,43 +37,43 @@ open class HeadersInjectingWebViewClient( companion object { private val FALLBACK_REASON_PHRASES = mapOf( - 100 to "Continue", - 101 to "Switching Protocols", - 200 to "OK", - 201 to "Created", - 202 to "Accepted", - 203 to "Non-Authoritative Information", - 204 to "No Content", - 205 to "Reset Content", - 206 to "Partial Content", - 300 to "Multiple Choices", - 301 to "Moved Permanently", - 302 to "Moved Temporarily", - 303 to "See Other", - 304 to "Not Modified", - 305 to "Use Proxy", - 400 to "Bad Request", - 401 to "Unauthorized", - 402 to "Payment Required", - 403 to "Forbidden", - 404 to "Not Found", - 405 to "Method Not Allowed", - 406 to "Not Acceptable", - 407 to "Proxy Authentication Required", - 408 to "Request Time-out", - 409 to "Conflict", - 410 to "Gone", - 411 to "Length Required", - 412 to "Precondition Failed", - 413 to "Request Entity Too Large", - 414 to "Request-URI Too Large", - 415 to "Unsupported Media Type", - 500 to "Internal Server Error", - 501 to "Not Implemented", - 502 to "Bad Gateway", - 503 to "Service Unavailable", - 504 to "Gateway Time-out", - 505 to "HTTP Version not supported" + 100 to "Continue", + 101 to "Switching Protocols", + 200 to "OK", + 201 to "Created", + 202 to "Accepted", + 203 to "Non-Authoritative Information", + 204 to "No Content", + 205 to "Reset Content", + 206 to "Partial Content", + 300 to "Multiple Choices", + 301 to "Moved Permanently", + 302 to "Moved Temporarily", + 303 to "See Other", + 304 to "Not Modified", + 305 to "Use Proxy", + 400 to "Bad Request", + 401 to "Unauthorized", + 402 to "Payment Required", + 403 to "Forbidden", + 404 to "Not Found", + 405 to "Method Not Allowed", + 406 to "Not Acceptable", + 407 to "Proxy Authentication Required", + 408 to "Request Time-out", + 409 to "Conflict", + 410 to "Gone", + 411 to "Length Required", + 412 to "Precondition Failed", + 413 to "Request Entity Too Large", + 414 to "Request-URI Too Large", + 415 to "Unsupported Media Type", + 500 to "Internal Server Error", + 501 to "Not Implemented", + 502 to "Bad Gateway", + 503 to "Service Unavailable", + 504 to "Gateway Time-out", + 505 to "HTTP Version not supported" ) } } diff --git a/app/src/main/java/exh/ui/captcha/WebViewUtil.kt b/app/src/main/java/exh/ui/captcha/WebViewUtil.kt index 6f887b4ef..fa4ce3693 100644 --- a/app/src/main/java/exh/ui/captcha/WebViewUtil.kt +++ b/app/src/main/java/exh/ui/captcha/WebViewUtil.kt @@ -5,8 +5,8 @@ import okhttp3.Request fun WebResourceRequest.toOkHttpRequest(): Request { val request = Request.Builder() - .url(url.toString()) - .method(method, null) + .url(url.toString()) + .method(method, null) requestHeaders.entries.forEach { (t, u) -> request.addHeader(t, u) diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt index 0ad5011fa..7c786423e 100755 --- a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt +++ b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt @@ -54,33 +54,35 @@ class InterceptActivity : BaseRxActivity { - binding.interceptProgress.gone() - binding.interceptStatus.text = "Launching app..." - onBackPressed() - startActivity(Intent(this, MainActivity::class.java) - .setAction(MainActivity.SHORTCUT_MANGA) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - .putExtra(MangaController.MANGA_EXTRA, it.mangaId)) - } - is InterceptResult.Failure -> { - binding.interceptProgress.gone() - binding.interceptStatus.text = "Error: ${it.reason}" - MaterialDialog(this) - .title(text = "Error") - .message(text = "Could not open this gallery:\n\n${it.reason}") - .cancelable(true) - .cancelOnTouchOutside(true) - .positiveButton(android.R.string.ok) - .onCancel { onBackPressed() } - .onDismiss { onBackPressed() } - .show() - } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + when (it) { + is InterceptResult.Success -> { + binding.interceptProgress.gone() + binding.interceptStatus.text = "Launching app..." + onBackPressed() + startActivity( + Intent(this, MainActivity::class.java) + .setAction(MainActivity.SHORTCUT_MANGA) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(MangaController.MANGA_EXTRA, it.mangaId) + ) + } + is InterceptResult.Failure -> { + binding.interceptProgress.gone() + binding.interceptStatus.text = "Error: ${it.reason}" + MaterialDialog(this) + .title(text = "Error") + .message(text = "Could not open this gallery:\n\n${it.reason}") + .cancelable(true) + .cancelOnTouchOutside(true) + .positiveButton(android.R.string.ok) + .onCancel { onBackPressed() } + .onDismiss { onBackPressed() } + .show() } } + } } override fun onStop() { diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivityPresenter.kt b/app/src/main/java/exh/ui/intercept/InterceptActivityPresenter.kt index d69d0cc9d..07bc79d0e 100644 --- a/app/src/main/java/exh/ui/intercept/InterceptActivityPresenter.kt +++ b/app/src/main/java/exh/ui/intercept/InterceptActivityPresenter.kt @@ -21,12 +21,14 @@ class InterceptActivityPresenter : BasePresenter() { thread { val result = galleryAdder.addGallery(gallery) - status.onNext(when (result) { - is GalleryAddEvent.Success -> result.manga.id?.let { - InterceptResult.Success(it) - } ?: InterceptResult.Failure("Manga ID is null!") - is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage) - }) + status.onNext( + when (result) { + is GalleryAddEvent.Success -> result.manga.id?.let { + InterceptResult.Success(it) + } ?: InterceptResult.Failure("Manga ID is null!") + is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage) + } + ) } } } diff --git a/app/src/main/java/exh/ui/lock/FingerLockPreference.kt b/app/src/main/java/exh/ui/lock/FingerLockPreference.kt index 9885d0f4a..f14aef554 100644 --- a/app/src/main/java/exh/ui/lock/FingerLockPreference.kt +++ b/app/src/main/java/exh/ui/lock/FingerLockPreference.kt @@ -25,18 +25,18 @@ import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.injectLazy class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - SwitchPreferenceCompat(context, attrs) { + SwitchPreferenceCompat(context, attrs) { val prefs: PreferencesHelper by injectLazy() val fingerprintSupported get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - Reprint.isHardwarePresent() && - Reprint.hasFingerprintRegistered() + Reprint.isHardwarePresent() && + Reprint.hasFingerprintRegistered() val useFingerprint get() = fingerprintSupported && - prefs.eh_lockUseFingerprint().getOrDefault() + prefs.eh_lockUseFingerprint().getOrDefault() @SuppressLint("NewApi") override fun onAttached() { @@ -44,29 +44,32 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At if (fingerprintSupported) { updateSummary() onChange { - if (it as Boolean) + if (it as Boolean) { tryChange() - else + } else { prefs.eh_lockUseFingerprint().set(false) + } !it } } else { title = "Fingerprint unsupported" shouldDisableView = true - summary = if (!Reprint.hasFingerprintRegistered()) + summary = if (!Reprint.hasFingerprintRegistered()) { "No fingerprints enrolled!" - else + } else { "Fingerprint unlock is unsupported on this device!" + } onChange { false } } } private fun updateSummary() { isChecked = useFingerprint - title = if (isChecked) + title = if (isChecked) { "Fingerprint enabled" - else + } else { "Fingerprint disabled" + } } @TargetApi(Build.VERSION_CODES.M) @@ -74,9 +77,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At val statusTextView = TextView(context).apply { text = "Please touch the fingerprint sensor" val size = ViewGroup.LayoutParams.WRAP_CONTENT - layoutParams = (layoutParams ?: ViewGroup.LayoutParams( + layoutParams = ( + layoutParams ?: ViewGroup.LayoutParams( size, size - )).apply { + ) + ).apply { width = size height = size 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 size = dpToPx(context, 30) - layoutParams = (layoutParams ?: ViewGroup.LayoutParams( + layoutParams = ( + layoutParams ?: ViewGroup.LayoutParams( size, size - )).apply { + ) + ).apply { width = size height = size } @@ -96,9 +103,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At orientation = LinearLayoutCompat.HORIZONTAL gravity = Gravity.CENTER_VERTICAL val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT - layoutParams = (layoutParams ?: LinearLayoutCompat.LayoutParams( + layoutParams = ( + layoutParams ?: LinearLayoutCompat.LayoutParams( size, size - )).apply { + ) + ).apply { width = size height = size val pSize = dpToPx(context, 24) @@ -109,39 +118,39 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At addView(iconView) } val dialog = MaterialDialog(context) - .title(text = "Fingerprint verification") - .customView(view = linearLayout) - .negativeButton(R.string.action_cancel) - .cancelable(true) - .cancelOnTouchOutside(true) + .title(text = "Fingerprint verification") + .customView(view = linearLayout) + .negativeButton(R.string.action_cancel) + .cancelable(true) + .cancelOnTouchOutside(true) dialog.show() iconView.setState(SwirlView.State.ON) val subscription = RxReprint.authenticate() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { result -> - when (result.status) { - AuthenticationResult.Status.SUCCESS -> { - iconView.setState(SwirlView.State.ON) - prefs.eh_lockUseFingerprint().set(true) - dialog.dismiss() - updateSummary() - } - AuthenticationResult.Status.NONFATAL_FAILURE -> { - iconView.setState(SwirlView.State.ERROR) - statusTextView.text = result.errorMessage - } - AuthenticationResult.Status.FATAL_FAILURE, null -> { - MaterialDialog(context) - .title(text = "Fingerprint verification failed!") - .message(text = result.errorMessage) - .positiveButton(android.R.string.ok) - .cancelable(true) - .cancelOnTouchOutside(false) - .show() - dialog.dismiss() - } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { result -> + when (result.status) { + AuthenticationResult.Status.SUCCESS -> { + iconView.setState(SwirlView.State.ON) + prefs.eh_lockUseFingerprint().set(true) + dialog.dismiss() + updateSummary() + } + AuthenticationResult.Status.NONFATAL_FAILURE -> { + iconView.setState(SwirlView.State.ERROR) + statusTextView.text = result.errorMessage + } + AuthenticationResult.Status.FATAL_FAILURE, null -> { + MaterialDialog(context) + .title(text = "Fingerprint verification failed!") + .message(text = result.errorMessage) + .positiveButton(android.R.string.ok) + .cancelable(true) + .cancelOnTouchOutside(false) + .show() + dialog.dismiss() } } + } dialog.setOnDismissListener { subscription.unsubscribe() } diff --git a/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt b/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt index 90e410c8e..c50c5ff35 100644 --- a/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt +++ b/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt @@ -20,19 +20,21 @@ object LockActivityDelegate { private val uiScope = CoroutineScope(Dispatchers.Main) fun doLock(router: Router, animate: Boolean = false) { - router.pushController(RouterTransaction.with(LockController()) - .popChangeHandler(LockChangeHandler(animate))) + router.pushController( + RouterTransaction.with(LockController()) + .popChangeHandler(LockChangeHandler(animate)) + ) } fun onCreate(activity: FragmentActivity) { preferences.secureScreen().asFlow() - .onEach { - if (it) { - activity.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) - } else { - activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } + .onEach { + if (it) { + activity.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) + } else { + activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) } + } .launchIn(uiScope) } diff --git a/app/src/main/java/exh/ui/lock/LockChangeHandler.kt b/app/src/main/java/exh/ui/lock/LockChangeHandler.kt index e508165a6..b9c3dd5c1 100644 --- a/app/src/main/java/exh/ui/lock/LockChangeHandler.kt +++ b/app/src/main/java/exh/ui/lock/LockChangeHandler.kt @@ -35,5 +35,5 @@ class LockChangeHandler : AnimatorChangeHandler { override fun resetFromView(from: View) {} override fun copy(): ControllerChangeHandler = - LockChangeHandler(animationDuration, removesFromViewOnPush()) + LockChangeHandler(animationDuration, removesFromViewOnPush()) } diff --git a/app/src/main/java/exh/ui/lock/LockController.kt b/app/src/main/java/exh/ui/lock/LockController.kt index 6641ef5d0..4e5b62d7c 100755 --- a/app/src/main/java/exh/ui/lock/LockController.kt +++ b/app/src/main/java/exh/ui/lock/LockController.kt @@ -53,12 +53,12 @@ class LockController : NucleusController() { closeLock() } else { MaterialDialog(context) - .title(text = "PIN code incorrect") - .message(text = "The PIN code you entered is incorrect. Please try again.") - .cancelable(true) - .cancelOnTouchOutside(true) - .positiveButton(android.R.string.ok) - .show() + .title(text = "PIN code incorrect") + .message(text = "The PIN code you entered is incorrect. Please try again.") + .cancelable(true) + .cancelOnTouchOutside(true) + .positiveButton(android.R.string.ok) + .show() binding.pinLockView.resetPinLockView() } } @@ -79,9 +79,11 @@ class LockController : NucleusController() { binding.swirlContainer.removeAllViews() val icon = SwirlView(context).apply { val size = dpToPx(context, 60) - layoutParams = (layoutParams ?: ViewGroup.LayoutParams( + layoutParams = ( + layoutParams ?: ViewGroup.LayoutParams( size, size - )).apply { + ) + ).apply { width = size height = size @@ -92,29 +94,30 @@ class LockController : NucleusController() { setBackgroundColor(lockColor) val bgColor = resolvColor(android.R.attr.colorBackground) // Disable elevation if lock color is same as background color - if (lockColor == bgColor) + if (lockColor == bgColor) { this@with.swirl_container.cardElevation = 0f + } setState(SwirlView.State.OFF, true) } binding.swirlContainer.addView(icon) icon.setState(SwirlView.State.ON) RxReprint.authenticate() - .subscribeUntilDetach { - when (it.status) { - AuthenticationResult.Status.SUCCESS -> closeLock() - AuthenticationResult.Status.NONFATAL_FAILURE -> icon.setState(SwirlView.State.ERROR) - AuthenticationResult.Status.FATAL_FAILURE, null -> { - MaterialDialog(context) - .title(text = "Fingerprint error!") - .message(text = it.errorMessage) - .cancelable(false) - .cancelOnTouchOutside(false) - .positiveButton(android.R.string.ok) - .show() - icon.setState(SwirlView.State.OFF) - } + .subscribeUntilDetach { + when (it.status) { + AuthenticationResult.Status.SUCCESS -> closeLock() + AuthenticationResult.Status.NONFATAL_FAILURE -> icon.setState(SwirlView.State.ERROR) + AuthenticationResult.Status.FATAL_FAILURE, null -> { + MaterialDialog(context) + .title(text = "Fingerprint error!") + .message(text = it.errorMessage) + .cancelable(false) + .cancelOnTouchOutside(false) + .positiveButton(android.R.string.ok) + .show() + icon.setState(SwirlView.State.OFF) } } + } } else { binding.swirlContainer.visibility = View.GONE } diff --git a/app/src/main/java/exh/ui/lock/LockPreference.kt b/app/src/main/java/exh/ui/lock/LockPreference.kt index d8c299441..c527df819 100755 --- a/app/src/main/java/exh/ui/lock/LockPreference.kt +++ b/app/src/main/java/exh/ui/lock/LockPreference.kt @@ -17,7 +17,7 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - SwitchPreferenceCompat(context, attrs) { + SwitchPreferenceCompat(context, attrs) { private val secureRandom by lazy { SecureRandom() } @@ -46,28 +46,28 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut fun tryChange() { if (!notifyLockSecurity(context)) { MaterialDialog(context) - .title(text = "Lock application") - .message(text = "Enter a pin to lock the application. Enter nothing to disable the pin lock.") - // .inputRangeRes(0, 10, R.color.material_red_500) - // .inputType(InputType.TYPE_CLASS_NUMBER) - .input(maxLength = 10, inputType = InputType.TYPE_CLASS_NUMBER, allowEmpty = true) { _, c -> - val progressDialog = MaterialDialog(context) - .title(text = "Saving password") - .cancelable(false) - progressDialog.show() - Observable.fromCallable { - savePassword(c.toString()) - }.subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - progressDialog.dismiss() - updateSummary() - } - } - .negativeButton(R.string.action_cancel) - .cancelable(true) - .cancelOnTouchOutside(true) - .show() + .title(text = "Lock application") + .message(text = "Enter a pin to lock the application. Enter nothing to disable the pin lock.") + // .inputRangeRes(0, 10, R.color.material_red_500) + // .inputType(InputType.TYPE_CLASS_NUMBER) + .input(maxLength = 10, inputType = InputType.TYPE_CLASS_NUMBER, allowEmpty = true) { _, c -> + val progressDialog = MaterialDialog(context) + .title(text = "Saving password") + .cancelable(false) + progressDialog.show() + Observable.fromCallable { + savePassword(c.toString()) + }.subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + progressDialog.dismiss() + updateSummary() + } + } + .negativeButton(R.string.action_cancel) + .cancelable(true) + .cancelOnTouchOutside(true) + .show() } } diff --git a/app/src/main/java/exh/ui/lock/LockPresenter.kt b/app/src/main/java/exh/ui/lock/LockPresenter.kt index 181cb959e..cb33a8f3c 100644 --- a/app/src/main/java/exh/ui/lock/LockPresenter.kt +++ b/app/src/main/java/exh/ui/lock/LockPresenter.kt @@ -12,7 +12,7 @@ class LockPresenter : BasePresenter() { val useFingerprint get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - Reprint.isHardwarePresent() && - Reprint.hasFingerprintRegistered() && - prefs.eh_lockUseFingerprint().getOrDefault() + Reprint.isHardwarePresent() && + Reprint.hasFingerprintRegistered() && + prefs.eh_lockUseFingerprint().getOrDefault() } diff --git a/app/src/main/java/exh/ui/lock/LockUtils.kt b/app/src/main/java/exh/ui/lock/LockUtils.kt index e17f2a0e6..8710587e7 100755 --- a/app/src/main/java/exh/ui/lock/LockUtils.kt +++ b/app/src/main/java/exh/ui/lock/LockUtils.kt @@ -39,8 +39,8 @@ fun sha512(passwordToHash: String, salt: String): String { */ fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) = prefs.eh_lockHash().get() != null && - prefs.eh_lockSalt().get() != null && - prefs.eh_lockLength().getOrDefault() != -1 + prefs.eh_lockSalt().get() != null && + prefs.eh_lockLength().getOrDefault() != -1 /** * Check if the lock will function properly @@ -53,30 +53,35 @@ fun notifyLockSecurity( ): Boolean { return false if (!prefs.eh_lockManually().getOrDefault() && - !hasAccessToUsageStats(context)) { + !hasAccessToUsageStats(context) + ) { MaterialDialog(context) - .title(text = "Permission required") - .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. " + - "Press OK to grant this permission now.") - .negativeButton(R.string.action_cancel) - .positiveButton(android.R.string.ok) { - try { - context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)) - } catch (e: ActivityNotFoundException) { - XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!") - MaterialDialog(context) - .title(text = "Grant permission manually") - .message(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() } - .cancelable(true) - .cancelOnTouchOutside(false) - .show() - } + .title(text = "Permission required") + .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. " + + "Press OK to grant this permission now." + ) + .negativeButton(R.string.action_cancel) + .positiveButton(android.R.string.ok) { + try { + context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)) + } catch (e: ActivityNotFoundException) { + XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!") + MaterialDialog(context) + .title(text = "Grant permission manually") + .message( + 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() } + .cancelable(true) + .cancelOnTouchOutside(false) + .show() } - .cancelable(false) - .show() + } + .cancelable(false) + .show() return true } else { return false diff --git a/app/src/main/java/exh/ui/login/LoginController.kt b/app/src/main/java/exh/ui/login/LoginController.kt index 5bc2c6ec8..b79cec5f1 100755 --- a/app/src/main/java/exh/ui/login/LoginController.kt +++ b/app/src/main/java/exh/ui/login/LoginController.kt @@ -97,10 +97,11 @@ class LoginController : NucleusController return parsed.filter { - (it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) || - it.name.equals(PASS_HASH_COOKIE, ignoreCase = true)) && - it.value.isNotBlank() + ( + it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) || + it.name.equals(PASS_HASH_COOKIE, ignoreCase = true) + ) && + it.value.isNotBlank() }.count() >= 2 } return false @@ -168,11 +171,11 @@ class LoginController : NucleusController? = - CookieManager.getInstance().getCookie(url)?.let { - it.split("; ").flatMap { - HttpCookie.parse(it) + CookieManager.getInstance().getCookie(url)?.let { + it.split("; ").flatMap { + HttpCookie.parse(it) + } } - } companion object { const val PARAM_SKIP_INJECT = "TEH_SKIP_INJECT" @@ -181,7 +184,8 @@ class LoginController : NucleusController(), CoroutineScope { + BasePresenter(), CoroutineScope { override val coroutineContext = Job() + Dispatchers.Main diff --git a/app/src/main/java/exh/util/LoggingRealmQuery.kt b/app/src/main/java/exh/util/LoggingRealmQuery.kt index cdddbc563..d7bbf803d 100644 --- a/app/src/main/java/exh/util/LoggingRealmQuery.kt +++ b/app/src/main/java/exh/util/LoggingRealmQuery.kt @@ -14,16 +14,16 @@ import java.util.Date inline fun RealmQuery.beginLog( clazz: Class? = -E::class.java + E::class.java ): LoggingRealmQuery = LoggingRealmQuery.fromQuery(this, clazz) class LoggingRealmQuery(val query: RealmQuery) { companion object { fun fromQuery(q: RealmQuery, clazz: Class?) = - LoggingRealmQuery(q).apply { - log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE" - } + LoggingRealmQuery(q).apply { + log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE" + } } private val log = mutableListOf() @@ -47,9 +47,13 @@ class LoggingRealmQuery(val query: RealmQuery) { } private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) { - log += sec("\"$fieldName\" == \"$value\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "\"$fieldName\" == \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun equalTo(fieldName: String, value: String): RealmQuery { @@ -108,11 +112,18 @@ class LoggingRealmQuery(val query: RealmQuery) { } fun appendIn(fieldName: String, values: Array, casing: Case? = null) { - log += sec("[${values.joinToString(separator = ", ", transform = { - "\"$it\"" - })}] IN \"$fieldName\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "[${values.joinToString( + separator = ", ", + transform = { + "\"$it\"" + } + )}] IN \"$fieldName\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun `in`(fieldName: String, values: Array): RealmQuery { @@ -166,9 +177,13 @@ class LoggingRealmQuery(val query: RealmQuery) { } private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) { - log += sec("\"$fieldName\" != \"$value\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "\"$fieldName\" != \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun notEqualTo(fieldName: String, value: String): RealmQuery { @@ -372,9 +387,13 @@ class LoggingRealmQuery(val query: RealmQuery) { } private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) { - log += sec("\"$fieldName\" CONTAINS \"$value\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "\"$fieldName\" CONTAINS \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun contains(fieldName: String, value: String): RealmQuery { @@ -388,9 +407,13 @@ class LoggingRealmQuery(val query: RealmQuery) { } private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) { - log += sec("\"$fieldName\" BEGINS WITH \"$value\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "\"$fieldName\" BEGINS WITH \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun beginsWith(fieldName: String, value: String): RealmQuery { @@ -404,9 +427,13 @@ class LoggingRealmQuery(val query: RealmQuery) { } private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) { - log += sec("\"$fieldName\" ENDS WITH \"$value\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "\"$fieldName\" ENDS WITH \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun endsWith(fieldName: String, value: String): RealmQuery { @@ -420,9 +447,13 @@ class LoggingRealmQuery(val query: RealmQuery) { } private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) { - log += sec("\"$fieldName\" LIKE \"$value\"" + (casing?.let { - " CASE ${casing.name}" - } ?: "")) + log += sec( + "\"$fieldName\" LIKE \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) } fun like(fieldName: String, value: String): RealmQuery { diff --git a/app/src/main/java/exh/util/NakedTrie.kt b/app/src/main/java/exh/util/NakedTrie.kt index 570ad6abe..8313da6e6 100644 --- a/app/src/main/java/exh/util/NakedTrie.kt +++ b/app/src/main/java/exh/util/NakedTrie.kt @@ -209,10 +209,14 @@ class NakedTrie : MutableMap { override val entries: Set> get() { val out = mutableSetOf>() - node.walk("", { k, v -> - out.add(AbstractMap.SimpleImmutableEntry(k, v)) - true - }, leavesOnly) + node.walk( + "", + { k, v -> + out.add(AbstractMap.SimpleImmutableEntry(k, v)) + true + }, + leavesOnly + ) return out } /** @@ -221,10 +225,14 @@ class NakedTrie : MutableMap { override val keys: Set get() { val out = mutableSetOf() - node.walk("", { k, _ -> - out.add(k) - true - }, leavesOnly) + node.walk( + "", + { k, _ -> + out.add(k) + true + }, + leavesOnly + ) return out } @@ -243,10 +251,14 @@ class NakedTrie : MutableMap { override val values: Collection get() { val out = mutableSetOf() - node.walk("", { _, v -> - out.add(v) - true - }, leavesOnly) + node.walk( + "", + { _, v -> + out.add(v) + true + }, + leavesOnly + ) return out } @@ -264,10 +276,14 @@ class NakedTrie : MutableMap { * Returns `true` if the map maps one or more keys to the specified [value]. */ override fun containsValue(value: T): Boolean { - node.walk("", { _, v -> - if (v == value) return true - true - }, leavesOnly) + node.walk( + "", + { _, v -> + if (v == value) return true + true + }, + leavesOnly + ) return false } @@ -315,32 +331,38 @@ class NakedTrie : MutableMap { * Returns a [MutableSet] of all key/value pairs in this map. */ override val entries: MutableSet> - get() = FakeMutableSet.fromSet(mutableSetOf>().apply { - walk { k, v -> - this += FakeMutableEntry.fromPair(k, v) - true + get() = FakeMutableSet.fromSet( + mutableSetOf>().apply { + walk { k, v -> + this += FakeMutableEntry.fromPair(k, v) + true + } } - }) + ) /** * Returns a [MutableSet] of all keys in this map. */ override val keys: MutableSet - get() = FakeMutableSet.fromSet(mutableSetOf().apply { - walk { k, _ -> - this += k - true + get() = FakeMutableSet.fromSet( + mutableSetOf().apply { + walk { k, _ -> + this += k + true + } } - }) + ) /** * Returns a [MutableCollection] of all values in this map. Note that this collection may contain duplicate values. */ override val values: MutableCollection - get() = FakeMutableCollection.fromCollection(mutableListOf().apply { - walk { _, v -> - this += v - true + get() = FakeMutableCollection.fromCollection( + mutableListOf().apply { + walk { _, v -> + this += v + true + } } - }) + ) } diff --git a/app/src/main/java/exh/util/OkHttpUtil.kt b/app/src/main/java/exh/util/OkHttpUtil.kt index 7ca46c23e..714caf743 100644 --- a/app/src/main/java/exh/util/OkHttpUtil.kt +++ b/app/src/main/java/exh/util/OkHttpUtil.kt @@ -9,11 +9,12 @@ import org.jsoup.nodes.Document fun Response.interceptAsHtml(block: (Document) -> Unit): Response { val body = body if (body?.contentType()?.type == "text" && - body.contentType()?.subtype == "html") { + body.contentType()?.subtype == "html" + ) { val bodyString = body.string() val rebuiltResponse = newBuilder() - .body(ResponseBody.create(body.contentType(), bodyString)) - .build() + .body(ResponseBody.create(body.contentType(), bodyString)) + .build() try { // Search for captcha val parsed = asJsoup(html = bodyString) diff --git a/app/src/main/java/exh/util/RealmUtil.kt b/app/src/main/java/exh/util/RealmUtil.kt index 91c510579..475bea220 100644 --- a/app/src/main/java/exh/util/RealmUtil.kt +++ b/app/src/main/java/exh/util/RealmUtil.kt @@ -53,4 +53,4 @@ fun Realm.createUUIDObj(clazz: Class) = createObject(clazz, UUID.randomUUID().toString())!! inline fun Realm.createUUIDObj() = - createUUIDObj(T::class.java) + createUUIDObj(T::class.java) diff --git a/app/src/main/java/exh/util/RxUtil.kt b/app/src/main/java/exh/util/RxUtil.kt index 1028d7da1..25be43130 100644 --- a/app/src/main/java/exh/util/RxUtil.kt +++ b/app/src/main/java/exh/util/RxUtil.kt @@ -37,14 +37,18 @@ suspend fun Single.await(subscribeOn: Scheduler? = null): T { return suspendCancellableCoroutine { continuation -> val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this lateinit var sub: Subscription - sub = self.subscribe({ - continuation.resume(it) { - sub.unsubscribe() + sub = self.subscribe( + { + continuation.resume(it) { + sub.unsubscribe() + } + }, + { + if (!continuation.isCancelled) { + continuation.resumeWithException(it) + } } - }, { - if (!continuation.isCancelled) - continuation.resumeWithException(it) - }) + ) continuation.invokeOnCancellation { sub.unsubscribe() @@ -59,14 +63,18 @@ suspend fun Completable.awaitSuspending(subscribeOn: Scheduler? = null) { return suspendCancellableCoroutine { continuation -> val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this lateinit var sub: Subscription - sub = self.subscribe({ - continuation.resume(Unit) { - sub.unsubscribe() + sub = self.subscribe( + { + continuation.resume(Unit) { + sub.unsubscribe() + } + }, + { + if (!continuation.isCancelled) { + continuation.resumeWithException(it) + } } - }, { - if (!continuation.isCancelled) - continuation.resumeWithException(it) - }) + ) continuation.invokeOnCancellation { sub.unsubscribe() diff --git a/app/src/main/java/exh/util/SearchOverride.kt b/app/src/main/java/exh/util/SearchOverride.kt index 7573a4a12..ca4593729 100644 --- a/app/src/main/java/exh/util/SearchOverride.kt +++ b/app/src/main/java/exh/util/SearchOverride.kt @@ -14,15 +14,21 @@ private val galleryAdder by lazy { * A version of fetchSearchManga that supports URL importing */ fun UrlImportableSource.urlImportFetchSearchManga(query: String, fail: () -> Observable) = - when { - query.startsWith("http://") || query.startsWith("https://") -> { - Observable.fromCallable { - val res = galleryAdder.addGallery(query, false, this) - MangasPage((if (res is GalleryAddEvent.Success) - listOf(res.manga) - else - emptyList()), false) - } + when { + query.startsWith("http://") || query.startsWith("https://") -> { + Observable.fromCallable { + val res = galleryAdder.addGallery(query, false, this) + MangasPage( + ( + if (res is GalleryAddEvent.Success) { + listOf(res.manga) + } else { + emptyList() + } + ), + false + ) } - else -> fail() } + else -> fail() + } diff --git a/app/src/main/java/exh/util/SparseArrayCollection.kt b/app/src/main/java/exh/util/SparseArrayCollection.kt index bfe1acbad..10259fd29 100644 --- a/app/src/main/java/exh/util/SparseArrayCollection.kt +++ b/app/src/main/java/exh/util/SparseArrayCollection.kt @@ -65,8 +65,8 @@ class SparseArrayCollection(val sparseArray: SparseArray, var reverse: Boo var idx = index++ if (reverse) idx = sparseArray.size() - 1 - idx return AbstractMap.SimpleImmutableEntry( - sparseArray.keyAt(idx), - sparseArray.valueAt(idx) + sparseArray.keyAt(idx), + sparseArray.valueAt(idx) ) } } diff --git a/app/src/main/java/org/vepta/vdm/ByteCursor.kt b/app/src/main/java/org/vepta/vdm/ByteCursor.kt index cd18f9f51..bbcbf2b0e 100644 --- a/app/src/main/java/org/vepta/vdm/ByteCursor.kt +++ b/app/src/main/java/org/vepta/vdm/ByteCursor.kt @@ -54,30 +54,34 @@ class ByteCursor(val content: ByteArray) { } fun expect(vararg bytes: Byte) { - if (bytes.size > remaining()) + if (bytes.size > remaining()) { throw IllegalStateException("Unexpected end of content!") + } for (i in 0..bytes.lastIndex) { val expected = bytes[i] val actual = content[index + i + 1] - if (expected != actual) + if (expected != actual) { throw IllegalStateException("Unexpected content (expected: $expected, actual: $actual)!") + } } index += bytes.size } fun checkEqual(vararg bytes: Byte): Boolean { - if (bytes.size > remaining()) + if (bytes.size > remaining()) { return false + } for (i in 0..bytes.lastIndex) { val expected = bytes[i] val actual = content[index + i + 1] - if (expected != actual) + if (expected != actual) { return false + } } return true diff --git a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt index e94e4098c..070cc32e3 100644 --- a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt +++ b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt @@ -21,17 +21,17 @@ import kotlin.reflect.full.isSubclassOf class FilterSerializer { val serializers = listOf>( - // EXH --> - HelpDialogSerializer(this), - // EXH <-- - HeaderSerializer(this), - SeparatorSerializer(this), - SelectSerializer(this), - TextSerializer(this), - CheckboxSerializer(this), - TriStateSerializer(this), - GroupSerializer(this), - SortSerializer(this) + // EXH --> + HelpDialogSerializer(this), + // EXH <-- + HeaderSerializer(this), + SeparatorSerializer(this), + SelectSerializer(this), + TextSerializer(this), + CheckboxSerializer(this), + TriStateSerializer(this), + GroupSerializer(this), + SortSerializer(this) ) fun serialize(filters: FilterList) = JsonArray().apply { diff --git a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt index aa3eaa40f..faa5b79f4 100644 --- a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt +++ b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt @@ -31,9 +31,9 @@ class HelpDialogSerializer(override val serializer: FilterSerializer) : Serializ override val clazz = Filter.HelpDialog::class override fun mappings() = listOf( - Pair(NAME, Filter.HelpDialog::name), - Pair(DIALOG_TITLE, Filter.HelpDialog::dialogTitle), - Pair(MARKDOWN, Filter.HelpDialog::markdown) + Pair(NAME, Filter.HelpDialog::name), + Pair(DIALOG_TITLE, Filter.HelpDialog::dialogTitle), + Pair(MARKDOWN, Filter.HelpDialog::markdown) ) companion object { @@ -49,7 +49,7 @@ class HeaderSerializer(override val serializer: FilterSerializer) : Serializer::name), - Pair(STATE, Filter.Select::state) + Pair(NAME, Filter.Select::name), + Pair(STATE, Filter.Select::state) ) companion object { @@ -100,8 +100,8 @@ class TextSerializer(override val serializer: FilterSerializer) : Serializer) { json[STATE] = JsonArray().apply { filter.state.forEach { - add(if (it is Filter<*>) - serializer.serialize(it as Filter) - else - JsonNull.INSTANCE + add( + if (it is Filter<*>) { + serializer.serialize(it as Filter) + } else { + JsonNull.INSTANCE + } ) } } @@ -158,13 +160,14 @@ class GroupSerializer(override val serializer: FilterSerializer) : Serializer) { json[STATE].asJsonArray.forEachIndexed { index, jsonElement -> - if (!jsonElement.isJsonNull) + if (!jsonElement.isJsonNull) { serializer.deserialize(filter.state[index] as Filter, jsonElement.asJsonObject) + } } } override fun mappings() = listOf( - Pair(NAME, Filter.Group::name) + Pair(NAME, Filter.Group::name) ) companion object { @@ -195,13 +198,15 @@ class SortSerializer(override val serializer: FilterSerializer) : Serializer