mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Optimize imports, disallow wildcard imports because of klint, run linter
This commit is contained in:
		| @@ -43,7 +43,6 @@ class CategoryPutResolver : DefaultPutResolver<Category>() { | ||||
|         put(COL_FLAGS, obj.flags) | ||||
|         val orderString = obj.mangaOrder.joinToString("/") | ||||
|         put(COL_MANGA_ORDER, orderString) | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -29,6 +29,4 @@ class MangaUrlPutResolver : PutResolver<Manga>() { | ||||
|     fun mapToContentValues(manga: Manga) = ContentValues(1).apply { | ||||
|         put(MangaTable.COL_URL, manga.url) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,6 @@ object CategoryTable { | ||||
|             $COL_MANGA_ORDER TEXT NOT NULL | ||||
|             )""" | ||||
|  | ||||
|  | ||||
|     val addMangaOrder: String | ||||
|         get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT" | ||||
| } | ||||
|   | ||||
| @@ -72,4 +72,4 @@ class LibraryUpdateNotifier(private val context: Context) { | ||||
|         intent.action = MainActivity.SHORTCUT_RECENTLY_UPDATED | ||||
|         return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -45,4 +45,4 @@ class EmptyPreferenceDataStore : PreferenceDataStore() { | ||||
|  | ||||
|     override fun putStringSet(key: String?, values: Set<String>?) { | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -57,4 +57,4 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor | ||||
|  | ||||
|         return newBody.toString().toRequestBody(requestBody.contentType()) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -110,4 +110,4 @@ internal class UpdaterNotifier(private val context: Context) { | ||||
|         } | ||||
|         notificationBuilder.show(Notifications.ID_UPDATER) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -5,4 +5,4 @@ import androidx.preference.PreferenceScreen | ||||
| interface ConfigurableSource : Source { | ||||
|  | ||||
|     fun setupPreferenceScreen(screen: PreferenceScreen) | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -9,11 +9,11 @@ import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata | ||||
| import exh.metadata.metadata.base.getFlatMetadataForManga | ||||
| import exh.metadata.metadata.base.insertFlatMetadata | ||||
| import kotlin.reflect.KClass | ||||
| import rx.Completable | ||||
| import rx.Single | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| /** | ||||
|  * LEWD! | ||||
|   | ||||
| @@ -28,4 +28,4 @@ interface UrlImportableSource : Source { | ||||
|             url | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,12 @@ package eu.kanade.tachiyomi.source.online.all | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import com.elvishew.xlog.XLog | ||||
| import com.github.salomonbrys.kotson.* | ||||
| import com.github.salomonbrys.kotson.array | ||||
| import com.github.salomonbrys.kotson.get | ||||
| import com.github.salomonbrys.kotson.int | ||||
| import com.github.salomonbrys.kotson.obj | ||||
| import com.github.salomonbrys.kotson.set | ||||
| import com.github.salomonbrys.kotson.string | ||||
| import com.google.gson.JsonArray | ||||
| import com.google.gson.JsonObject | ||||
| import com.google.gson.JsonParser | ||||
| @@ -13,7 +18,12 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.network.asObservableWithAsyncStacktrace | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.Filter | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| @@ -36,23 +46,30 @@ import exh.util.UriFilter | ||||
| import exh.util.UriGroup | ||||
| import exh.util.ignore | ||||
| import exh.util.urlImportFetchSearchManga | ||||
| import java.net.URLEncoder | ||||
| import java.util.ArrayList | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import okhttp3.* | ||||
| import okhttp3.CacheControl | ||||
| import okhttp3.CookieJar | ||||
| import okhttp3.Headers | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||
| import okhttp3.Request | ||||
| import okhttp3.RequestBody | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import org.jsoup.nodes.TextNode | ||||
| import rx.Observable | ||||
| import rx.Single | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.net.URLEncoder | ||||
| import java.util.* | ||||
|  | ||||
| // TODO Consider gallery updating when doing tabbed browsing | ||||
| class EHentai(override val id: Long, | ||||
|               val exh: Boolean, | ||||
|               val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document>, UrlImportableSource { | ||||
| class EHentai( | ||||
|     override val id: Long, | ||||
|     val exh: Boolean, | ||||
|     val context: Context | ||||
| ) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document>, UrlImportableSource { | ||||
|     override val metaClass = EHentaiSearchMetadata::class | ||||
|  | ||||
|     val schema: String | ||||
| @@ -98,10 +115,10 @@ class EHentai(override val id: Long, | ||||
|                             favElement?.attr("style")?.substring(14, 17) | ||||
|                     ), | ||||
|                     manga = Manga.create(id).apply { | ||||
|                         //Get title | ||||
|                         // Get title | ||||
|                         title = thumbnailElement.attr("title") | ||||
|                         url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href")) | ||||
|                         //Get image | ||||
|                         // Get image | ||||
|                         thumbnail_url = thumbnailElement.attr("src") | ||||
|  | ||||
|                         // TODO Parse genre + uploader + tags | ||||
| @@ -110,9 +127,9 @@ class EHentai(override val id: Long, | ||||
|  | ||||
|         val parsedLocation = doc.location().toHttpUrlOrNull() | ||||
|  | ||||
|         //Add to page if required | ||||
|         val hasNextPage = if (parsedLocation == null | ||||
|                 || !parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) { | ||||
|         // Add to page if required | ||||
|         val hasNextPage = if (parsedLocation == null || | ||||
|                 !parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) { | ||||
|             select("a[onclick=return false]").last()?.let { | ||||
|                 it.text() == ">" | ||||
|             } ?: false | ||||
| @@ -212,8 +229,11 @@ class EHentai(override val id: Long, | ||||
|         } | ||||
|     }!! | ||||
|  | ||||
|     private fun fetchChapterPage(chapter: SChapter, np: String, | ||||
|                                  pastUrls: List<String> = emptyList()): Observable<List<String>> { | ||||
|     private fun fetchChapterPage( | ||||
|         chapter: SChapter, | ||||
|         np: String, | ||||
|         pastUrls: List<String> = emptyList() | ||||
|     ): Observable<List<String>> { | ||||
|         val urls = ArrayList(pastUrls) | ||||
|         return chapterPageCall(np).flatMap { | ||||
|             val jsoup = it.asJsoup() | ||||
| @@ -245,7 +265,7 @@ class EHentai(override val id: Long, | ||||
|     else | ||||
|         exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists | ||||
|  | ||||
|     //Support direct URL importing | ||||
|     // Support direct URL importing | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = | ||||
|             urlImportFetchSearchManga(query) { | ||||
|                 searchMangaRequestObservable(page, query, filters).flatMap { | ||||
| @@ -377,7 +397,7 @@ class EHentai(override val id: Long, | ||||
|  | ||||
|                 uploader = select("#gdn").text().nullIfBlank()?.trim() | ||||
|  | ||||
|                 //Parse the table | ||||
|                 // Parse the table | ||||
|                 select("#gdd tr").forEach { | ||||
|                     val left = it.select(".gdt1").text().nullIfBlank()?.trim() | ||||
|                     val rightElement = it.selectFirst(".gdt2") | ||||
| @@ -407,13 +427,13 @@ class EHentai(override val id: Long, | ||||
|                 } | ||||
|  | ||||
|                 lastUpdateCheck = System.currentTimeMillis() | ||||
|                 if (datePosted != null | ||||
|                         && lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) { | ||||
|                 if (datePosted != null && | ||||
|                         lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) { | ||||
|                     aged = true | ||||
|                     XLog.d("aged %s - too old", title) | ||||
|                 } | ||||
|  | ||||
|                 //Parse ratings | ||||
|                 // Parse ratings | ||||
|                 ignore { | ||||
|                     averageRating = select("#rating_label") | ||||
|                             .text() | ||||
| @@ -428,7 +448,7 @@ class EHentai(override val id: Long, | ||||
|                             ?.toInt() | ||||
|                 } | ||||
|  | ||||
|                 //Parse tags | ||||
|                 // Parse tags | ||||
|                 tags.clear() | ||||
|                 select("#taglist tr").forEach { | ||||
|                     val namespace = it.select(".tc").text().removeSuffix(":") | ||||
| @@ -465,7 +485,7 @@ class EHentai(override val id: Long, | ||||
|     fun realImageUrlParse(response: Response, page: Page): String { | ||||
|         with(response.asJsoup()) { | ||||
|             val currentImage = getElementById("img").attr("src") | ||||
|             //Each press of the retry button will choose another server | ||||
|             // Each press of the retry button will choose another server | ||||
|             select("#loadfail").attr("onclick").nullIfBlank()?.let { | ||||
|                 page.url = addParam(page.url, "nl", it.substring(it.indexOf('\'') + 1 until it.lastIndexOf('\''))) | ||||
|             } | ||||
| @@ -490,17 +510,17 @@ class EHentai(override val id: Long, | ||||
|                     cache = false)).execute() | ||||
|             val doc = response2.asJsoup() | ||||
|  | ||||
|             //Parse favorites | ||||
|             // Parse favorites | ||||
|             val parsed = extendedGenericMangaParse(doc) | ||||
|             result += parsed.first | ||||
|  | ||||
|             //Parse fav names | ||||
|             // Parse fav names | ||||
|             if (favNames == null) | ||||
|                 favNames = doc.select(".fp:not(.fps)").mapNotNull { | ||||
|                     it.child(2).text() | ||||
|                 } | ||||
|  | ||||
|             //Next page | ||||
|             // Next page | ||||
|             page++ | ||||
|         } while (parsed.second) | ||||
|  | ||||
| @@ -544,7 +564,7 @@ class EHentai(override val id: Long, | ||||
|  | ||||
|     fun cookiesHeader(sp: Int = spPref().getOrDefault()) = buildCookies(rawCookies(sp)) | ||||
|  | ||||
|     //Headers | ||||
|     // Headers | ||||
|     override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!! | ||||
|  | ||||
|     fun addParam(url: String, param: String, value: String) = Uri.parse(url) | ||||
| @@ -565,7 +585,7 @@ class EHentai(override val id: Long, | ||||
|                 chain.proceed(newReq) | ||||
|             }.build()!! | ||||
|  | ||||
|     //Filters | ||||
|     // Filters | ||||
|     override fun getFilterList() = FilterList( | ||||
|             Watched(), | ||||
|             GenreGroup(), | ||||
| @@ -673,11 +693,11 @@ class EHentai(override val id: Long, | ||||
|     override fun mapUrlToMangaUrl(uri: Uri): String? { | ||||
|         return when (uri.pathSegments.firstOrNull()) { | ||||
|             "g" -> { | ||||
|                 //Is already gallery page, do nothing | ||||
|                 // Is already gallery page, do nothing | ||||
|                 uri.toString() | ||||
|             } | ||||
|             "s" -> { | ||||
|                 //Is page, fetch gallery token and use that | ||||
|                 // Is page, fetch gallery token and use that | ||||
|                 getGalleryUrlFromPage(uri) | ||||
|             } | ||||
|             else -> null | ||||
| @@ -713,7 +733,6 @@ class EHentai(override val id: Long, | ||||
|         return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/" | ||||
|     } | ||||
|  | ||||
|  | ||||
|     companion object { | ||||
|         private const val QUERY_PREFIX = "?f_apply=Apply+Filter" | ||||
|         private const val TR_SUFFIX = "TR" | ||||
| @@ -738,6 +757,5 @@ class EHentai(override val id: Long, | ||||
|         fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") { | ||||
|             "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -10,7 +10,11 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| @@ -24,6 +28,8 @@ import exh.metadata.metadata.HitomiSearchMetadata.Companion.TAG_TYPE_DEFAULT | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL | ||||
| import exh.metadata.metadata.base.RaisedTag | ||||
| import exh.util.urlImportFetchSearchManga | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.Locale | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| @@ -32,8 +38,6 @@ import rx.Observable | ||||
| import rx.Single | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
|  | ||||
| /** | ||||
|  * Man, I hate this source :( | ||||
| @@ -61,8 +65,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo | ||||
|     private var tagIndexVersionCacheTime: Long = 0 | ||||
|     private fun tagIndexVersion(): Single<Long> { | ||||
|         val sCachedTagIndexVersion = cachedTagIndexVersion | ||||
|         return if (sCachedTagIndexVersion == null | ||||
|                 || tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) { | ||||
|         return if (sCachedTagIndexVersion == null || | ||||
|                 tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) { | ||||
|             HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext { | ||||
|                 cachedTagIndexVersion = it | ||||
|                 tagIndexVersionCacheTime = System.currentTimeMillis() | ||||
| @@ -76,8 +80,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo | ||||
|     private var galleryIndexVersionCacheTime: Long = 0 | ||||
|     private fun galleryIndexVersion(): Single<Long> { | ||||
|         val sCachedGalleryIndexVersion = cachedGalleryIndexVersion | ||||
|         return if (sCachedGalleryIndexVersion == null | ||||
|                 || galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) { | ||||
|         return if (sCachedGalleryIndexVersion == null || | ||||
|                 galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) { | ||||
|             HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext { | ||||
|                 cachedGalleryIndexVersion = it | ||||
|                 galleryIndexVersionCacheTime = System.currentTimeMillis() | ||||
| @@ -307,7 +311,6 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Returns an observable with the updated details for a manga. Normally it's not needed to | ||||
|      * override this method. | ||||
| @@ -423,5 +426,4 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo | ||||
|                 SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,13 +2,22 @@ package eu.kanade.tachiyomi.source.online.all | ||||
|  | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import com.github.salomonbrys.kotson.* | ||||
| import com.github.salomonbrys.kotson.get | ||||
| import com.github.salomonbrys.kotson.nullArray | ||||
| import com.github.salomonbrys.kotson.nullLong | ||||
| import com.github.salomonbrys.kotson.nullObj | ||||
| import com.github.salomonbrys.kotson.nullString | ||||
| import com.google.gson.JsonParser | ||||
| import eu.kanade.tachiyomi.BuildConfig | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.Filter | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| @@ -30,8 +39,8 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata | ||||
|     override val metaClass = NHentaiSearchMetadata::class | ||||
|  | ||||
|     override fun fetchPopularManga(page: Int): Observable<MangasPage> { | ||||
|         //TODO There is currently no way to get the most popular mangas | ||||
|         //TODO Instead, we delegate this to the latest updates thing to avoid confusing users with an empty screen | ||||
|         // TODO There is currently no way to get the most popular mangas | ||||
|         // TODO Instead, we delegate this to the latest updates thing to avoid confusing users with an empty screen | ||||
|         return fetchLatestUpdates(page) | ||||
|     } | ||||
|  | ||||
| @@ -39,7 +48,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata | ||||
|  | ||||
|     override fun popularMangaParse(response: Response) = throw UnsupportedOperationException() | ||||
|  | ||||
|     //Support direct URL importing | ||||
|     // Support direct URL importing | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { | ||||
|         val trimmedIdQuery = query.trim().removePrefix("id:") | ||||
|         val newQuery = if (trimmedIdQuery.toIntOrNull() ?: -1 >= 0) { | ||||
| @@ -246,7 +255,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata | ||||
|  | ||||
|     override fun getFilterList() = FilterList(SortFilter(), filterLang()) | ||||
|  | ||||
|     //language filtering | ||||
|     // language filtering | ||||
|     private class filterLang : Filter.Select<String>("Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray()) | ||||
|  | ||||
|     class SortFilter : Filter.Sort( | ||||
| @@ -305,7 +314,6 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata | ||||
|                 Pair("Chinese", " chinese") | ||||
|         ) | ||||
|  | ||||
|  | ||||
|         val jsonParser by lazy { | ||||
|             JsonParser() | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,12 @@ package eu.kanade.tachiyomi.source.online.all | ||||
| import android.net.Uri | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.Filter | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| @@ -17,14 +22,15 @@ import exh.metadata.metadata.base.RaisedTag | ||||
| import exh.util.UriFilter | ||||
| import exh.util.UriGroup | ||||
| import exh.util.urlImportFetchSearchManga | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.Locale | ||||
| import java.util.TimeZone | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import org.jsoup.nodes.TextNode | ||||
| import rx.Observable | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
|  | ||||
| // TODO Transform into delegated source | ||||
| class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(), | ||||
| @@ -54,7 +60,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour | ||||
|  | ||||
|     override fun popularMangaNextPageSelector(): String? = null | ||||
|  | ||||
|     //Support direct URL importing | ||||
|     // Support direct URL importing | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = | ||||
|             urlImportFetchSearchManga(query) { | ||||
|                 super.fetchSearchManga(page, query, filters) | ||||
| @@ -256,7 +262,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //Explicit type arg for listOf() to workaround this: KT-16570 | ||||
|     // Explicit type arg for listOf() to workaround this: KT-16570 | ||||
|     class ReleaseYearGroup : UriGroup<Filter<*>>("Release Year", listOf( | ||||
|             ReleaseYearRangeFilter(), | ||||
|             ReleaseYearYearFilter() | ||||
|   | ||||
| @@ -2,10 +2,14 @@ package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| import android.net.Uri | ||||
| import com.kizitonwose.time.hours | ||||
| import hu.akarnokd.rxjava.interop.RxJavaInterop | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.Filter | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| @@ -17,13 +21,18 @@ import exh.util.CachedField | ||||
| import exh.util.NakedTrie | ||||
| import exh.util.await | ||||
| import exh.util.urlImportFetchSearchManga | ||||
| import hu.akarnokd.rxjava.interop.RxJavaInterop | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.GlobalScope | ||||
| import kotlinx.coroutines.async | ||||
| import kotlinx.coroutines.rx2.asSingle | ||||
| import kotlinx.coroutines.withContext | ||||
| import okhttp3.* | ||||
| import okhttp3.Headers | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.Jsoup | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
|   | ||||
| @@ -4,12 +4,16 @@ import android.net.Uri | ||||
| import com.github.salomonbrys.kotson.array | ||||
| import com.github.salomonbrys.kotson.string | ||||
| import com.google.gson.JsonParser | ||||
| import hu.akarnokd.rxjava.interop.RxJavaInterop | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.POST | ||||
| import eu.kanade.tachiyomi.network.asObservable | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.Filter | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| @@ -23,7 +27,9 @@ import exh.search.Text | ||||
| import exh.util.await | ||||
| import exh.util.dropBlank | ||||
| import exh.util.urlImportFetchSearchManga | ||||
| import hu.akarnokd.rxjava.interop.RxJavaInterop | ||||
| import info.debatty.java.stringsimilarity.Levenshtein | ||||
| import kotlin.math.ceil | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.GlobalScope | ||||
| import kotlinx.coroutines.async | ||||
| @@ -36,7 +42,6 @@ import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import rx.Observable | ||||
| import rx.schedulers.Schedulers | ||||
| import kotlin.math.ceil | ||||
|  | ||||
| class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlImportableSource { | ||||
|     /** | ||||
| @@ -182,7 +187,6 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|  | ||||
|                     "/result" | ||||
|                 } else { | ||||
|                     "/search" | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate), | ||||
|      */ | ||||
|     override val metaClass = HentaiCafeSearchMetadata::class | ||||
|  | ||||
|     //Support direct URL importing | ||||
|     // Support direct URL importing | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = | ||||
|             urlImportFetchSearchManga(query) { | ||||
|                 super.fetchSearchManga(page, query, filters) | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate), | ||||
|      */ | ||||
|     override val metaClass = PururinSearchMetadata::class | ||||
|  | ||||
|     //Support direct URL importing | ||||
|     // Support direct URL importing | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { | ||||
|         val trimmedIdQuery = query.trim().removePrefix("id:") | ||||
|         val newQuery = if (trimmedIdQuery.toIntOrNull() ?: -1 >= 0) { | ||||
| @@ -106,4 +106,4 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate), | ||||
|     override fun mapUrlToMangaUrl(uri: Uri): String? { | ||||
|         return "${PururinSearchMetadata.BASE_URL}/gallery/${uri.pathSegments[1]}/${uri.lastPathSegment}" | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -4,32 +4,28 @@ import android.net.Uri | ||||
| import com.google.gson.JsonParser | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.LewdSource | ||||
| import eu.kanade.tachiyomi.source.online.UrlImportableSource | ||||
| import eu.kanade.tachiyomi.util.system.asJsoup | ||||
| import exh.metadata.metadata.TsuminoSearchMetadata | ||||
| import exh.metadata.metadata.TsuminoSearchMetadata.Companion.BASE_URL | ||||
| import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL | ||||
| import exh.metadata.metadata.base.RaisedTag | ||||
| import exh.source.DelegatedHttpSource | ||||
| import exh.util.dropBlank | ||||
| import exh.util.trimAll | ||||
| import exh.util.urlImportFetchSearchManga | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.Locale | ||||
| import org.jsoup.nodes.Document | ||||
| import rx.Observable | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
|  | ||||
| class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate), | ||||
|         LewdSource<TsuminoSearchMetadata, Document>, UrlImportableSource { | ||||
|     override val metaClass = TsuminoSearchMetadata::class; | ||||
|     override val metaClass = TsuminoSearchMetadata::class | ||||
|     override val lang = "en" | ||||
|  | ||||
|     //Support direct URL importing | ||||
|     // Support direct URL importing | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = | ||||
|             urlImportFetchSearchManga(query) { | ||||
|                 super.fetchSearchManga(page, query, filters) | ||||
|   | ||||
| @@ -7,4 +7,4 @@ interface TabbedController { | ||||
|     fun configureTabs(tabs: TabLayout) {} | ||||
|  | ||||
|     fun cleanupTabs(tabs: TabLayout) {} | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -8,4 +8,4 @@ abstract class BaseViewHolder(view: View) : androidx.recyclerview.widget.Recycle | ||||
|  | ||||
|     override val containerView: View? | ||||
|         get() = itemView | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -42,4 +42,4 @@ open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<Chec | ||||
|     class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { | ||||
|         val check: CheckBox = itemView.findViewById(R.id.nav_view_item) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -54,4 +54,4 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec | ||||
|         val text: TextView = itemView.findViewById(R.id.nav_view_item_text) | ||||
|         val spinner: Spinner = itemView.findViewById(R.id.nav_view_item) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package eu.kanade.tachiyomi.ui.browse.source.filter | ||||
|  | ||||
| import android.view.View | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
|   | ||||
| @@ -5,21 +5,24 @@ import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.tables.MangaTable | ||||
| import eu.kanade.tachiyomi.ui.category.CategoryAdapter | ||||
| import exh.isLewdSource | ||||
| import exh.metadata.sql.tables.SearchMetadataTable | ||||
| import exh.search.SearchEngine | ||||
| import exh.util.await | ||||
| import exh.util.cancellable | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.CancellationException | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.ensureActive | ||||
| import kotlinx.coroutines.flow.asFlow | ||||
| import kotlinx.coroutines.flow.filter | ||||
| import kotlinx.coroutines.flow.toList | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.withContext | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.davidea.flexibleadapter.SelectableAdapter | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
| import eu.kanade.tachiyomi.ui.category.CategoryAdapter | ||||
|  | ||||
| /** | ||||
|  * Adapter storing a list of manga in a certain category. | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| package eu.kanade.tachiyomi.ui.library | ||||
|  | ||||
| import android.view.View | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
| import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder | ||||
|  | ||||
| @@ -26,7 +26,6 @@ abstract class LibraryHolder( | ||||
|      */ | ||||
|     abstract fun onSetValues(item: LibraryItem) | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Called when an item is released. | ||||
|      * | ||||
| @@ -36,5 +35,4 @@ abstract class LibraryHolder( | ||||
|         super.onItemReleased(position) | ||||
|         (adapter as? LibraryCategoryAdapter)?.onItemReleaseListener?.onItemReleased(position) | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -235,4 +235,3 @@ class MangaInfoPresenter( | ||||
|         return toInsert | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -46,4 +46,4 @@ class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.Holde | ||||
|             section_text.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,17 @@ import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.preference.PreferenceKeys | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.util.preference.* | ||||
| import eu.kanade.tachiyomi.util.preference.defaultValue | ||||
| import eu.kanade.tachiyomi.util.preference.entriesRes | ||||
| import eu.kanade.tachiyomi.util.preference.intListPreference | ||||
| import eu.kanade.tachiyomi.util.preference.listPreference | ||||
| import eu.kanade.tachiyomi.util.preference.multiSelectListPreference | ||||
| import eu.kanade.tachiyomi.util.preference.onChange | ||||
| import eu.kanade.tachiyomi.util.preference.onClick | ||||
| import eu.kanade.tachiyomi.util.preference.preference | ||||
| import eu.kanade.tachiyomi.util.preference.preferenceCategory | ||||
| import eu.kanade.tachiyomi.util.preference.summaryRes | ||||
| import eu.kanade.tachiyomi.util.preference.switchPreference | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import exh.EH_SOURCE_ID | ||||
| import exh.EXH_SOURCE_ID | ||||
| @@ -34,6 +44,7 @@ import exh.ui.login.LoginController | ||||
| import exh.util.await | ||||
| import exh.util.trans | ||||
| import humanize.Humanize | ||||
| import java.util.Date | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.GlobalScope | ||||
| import kotlinx.coroutines.launch | ||||
| @@ -41,7 +52,6 @@ import kotlinx.coroutines.withContext | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.util.* | ||||
|  | ||||
| /** | ||||
|  * EH Settings fragment | ||||
| @@ -52,17 +62,17 @@ class SettingsEhController : SettingsController() { | ||||
|     private val db: DatabaseHelper by injectLazy() | ||||
|  | ||||
|     private fun Preference<*>.reconfigure(): Boolean { | ||||
|         //Listen for change commit | ||||
|         // Listen for change commit | ||||
|         asObservable() | ||||
|                 .skip(1) //Skip first as it is emitted immediately | ||||
|                 .take(1) //Only listen for first commit | ||||
|                 .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 | ||||
|                     // Only listen for first change commit | ||||
|                     WarnConfigureDialogController.uploadSettings(router) | ||||
|                 } | ||||
|  | ||||
|         //Always return true to save changes | ||||
|         // Always return true to save changes | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -23,4 +23,4 @@ fun Element.attrOrText(css: String): String { | ||||
|  */ | ||||
| fun Response.asJsoup(html: String? = null): Document { | ||||
|     return Jsoup.parse(html ?: body!!.string(), request.url.toString()) | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -16,4 +16,4 @@ fun ImageView.setVectorCompat(@DrawableRes drawable: Int, tint: Int? = null) { | ||||
|         vector?.setTint(tint) | ||||
|     } | ||||
|     setImageDrawable(vector) | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -12,4 +12,4 @@ import androidx.annotation.LayoutRes | ||||
|  */ | ||||
| fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View { | ||||
|     return LayoutInflater.from(context).inflate(layout, this, attachToRoot) | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -23,4 +23,4 @@ class IntListPreference @JvmOverloads constructor(context: Context, attrs: Attri | ||||
|             defaultReturnValue | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -53,7 +53,7 @@ private val lewdDelegatedSourceIds = SourceManager.DELEGATED_SOURCES.filter { | ||||
| }.map { it.value.sourceId }.sorted() | ||||
|  | ||||
| // This method MUST be fast! | ||||
| fun isLewdSource(source: Long) = source in 6900..6999 | ||||
|         || lewdDelegatedSourceIds.binarySearch(source) >= 0 | ||||
| fun isLewdSource(source: Long) = source in 6900..6999 || | ||||
|         lewdDelegatedSourceIds.binarySearch(source) >= 0 | ||||
|  | ||||
| fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package exh | ||||
|  | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
|  | ||||
| data class EXHSavedSearch(val name: String, | ||||
|                           val query: String, | ||||
|                           val filterList: FilterList) | ||||
| data class EXHSavedSearch( | ||||
|     val name: String, | ||||
|     val query: String, | ||||
|     val filterList: FilterList | ||||
| ) | ||||
|   | ||||
| @@ -16,20 +16,22 @@ class GalleryAdder { | ||||
|  | ||||
|     private val sourceManager: SourceManager by injectLazy() | ||||
|  | ||||
|     fun addGallery(url: String, | ||||
|                    fav: Boolean = false, | ||||
|                    forceSource: UrlImportableSource? = null, | ||||
|                    throttleFunc: () -> Unit = {}): GalleryAddEvent { | ||||
|     fun addGallery( | ||||
|         url: String, | ||||
|         fav: Boolean = false, | ||||
|         forceSource: UrlImportableSource? = null, | ||||
|         throttleFunc: () -> Unit = {} | ||||
|     ): GalleryAddEvent { | ||||
|         XLog.d("Importing gallery (url: %s, fav: %s, forceSource: %s)...", url, fav, forceSource) | ||||
|         try { | ||||
|             val uri = Uri.parse(url) | ||||
|  | ||||
|             // Find matching source | ||||
|             val source = if(forceSource != null) { | ||||
|             val source = if (forceSource != null) { | ||||
|                 try { | ||||
|                     if (forceSource.matchesUri(uri)) forceSource | ||||
|                     else return GalleryAddEvent.Fail.UnknownType(url) | ||||
|                 } catch(e: Exception) { | ||||
|                 } catch (e: Exception) { | ||||
|                     XLog.e("Source URI match check error!", e) | ||||
|                     return GalleryAddEvent.Fail.UnknownType(url) | ||||
|                 } | ||||
| @@ -39,7 +41,7 @@ class GalleryAdder { | ||||
|                         .find { | ||||
|                             try { | ||||
|                                 it.matchesUri(uri) | ||||
|                             } catch(e: Exception) { | ||||
|                             } catch (e: Exception) { | ||||
|                                 XLog.e("Source URI match check error!", e) | ||||
|                                 false | ||||
|                             } | ||||
| @@ -49,7 +51,7 @@ class GalleryAdder { | ||||
|             // Map URL to manga URL | ||||
|             val realUrl = try { | ||||
|                 source.mapUrlToMangaUrl(uri) | ||||
|             } catch(e: Exception) { | ||||
|             } catch (e: Exception) { | ||||
|                 XLog.e("Source URI map-to-manga error!", e) | ||||
|                 null | ||||
|             } ?: return GalleryAddEvent.Fail.UnknownType(url) | ||||
| @@ -57,12 +59,12 @@ class GalleryAdder { | ||||
|             // Clean URL | ||||
|             val cleanedUrl = try { | ||||
|                 source.cleanMangaUrl(realUrl) | ||||
|             } catch(e: Exception) { | ||||
|             } catch (e: Exception) { | ||||
|                 XLog.e("Source URI clean error!", e) | ||||
|                 null | ||||
|             } ?: return GalleryAddEvent.Fail.UnknownType(url) | ||||
|  | ||||
|             //Use manga in DB if possible, otherwise, make a new manga | ||||
|             // Use manga in DB if possible, otherwise, make a new manga | ||||
|             val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking() | ||||
|                     ?: Manga.create(source.id).apply { | ||||
|                 this.url = cleanedUrl | ||||
| @@ -71,7 +73,7 @@ class GalleryAdder { | ||||
|  | ||||
|             // Insert created manga if not in DB before fetching details | ||||
|             // This allows us to keep the metadata when fetching details | ||||
|             if(manga.id == null) { | ||||
|             if (manga.id == null) { | ||||
|                 db.insertManga(manga).executeAsBlocking().insertedId()?.let { | ||||
|                     manga.id = it | ||||
|                 } | ||||
| @@ -86,9 +88,9 @@ class GalleryAdder { | ||||
|  | ||||
|             db.insertManga(manga).executeAsBlocking() | ||||
|  | ||||
|             //Fetch and copy chapters | ||||
|             // Fetch and copy chapters | ||||
|             try { | ||||
|                 val chapterListObs = if(source is EHentai) { | ||||
|                 val chapterListObs = if (source is EHentai) { | ||||
|                     source.fetchChapterList(manga, throttleFunc) | ||||
|                 } else { | ||||
|                     source.fetchChapterList(manga) | ||||
| @@ -102,10 +104,10 @@ class GalleryAdder { | ||||
|             } | ||||
|  | ||||
|             return GalleryAddEvent.Success(url, manga) | ||||
|         } catch(e: Exception) { | ||||
|         } catch (e: Exception) { | ||||
|             XLog.w("Could not add gallery (url: $url)!", e) | ||||
|  | ||||
|             if(e is EHentai.GalleryNotFoundException) { | ||||
|             if (e is EHentai.GalleryNotFoundException) { | ||||
|                 return GalleryAddEvent.Fail.NotFound(url) | ||||
|             } | ||||
|  | ||||
| @@ -120,21 +122,25 @@ sealed class GalleryAddEvent { | ||||
|     abstract val galleryUrl: String | ||||
|     open val galleryTitle: String? = null | ||||
|  | ||||
|     class Success(override val galleryUrl: String, | ||||
|                   val manga: Manga): GalleryAddEvent() { | ||||
|     class Success( | ||||
|         override val galleryUrl: String, | ||||
|         val manga: Manga | ||||
|     ) : GalleryAddEvent() { | ||||
|         override val galleryTitle = manga.title | ||||
|         override val logMessage = "Added gallery: $galleryTitle" | ||||
|     } | ||||
|  | ||||
|     sealed class Fail: GalleryAddEvent() { | ||||
|         class UnknownType(override val galleryUrl: String): Fail() { | ||||
|     sealed class Fail : GalleryAddEvent() { | ||||
|         class UnknownType(override val galleryUrl: String) : Fail() { | ||||
|             override val logMessage = "Unknown gallery type for gallery: $galleryUrl" | ||||
|         } | ||||
|  | ||||
|         open class Error(override val galleryUrl: String, | ||||
|                     override val logMessage: String): Fail() | ||||
|         open class Error( | ||||
|             override val galleryUrl: String, | ||||
|             override val logMessage: String | ||||
|         ) : Fail() | ||||
|  | ||||
|         class NotFound(galleryUrl: String): | ||||
|         class NotFound(galleryUrl: String) : | ||||
|                 Error(galleryUrl, "Gallery does not exist: $galleryUrl") | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -9,20 +9,19 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.util.system.jobScheduler | ||||
| import exh.EH_SOURCE_ID | ||||
| import exh.EXH_SOURCE_ID | ||||
| import exh.EXHMigrations | ||||
| import exh.EXH_SOURCE_ID | ||||
| import exh.eh.EHentaiUpdateWorker | ||||
| import exh.metadata.metadata.EHentaiSearchMetadata | ||||
| import exh.metadata.metadata.base.getFlatMetadataForManga | ||||
| import exh.metadata.metadata.base.insertFlatMetadata | ||||
| import exh.util.await | ||||
| import exh.util.cancellable | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.flow.asFlow | ||||
| import kotlinx.coroutines.flow.mapNotNull | ||||
| import kotlinx.coroutines.flow.toList | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| object DebugFunctions { | ||||
|     val app: Application by injectLazy() | ||||
| @@ -31,7 +30,7 @@ object DebugFunctions { | ||||
|     val sourceManager: SourceManager by injectLazy() | ||||
|  | ||||
|     fun forceUpgradeMigration() { | ||||
| 	prefs.eh_lastVersionCode().set(0) | ||||
|     prefs.eh_lastVersionCode().set(0) | ||||
|         EXHMigrations.upgrade(prefs) | ||||
|     } | ||||
|  | ||||
| @@ -47,7 +46,7 @@ object DebugFunctions { | ||||
|  | ||||
|             for (manga in allManga) { | ||||
|                 val meta = db.getFlatMetadataForManga(manga.id!!).await()?.raise<EHentaiSearchMetadata>() | ||||
|                 if(meta != null) { | ||||
|                 if (meta != null) { | ||||
|                     // remove age flag | ||||
|                     meta.aged = false | ||||
|                     db.insertFlatMetadata(meta.flatten()).await() | ||||
|   | ||||
| @@ -26,4 +26,4 @@ enum class DebugToggles(val default: Boolean) { | ||||
|     companion object { | ||||
|         private val prefs: PreferencesHelper by injectLazy() | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -8,8 +8,12 @@ import android.widget.HorizontalScrollView | ||||
| import android.widget.TextView | ||||
| import androidx.preference.PreferenceScreen | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| import eu.kanade.tachiyomi.ui.setting.* | ||||
| import eu.kanade.tachiyomi.util.preference.* | ||||
| import eu.kanade.tachiyomi.ui.setting.SettingsController | ||||
| import eu.kanade.tachiyomi.util.preference.defaultValue | ||||
| import eu.kanade.tachiyomi.util.preference.onClick | ||||
| import eu.kanade.tachiyomi.util.preference.preference | ||||
| import eu.kanade.tachiyomi.util.preference.preferenceCategory | ||||
| import eu.kanade.tachiyomi.util.preference.switchPreference | ||||
| import kotlin.reflect.KVisibility | ||||
| import kotlin.reflect.full.declaredFunctions | ||||
|  | ||||
| @@ -41,7 +45,7 @@ class SettingsDebugController : SettingsController() { | ||||
|                             view.text = "Function returned result:\n\n$result" | ||||
|                             MaterialDialog.Builder(context) | ||||
|                                     .customView(hView, true) | ||||
|                         } catch(t: Throwable) { | ||||
|                         } catch (t: Throwable) { | ||||
|                             view.text = "Function threw exception:\n\n${Log.getStackTraceString(t)}" | ||||
|                             MaterialDialog.Builder(context) | ||||
|                                     .customView(hView, true) | ||||
| @@ -59,8 +63,8 @@ class SettingsDebugController : SettingsController() { | ||||
|                     title = it.name.replace('_', ' ').toLowerCase().capitalize() | ||||
|                     key = it.prefKey | ||||
|                     defaultValue = it.default | ||||
|                     summaryOn = if(it.default) "" else MODIFIED_TEXT | ||||
|                     summaryOff = if(it.default) MODIFIED_TEXT else "" | ||||
|                     summaryOn = if (it.default) "" else MODIFIED_TEXT | ||||
|                     summaryOff = if (it.default) MODIFIED_TEXT else "" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -74,4 +78,4 @@ class SettingsDebugController : SettingsController() { | ||||
|     companion object { | ||||
|         private val MODIFIED_TEXT = Html.fromHtml("<font color='red'>MODIFIED</font>") | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,19 +1,21 @@ | ||||
| package exh.eh | ||||
|  | ||||
| class EHentaiThrottleManager(private val max: Int = THROTTLE_MAX, | ||||
|                              private val inc: Int = THROTTLE_INC) { | ||||
| class EHentaiThrottleManager( | ||||
|     private val max: Int = THROTTLE_MAX, | ||||
|     private val inc: Int = THROTTLE_INC | ||||
| ) { | ||||
|     private var lastThrottleTime: Long = 0 | ||||
|     var throttleTime: Long = 0 | ||||
|         private set | ||||
|  | ||||
|     fun throttle() { | ||||
|         //Throttle requests if necessary | ||||
|         // 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() | ||||
| @@ -28,4 +30,4 @@ class EHentaiThrottleManager(private val max: Int = THROTTLE_MAX, | ||||
|         const val THROTTLE_MAX = 5500 | ||||
|         const val THROTTLE_INC = 20 | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -8,10 +8,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaCategory | ||||
| import exh.metadata.metadata.EHentaiSearchMetadata | ||||
| import exh.metadata.metadata.base.getFlatMetadataForManga | ||||
| import java.io.File | ||||
| import rx.Observable | ||||
| import rx.Single | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.io.File | ||||
|  | ||||
| data class ChapterChain(val manga: Manga, val chapters: List<Chapter>) | ||||
|  | ||||
| @@ -61,7 +61,7 @@ class EHentaiUpdateHelper(context: Context) { | ||||
|  | ||||
|             val chainsAsChapters = chains.flatMap { it.chapters } | ||||
|  | ||||
|             if(toDiscard.isNotEmpty()) { | ||||
|             if (toDiscard.isNotEmpty()) { | ||||
|                 var new = false | ||||
|  | ||||
|                 // Copy chain chapters to curChapters | ||||
| @@ -75,9 +75,9 @@ class EHentaiUpdateHelper(context: Context) { | ||||
|  | ||||
|                             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) { | ||||
|                                 if (chapter.date_upload <= 0 && | ||||
|                                         meta?.datePosted != null && | ||||
|                                         meta?.title != null) { | ||||
|                                     chapter.name = meta!!.title!! | ||||
|                                     chapter.date_upload = meta!!.datePosted!! | ||||
|                                 } | ||||
| @@ -92,7 +92,7 @@ class EHentaiUpdateHelper(context: Context) { | ||||
|                             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) { | ||||
|                                 if (newLastPageRead != null && existing.last_page_read <= 0) { | ||||
|                                     existing.last_page_read = newLastPageRead | ||||
|                                 } | ||||
|                                 existing.bookmark = existing.bookmark || chapter.bookmark | ||||
| @@ -107,7 +107,7 @@ class EHentaiUpdateHelper(context: Context) { | ||||
|                                     bookmark = chapter.bookmark | ||||
|  | ||||
|                                     last_page_read = chapter.last_page_read | ||||
|                                     if(newLastPageRead != null && last_page_read <= 0) { | ||||
|                                     if (newLastPageRead != null && last_page_read <= 0) { | ||||
|                                         last_page_read = newLastPageRead | ||||
|                                     } | ||||
|  | ||||
| @@ -153,7 +153,7 @@ class EHentaiUpdateHelper(context: Context) { | ||||
| } | ||||
|  | ||||
| data class GalleryEntry(val gId: String, val gToken: String) { | ||||
|     class Serializer: MemAutoFlushingLookupTable.EntrySerializer<GalleryEntry> { | ||||
|     class Serializer : MemAutoFlushingLookupTable.EntrySerializer<GalleryEntry> { | ||||
|         /** | ||||
|          * Serialize an entry as a String. | ||||
|          */ | ||||
|   | ||||
| @@ -20,8 +20,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.online.all.EHentai | ||||
| import eu.kanade.tachiyomi.util.system.jobScheduler | ||||
| import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource | ||||
| import eu.kanade.tachiyomi.util.system.jobScheduler | ||||
| import exh.EH_SOURCE_ID | ||||
| import exh.EXH_SOURCE_ID | ||||
| import exh.debug.DebugToggles | ||||
| @@ -31,18 +31,23 @@ import exh.metadata.metadata.base.getFlatMetadataForManga | ||||
| import exh.metadata.metadata.base.insertFlatMetadata | ||||
| import exh.util.await | ||||
| import exh.util.cancellable | ||||
| import kotlinx.coroutines.* | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.cancelAndJoin | ||||
| import kotlinx.coroutines.flow.asFlow | ||||
| import kotlinx.coroutines.flow.mapNotNull | ||||
| import kotlinx.coroutines.flow.toList | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import kotlin.coroutines.CoroutineContext | ||||
|  | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
| class EHentaiUpdateWorker : JobService(), CoroutineScope { | ||||
|     override val coroutineContext: CoroutineContext | ||||
|         get() = Dispatchers.Default + Job() | ||||
|  | ||||
| @@ -215,8 +220,8 @@ class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
|                 val (acceptedRoot, discardedRoots, hasNew) = | ||||
|                         updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await() | ||||
|  | ||||
|                 if((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) | ||||
|                         || (hasNew && updatedManga.none { it.id == acceptedRoot.manga.id })) { | ||||
|                 if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) || | ||||
|                         (hasNew && updatedManga.none { it.id == acceptedRoot.manga.id })) { | ||||
|                     updatedManga += acceptedRoot.manga | ||||
|                 } | ||||
|  | ||||
| @@ -235,7 +240,7 @@ class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
|                     ) | ||||
|             ) | ||||
|  | ||||
|             if(updatedManga.isNotEmpty()) { | ||||
|             if (updatedManga.isNotEmpty()) { | ||||
|                 updateNotifier.showResultNotification(updatedManga) | ||||
|             } | ||||
|         } | ||||
| @@ -254,10 +259,10 @@ class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
|             val newChapters = source.fetchChapterList(manga).toSingle().await(Schedulers.io()) | ||||
|             val (new, _) = syncChaptersWithSource(db, newChapters, manga, source) // Not suspending, but does block, maybe fix this? | ||||
|             return new to db.getChapters(manga).await() | ||||
|         } catch(t: Throwable) { | ||||
|             if(t is EHentai.GalleryNotFoundException) { | ||||
|         } catch (t: Throwable) { | ||||
|             if (t is EHentai.GalleryNotFoundException) { | ||||
|                 val meta = db.getFlatMetadataForManga(manga.id!!).await()?.raise<EHentaiSearchMetadata>() | ||||
|                 if(meta != null) { | ||||
|                 if (meta != null) { | ||||
|                     // Age dead galleries | ||||
|                     logger.d("Aged %s - notfound", manga.id) | ||||
|                     meta.aged = true | ||||
| @@ -286,18 +291,20 @@ class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
|  | ||||
|         private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder { | ||||
|             return JobInfo.Builder( | ||||
|                     if(isTest) JOB_ID_UPDATE_BACKGROUND_TEST | ||||
|                     if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST | ||||
|                     else JOB_ID_UPDATE_BACKGROUND, componentName()) | ||||
|         } | ||||
|  | ||||
|         private fun Context.periodicBackgroundJobInfo(period: Long, | ||||
|                                                       requireCharging: Boolean, | ||||
|                                                       requireUnmetered: Boolean): JobInfo { | ||||
|         private fun Context.periodicBackgroundJobInfo( | ||||
|             period: Long, | ||||
|             requireCharging: Boolean, | ||||
|             requireUnmetered: Boolean | ||||
|         ): JobInfo { | ||||
|             return baseBackgroundJobInfo(false) | ||||
|                     .setPeriodic(period) | ||||
|                     .setPersisted(true) | ||||
|                     .setRequiredNetworkType( | ||||
|                             if(requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED | ||||
|                             if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED | ||||
|                             else JobInfo.NETWORK_TYPE_ANY) | ||||
|                     .apply { | ||||
|                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
| @@ -321,7 +328,7 @@ class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
|  | ||||
|         fun launchBackgroundTest(context: Context) { | ||||
|             val jobScheduler = context.jobScheduler | ||||
|             if(jobScheduler.schedule(context.testBackgroundJobInfo()) == JobScheduler.RESULT_FAILURE) { | ||||
|             if (jobScheduler.schedule(context.testBackgroundJobInfo()) == JobScheduler.RESULT_FAILURE) { | ||||
|                 logger.e("Failed to schedule background test job!") | ||||
|             } else { | ||||
|                 logger.d("Successfully scheduled background test job!") | ||||
| @@ -344,7 +351,7 @@ class EHentaiUpdateWorker: JobService(), CoroutineScope { | ||||
|                         wifiRestriction | ||||
|                 ) | ||||
|  | ||||
|                 if(context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) { | ||||
|                 if (context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) { | ||||
|                     logger.e("Failed to schedule background update job!") | ||||
|                 } else { | ||||
|                     logger.d("Successfully scheduled background update job!") | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package exh.eh | ||||
|  | ||||
| data class EHentaiUpdaterStats( | ||||
|         val startTime: Long, | ||||
|         val possibleUpdates: Int, | ||||
|         val updateCount: Int | ||||
| ) | ||||
|     val startTime: Long, | ||||
|     val possibleUpdates: Int, | ||||
|     val updateCount: Int | ||||
| ) | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| package exh.eh | ||||
|  | ||||
| class GalleryNotUpdatedException(val network: Boolean, cause: Throwable): RuntimeException(cause) | ||||
| class GalleryNotUpdatedException(val network: Boolean, cause: Throwable) : RuntimeException(cause) | ||||
|   | ||||
| @@ -3,9 +3,6 @@ package exh.eh | ||||
| import android.util.SparseArray | ||||
| import androidx.core.util.AtomicFile | ||||
| import com.elvishew.xlog.XLog | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
| import java.io.Closeable | ||||
| import java.io.File | ||||
| import java.io.FileNotFoundException | ||||
| @@ -13,6 +10,18 @@ import java.io.InputStream | ||||
| import java.nio.ByteBuffer | ||||
| import kotlin.concurrent.thread | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.NonCancellable | ||||
| import kotlinx.coroutines.SupervisorJob | ||||
| import kotlinx.coroutines.cancelAndJoin | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
| import kotlinx.coroutines.withContext | ||||
|  | ||||
| /** | ||||
|  * In memory Int -> Obj lookup table implementation that | ||||
| @@ -23,9 +32,9 @@ import kotlin.coroutines.CoroutineContext | ||||
|  * @author nulldev | ||||
|  */ | ||||
| class MemAutoFlushingLookupTable<T>( | ||||
|         file: File, | ||||
|         private val serializer: EntrySerializer<T>, | ||||
|         private val debounceTimeMs: Long = 3000 | ||||
|     file: File, | ||||
|     private val serializer: EntrySerializer<T>, | ||||
|     private val debounceTimeMs: Long = 3000 | ||||
| ) : CoroutineScope, Closeable { | ||||
|     /** | ||||
|      * The context of this scope. | ||||
| @@ -49,7 +58,7 @@ class MemAutoFlushingLookupTable<T>( | ||||
|     private val atomicFile = AtomicFile(file) | ||||
|  | ||||
|     private val shutdownHook = thread(start = false) { | ||||
|         if(!flushed) writeSynchronously() | ||||
|         if (!flushed) writeSynchronously() | ||||
|     } | ||||
|  | ||||
|     init { | ||||
| @@ -62,9 +71,9 @@ class MemAutoFlushingLookupTable<T>( | ||||
|         var readIter = 0 | ||||
|         while (true) { | ||||
|             val readThisIter = read(targetArray, readIter, byteCount - readIter) | ||||
|             if(readThisIter <= 0) return false // No more data to read | ||||
|             if (readThisIter <= 0) return false // No more data to read | ||||
|             readIter += readThisIter | ||||
|             if(readIter == byteCount) return true | ||||
|             if (readIter == byteCount) return true | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -74,16 +83,16 @@ class MemAutoFlushingLookupTable<T>( | ||||
|                 atomicFile.openRead().buffered().use { input -> | ||||
|                     val bb = ByteBuffer.allocate(8) | ||||
|  | ||||
|                     while(true) { | ||||
|                         if(!input.requireBytes(bb.array(), 8)) break | ||||
|                     while (true) { | ||||
|                         if (!input.requireBytes(bb.array(), 8)) break | ||||
|                         val k = bb.getInt(0) | ||||
|                         val size = bb.getInt(4) | ||||
|                         val strBArr = ByteArray(size) | ||||
|                         if(!input.requireBytes(strBArr, size)) break | ||||
|                         if (!input.requireBytes(strBArr, size)) break | ||||
|                         table.put(k, serializer.read(strBArr.toString(Charsets.UTF_8))) | ||||
|                     } | ||||
|                 } | ||||
|             } catch(e: FileNotFoundException) { | ||||
|             } catch (e: FileNotFoundException) { | ||||
|                 XLog.d("Lookup table not found!", e) | ||||
|                 // Ignored | ||||
|             } | ||||
| @@ -97,11 +106,11 @@ class MemAutoFlushingLookupTable<T>( | ||||
|         flushed = false | ||||
|         launch { | ||||
|             delay(debounceTimeMs) | ||||
|             if(id != writeCounter) return@launch | ||||
|             if (id != writeCounter) return@launch | ||||
|  | ||||
|             mutex.withLock { | ||||
|                 // Second check inside of mutex to prevent dupe writes | ||||
|                 if(id != writeCounter) return@launch | ||||
|                 if (id != writeCounter) return@launch | ||||
|                 withContext(NonCancellable) { | ||||
|                     writeSynchronously() | ||||
|  | ||||
| @@ -118,7 +127,7 @@ class MemAutoFlushingLookupTable<T>( | ||||
|         val fos = atomicFile.startWrite() | ||||
|         try { | ||||
|             val out = fos.buffered() | ||||
|             for(i in 0 until table.size()) { | ||||
|             for (i in 0 until table.size()) { | ||||
|                 val k = table.keyAt(i) | ||||
|                 val v = serializer.write(table.valueAt(i)).toByteArray(Charsets.UTF_8) | ||||
|                 bb.putInt(0, k) | ||||
| @@ -128,7 +137,7 @@ class MemAutoFlushingLookupTable<T>( | ||||
|             } | ||||
|             out.flush() | ||||
|             atomicFile.finishWrite(fos) | ||||
|         } catch(t: Throwable) { | ||||
|         } catch (t: Throwable) { | ||||
|             atomicFile.failWrite(fos) | ||||
|             throw t | ||||
|         } | ||||
| @@ -212,4 +221,4 @@ class MemAutoFlushingLookupTable<T>( | ||||
|         private const val INITIAL_SIZE = 1000 | ||||
|         private const val ENTRY_SIZE_BYTES = 8 | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import io.realm.RealmObject | ||||
| import io.realm.annotations.Index | ||||
| import io.realm.annotations.PrimaryKey | ||||
| import io.realm.annotations.RealmClass | ||||
| import java.util.* | ||||
| import java.util.UUID | ||||
|  | ||||
| @RealmClass | ||||
| open class FavoriteEntry : RealmObject() { | ||||
|   | ||||
| @@ -17,18 +17,21 @@ import eu.kanade.tachiyomi.util.lang.launchUI | ||||
| import eu.kanade.tachiyomi.util.system.powerManager | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.util.system.wifiManager | ||||
| import exh.* | ||||
| import exh.EH_SOURCE_ID | ||||
| import exh.EXH_SOURCE_ID | ||||
| import exh.GalleryAddEvent | ||||
| import exh.GalleryAdder | ||||
| import exh.eh.EHentaiThrottleManager | ||||
| import exh.eh.EHentaiUpdateWorker | ||||
| import exh.util.ignore | ||||
| import exh.util.trans | ||||
| import kotlin.concurrent.thread | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.Request | ||||
| import rx.subjects.BehaviorSubject | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| class FavoritesSyncHelper(val context: Context) { | ||||
|     private val db: DatabaseHelper by injectLazy() | ||||
| @@ -55,7 +58,7 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|  | ||||
|     @Synchronized | ||||
|     fun runSync() { | ||||
|         if(status.value !is FavoritesSyncStatus.Idle) { | ||||
|         if (status.value !is FavoritesSyncStatus.Idle) { | ||||
|             return | ||||
|         } | ||||
|  | ||||
| @@ -65,8 +68,8 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|     } | ||||
|  | ||||
|     private fun beginSync() { | ||||
|         //Check if logged in | ||||
|         if(!prefs.enableExhentai().getOrDefault()) { | ||||
|         // Check if logged in | ||||
|         if (!prefs.enableExhentai().getOrDefault()) { | ||||
|             status.onNext(FavoritesSyncStatus.Error("Please log in!")) | ||||
|             return | ||||
|         } | ||||
| @@ -76,9 +79,9 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|         val libraryManga = db.getLibraryMangas().executeAsBlocking() | ||||
|         val seenManga = HashSet<Long>(libraryManga.size) | ||||
|         libraryManga.forEach { | ||||
|             if(it.source != EXH_SOURCE_ID && it.source != EH_SOURCE_ID) return@forEach | ||||
|             if (it.source != EXH_SOURCE_ID && it.source != EH_SOURCE_ID) return@forEach | ||||
|  | ||||
|             if(it.id in seenManga) { | ||||
|             if (it.id in seenManga) { | ||||
|                 val inCategories = db.getCategoriesForManga(it).executeAsBlocking() | ||||
|                 status.onNext(FavoritesSyncStatus.BadLibraryState | ||||
|                         .MangaInMultipleCategories(it, inCategories)) | ||||
| @@ -89,20 +92,20 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Download remote favorites | ||||
|         // Download remote favorites | ||||
|         val favorites = try { | ||||
|             status.onNext(FavoritesSyncStatus.Processing("Downloading favorites from remote server")) | ||||
|             exh.fetchFavorites() | ||||
|         } catch(e: Exception) { | ||||
|         } catch (e: Exception) { | ||||
|             status.onNext(FavoritesSyncStatus.Error("Failed to fetch favorites from remote server!")) | ||||
|             logger.e( "Could not fetch favorites!", e) | ||||
|             logger.e("Could not fetch favorites!", e) | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         val errorList = mutableListOf<String>() | ||||
|  | ||||
|         try { | ||||
|             //Take wake + wifi locks | ||||
|             // Take wake + wifi locks | ||||
|             ignore { wakeLock?.release() } | ||||
|             wakeLock = ignore { | ||||
|                 context.powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, | ||||
| @@ -124,20 +127,20 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                     db.inTransaction { | ||||
|                         status.onNext(FavoritesSyncStatus.Processing("Calculating remote changes")) | ||||
|                         val remoteChanges = storage.getChangedRemoteEntries(realm, favorites.first) | ||||
|                         val localChanges = if(prefs.eh_readOnlySync().getOrDefault()) { | ||||
|                             null //Do not build local changes if they are not going to be applied | ||||
|                         val localChanges = if (prefs.eh_readOnlySync().getOrDefault()) { | ||||
|                             null // Do not build local changes if they are not going to be applied | ||||
|                         } else { | ||||
|                             status.onNext(FavoritesSyncStatus.Processing("Calculating local changes")) | ||||
|                             storage.getChangedDbEntries(realm) | ||||
|                         } | ||||
|  | ||||
|                         //Apply remote categories | ||||
|                         // Apply remote categories | ||||
|                         status.onNext(FavoritesSyncStatus.Processing("Updating category names")) | ||||
|                         applyRemoteCategories(errorList, favorites.second) | ||||
|  | ||||
|                         //Apply change sets | ||||
|                         // Apply change sets | ||||
|                         applyChangeSetToLocal(errorList, remoteChanges) | ||||
|                         if(localChanges != null) | ||||
|                         if (localChanges != null) | ||||
|                             applyChangeSetToRemote(errorList, localChanges) | ||||
|  | ||||
|                         status.onNext(FavoritesSyncStatus.Processing("Cleaning up")) | ||||
| @@ -150,16 +153,16 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|             launchUI { | ||||
|                 theContext.toast("Sync complete!") | ||||
|             } | ||||
|         } catch(e: IgnoredException) { | ||||
|             //Do not display error as this error has already been reported | ||||
|             logger.w( "Ignoring exception!", e) | ||||
|         } catch (e: IgnoredException) { | ||||
|             // Do not display error as this error has already been reported | ||||
|             logger.w("Ignoring exception!", e) | ||||
|             return | ||||
|         } catch (e: Exception) { | ||||
|             status.onNext(FavoritesSyncStatus.Error("Unknown error: ${e.message}")) | ||||
|             logger.e( "Sync error!", e) | ||||
|             logger.e("Sync error!", e) | ||||
|             return | ||||
|         } finally { | ||||
|             //Release wake + wifi locks | ||||
|             // Release wake + wifi locks | ||||
|             ignore { | ||||
|                 wakeLock?.release() | ||||
|                 wakeLock = null | ||||
| @@ -175,7 +178,7 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if(errorList.isEmpty()) | ||||
|         if (errorList.isEmpty()) | ||||
|             status.onNext(FavoritesSyncStatus.Idle()) | ||||
|         else | ||||
|             status.onNext(FavoritesSyncStatus.CompleteWithErrors(errorList)) | ||||
| @@ -195,31 +198,31 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                 Category.create(remote).apply { | ||||
|                     order = index | ||||
|  | ||||
|                     //Going through categories list from front to back | ||||
|                     //If category does not exist, list size <= category index | ||||
|                     //Thus, we can just add it here and not worry about indexing | ||||
|                     // Going through categories list from front to back | ||||
|                     // If category does not exist, list size <= category index | ||||
|                     // Thus, we can just add it here and not worry about indexing | ||||
|                     newLocalCategories += this | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if(local.name != remote) { | ||||
|             if (local.name != remote) { | ||||
|                 changed = true | ||||
|  | ||||
|                 local.name = remote | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Ensure consistent ordering | ||||
|         // Ensure consistent ordering | ||||
|         newLocalCategories.forEachIndexed { index, category -> | ||||
|             if(category.order != index) { | ||||
|             if (category.order != index) { | ||||
|                 changed = true | ||||
|  | ||||
|                 category.order = index | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Only insert categories if changed | ||||
|         if(changed) | ||||
|         // Only insert categories if changed | ||||
|         if (changed) | ||||
|             db.insertCategories(newLocalCategories).executeAsBlocking() | ||||
|     } | ||||
|  | ||||
| @@ -236,10 +239,10 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                         .build()) | ||||
|                 .build() | ||||
|  | ||||
|         if(!explicitlyRetryExhRequest(10, request)) { | ||||
|             val errorString =  "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!" | ||||
|         if (!explicitlyRetryExhRequest(10, request)) { | ||||
|             val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!" | ||||
|  | ||||
|             if(prefs.eh_lenientSync().getOrDefault()) { | ||||
|             if (prefs.eh_lenientSync().getOrDefault()) { | ||||
|                 errorList += errorString | ||||
|             } else { | ||||
|                 status.onNext(FavoritesSyncStatus.Error(errorString)) | ||||
| @@ -251,7 +254,7 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|     private fun explicitlyRetryExhRequest(retryCount: Int, request: Request): Boolean { | ||||
|         var success = false | ||||
|  | ||||
|         for(i in 1 .. retryCount) { | ||||
|         for (i in 1..retryCount) { | ||||
|             try { | ||||
|                 val resp = exh.client.newCall(request).execute() | ||||
|  | ||||
| @@ -260,7 +263,7 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                     break | ||||
|                 } | ||||
|             } catch (e: Exception) { | ||||
|                 logger.w( "Sync network error!", e) | ||||
|                 logger.w("Sync network error!", e) | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -268,15 +271,15 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|     } | ||||
|  | ||||
|     private fun applyChangeSetToRemote(errorList: MutableList<String>, changeSet: ChangeSet) { | ||||
|         //Apply removals | ||||
|         if(changeSet.removed.isNotEmpty()) { | ||||
|         // Apply removals | ||||
|         if (changeSet.removed.isNotEmpty()) { | ||||
|             status.onNext(FavoritesSyncStatus.Processing("Removing ${changeSet.removed.size} galleries from remote server")) | ||||
|  | ||||
|             val formBody = FormBody.Builder() | ||||
|                     .add("ddact", "delete") | ||||
|                     .add("apply", "Apply") | ||||
|  | ||||
|             //Add change set to form | ||||
|             // Add change set to form | ||||
|             changeSet.removed.forEach { | ||||
|                 formBody.add("modifygids[]", it.gid) | ||||
|             } | ||||
| @@ -286,10 +289,10 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                     .post(formBody.build()) | ||||
|                     .build() | ||||
|  | ||||
|             if(!explicitlyRetryExhRequest(10, request)) { | ||||
|             if (!explicitlyRetryExhRequest(10, request)) { | ||||
|                 val errorString = "Unable to delete galleries from the remote servers!" | ||||
|  | ||||
|                 if(prefs.eh_lenientSync().getOrDefault()) { | ||||
|                 if (prefs.eh_lenientSync().getOrDefault()) { | ||||
|                     errorList += errorString | ||||
|                 } else { | ||||
|                     status.onNext(FavoritesSyncStatus.Error(errorString)) | ||||
| @@ -298,7 +301,7 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Apply additions | ||||
|         // Apply additions | ||||
|         throttleManager.resetThrottle() | ||||
|         changeSet.added.forEachIndexed { index, it -> | ||||
|             status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to remote server", | ||||
| @@ -313,17 +316,17 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|     private fun applyChangeSetToLocal(errorList: MutableList<String>, changeSet: ChangeSet) { | ||||
|         val removedManga = mutableListOf<Manga>() | ||||
|  | ||||
|         //Apply removals | ||||
|         // Apply removals | ||||
|         changeSet.removed.forEachIndexed { index, it -> | ||||
|             status.onNext(FavoritesSyncStatus.Processing("Removing gallery ${index + 1} of ${changeSet.removed.size} from local library")) | ||||
|             val url = it.getUrl() | ||||
|  | ||||
|             //Consider both EX and EH sources | ||||
|             // Consider both EX and EH sources | ||||
|             listOf(db.getManga(url, EXH_SOURCE_ID), | ||||
|                     db.getManga(url, EH_SOURCE_ID)).forEach { | ||||
|                 val manga = it.executeAsBlocking() | ||||
|  | ||||
|                 if(manga?.favorite == true) { | ||||
|                 if (manga?.favorite == true) { | ||||
|                     manga.favorite = false | ||||
|                     db.updateMangaFavorite(manga).executeAsBlocking() | ||||
|                     removedManga += manga | ||||
| @@ -339,7 +342,7 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|         val insertedMangaCategories = mutableListOf<Pair<MangaCategory, Manga>>() | ||||
|         val categories = db.getCategories().executeAsBlocking() | ||||
|  | ||||
|         //Apply additions | ||||
|         // Apply additions | ||||
|         throttleManager.resetThrottle() | ||||
|         changeSet.added.forEachIndexed { index, it -> | ||||
|             status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to local library", | ||||
| @@ -347,14 +350,14 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|  | ||||
|             throttleManager.throttle() | ||||
|  | ||||
|             //Import using gallery adder | ||||
|             // Import using gallery adder | ||||
|             val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}", | ||||
|                     true, | ||||
|                     exh, | ||||
|                     throttleManager::throttle) | ||||
|  | ||||
|             if(result is GalleryAddEvent.Fail) { | ||||
|                 if(result is GalleryAddEvent.Fail.NotFound) { | ||||
|             if (result is GalleryAddEvent.Fail) { | ||||
|                 if (result is GalleryAddEvent.Fail.NotFound) { | ||||
|                     XLog.e("Remote gallery does not exist, skipping: %s!", it.getUrl()) | ||||
|                     // Skip this gallery, it no longer exists | ||||
|                     return@forEachIndexed | ||||
| @@ -365,13 +368,13 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                     is GalleryAddEvent.Fail.UnknownType -> "'${it.title}' (${result.galleryUrl}) is not a valid gallery!" | ||||
|                 } | ||||
|  | ||||
|                 if(prefs.eh_lenientSync().getOrDefault()) { | ||||
|                 if (prefs.eh_lenientSync().getOrDefault()) { | ||||
|                     errorList += errorString | ||||
|                 } else { | ||||
|                     status.onNext(FavoritesSyncStatus.Error(errorString)) | ||||
|                     throw IgnoredException() | ||||
|                 } | ||||
|             } else if(result is GalleryAddEvent.Success) { | ||||
|             } else if (result is GalleryAddEvent.Success) { | ||||
|                 insertedMangaCategories += MangaCategory.create(result.manga, | ||||
|                         categories[it.category]) to result.manga | ||||
|             } | ||||
| @@ -385,8 +388,8 @@ class FavoritesSyncHelper(val context: Context) { | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun needWarnThrottle() | ||||
|             = throttleManager.throttleTime >= THROTTLE_WARN | ||||
|     fun needWarnThrottle() = | ||||
|             throttleManager.throttleTime >= THROTTLE_WARN | ||||
|  | ||||
|     class IgnoredException : RuntimeException() | ||||
|  | ||||
| @@ -399,12 +402,14 @@ sealed class FavoritesSyncStatus(val message: String) { | ||||
|     class Error(message: String) : FavoritesSyncStatus(message) | ||||
|     class Idle : FavoritesSyncStatus("Waiting for sync to start") | ||||
|     sealed class BadLibraryState(message: String) : FavoritesSyncStatus(message) { | ||||
|         class MangaInMultipleCategories(val manga: Manga, | ||||
|                                         val categories: List<Category>): | ||||
|         class MangaInMultipleCategories( | ||||
|             val manga: Manga, | ||||
|             val categories: List<Category> | ||||
|         ) : | ||||
|                 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) | ||||
|     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) | ||||
|   | ||||
| @@ -20,8 +20,8 @@ class LocalFavoritesStorage { | ||||
|  | ||||
|     fun getRealm() = Realm.getInstance(realmConfig) | ||||
|  | ||||
|     fun getChangedDbEntries(realm: Realm) | ||||
|             = getChangedEntries(realm, | ||||
|     fun getChangedDbEntries(realm: Realm) = | ||||
|             getChangedEntries(realm, | ||||
|             parseToFavoriteEntries( | ||||
|                     loadDbCategories( | ||||
|                             db.getFavoriteMangas() | ||||
| @@ -31,8 +31,8 @@ class LocalFavoritesStorage { | ||||
|             ) | ||||
|     ) | ||||
|  | ||||
|     fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) | ||||
|             = getChangedEntries(realm, | ||||
|     fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) = | ||||
|             getChangedEntries(realm, | ||||
|             parseToFavoriteEntries( | ||||
|                     entries.asSequence().map { | ||||
|                         Pair(it.fav, it.manga.apply { | ||||
| @@ -51,10 +51,10 @@ class LocalFavoritesStorage { | ||||
|                 ) | ||||
|         ) | ||||
|  | ||||
|         //Delete old snapshot | ||||
|         // Delete old snapshot | ||||
|         realm.delete(FavoriteEntry::class.java) | ||||
|  | ||||
|         //Insert new snapshots | ||||
|         // Insert new snapshots | ||||
|         realm.copyToRealm(dbMangas.toList()) | ||||
|     } | ||||
|  | ||||
| @@ -80,18 +80,18 @@ class LocalFavoritesStorage { | ||||
|         return ChangeSet(added, removed) | ||||
|     } | ||||
|  | ||||
|     private fun Realm.queryRealmForEntry(entry: FavoriteEntry) | ||||
|             = where(FavoriteEntry::class.java) | ||||
|     private fun Realm.queryRealmForEntry(entry: FavoriteEntry) = | ||||
|             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<FavoriteEntry>, entry: FavoriteEntry) | ||||
|             = list.find { | ||||
|         it.gid == entry.gid | ||||
|                 && it.token == entry.token | ||||
|                 && it.category == entry.category | ||||
|     private fun queryListForEntry(list: List<FavoriteEntry>, entry: FavoriteEntry) = | ||||
|             list.find { | ||||
|         it.gid == entry.gid && | ||||
|                 it.token == entry.token && | ||||
|                 it.category == entry.category | ||||
|     } | ||||
|  | ||||
|     private fun loadDbCategories(manga: Sequence<Manga>): Sequence<Pair<Int, Manga>> { | ||||
| @@ -105,8 +105,8 @@ class LocalFavoritesStorage { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun parseToFavoriteEntries(manga: Sequence<Pair<Int, Manga>>) | ||||
|             = manga.filter { | ||||
|     private fun parseToFavoriteEntries(manga: Sequence<Pair<Int, Manga>>) = | ||||
|             manga.filter { | ||||
|         validateDbManga(it.second) | ||||
|     }.mapNotNull { | ||||
|                 FavoriteEntry().apply { | ||||
| @@ -115,18 +115,20 @@ class LocalFavoritesStorage { | ||||
|                     token = EHentaiSearchMetadata.galleryToken(it.second.url) | ||||
|                     category = it.first | ||||
|  | ||||
|                     if(this.category > MAX_CATEGORIES) | ||||
|                     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) | ||||
|     private fun validateDbManga(manga: Manga) = | ||||
|             manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) | ||||
|  | ||||
|     companion object { | ||||
|         const val MAX_CATEGORIES = 9 | ||||
|     } | ||||
| } | ||||
|  | ||||
| data class ChangeSet(val added: List<FavoriteEntry>, | ||||
|                      val removed: List<FavoriteEntry>) | ||||
| data class ChangeSet( | ||||
|     val added: List<FavoriteEntry>, | ||||
|     val removed: List<FavoriteEntry> | ||||
| ) | ||||
|   | ||||
| @@ -4,42 +4,46 @@ import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.asObservable | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| import exh.metadata.metadata.HitomiSearchMetadata.Companion.LTN_BASE_URL | ||||
| import java.security.MessageDigest | ||||
| import okhttp3.Headers | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import org.vepta.vdm.ByteCursor | ||||
| import rx.Observable | ||||
| import rx.Single | ||||
| import java.security.MessageDigest | ||||
|  | ||||
| private typealias HashedTerm = ByteArray | ||||
|  | ||||
| private data class DataPair(val offset: Long, val length: Int) | ||||
| private data class Node(val keys: List<ByteArray>, | ||||
|                         val datas: List<DataPair>, | ||||
|                         val subnodeAddresses: List<Long>) | ||||
| private data class Node( | ||||
|     val keys: List<ByteArray>, | ||||
|     val datas: List<DataPair>, | ||||
|     val subnodeAddresses: List<Long> | ||||
| ) | ||||
|  | ||||
| /** | ||||
|  * Kotlin port of the hitomi.la search algorithm | ||||
|  * @author NerdNumber9 | ||||
|  */ | ||||
| class HitomiNozomi(private val client: OkHttpClient, | ||||
|                    private val tagIndexVersion: Long, | ||||
|                    private val galleriesIndexVersion: Long) { | ||||
| class HitomiNozomi( | ||||
|     private val client: OkHttpClient, | ||||
|     private val tagIndexVersion: Long, | ||||
|     private val galleriesIndexVersion: Long | ||||
| ) { | ||||
|     fun getGalleryIdsForQuery(query: String): Single<List<Int>> { | ||||
|         val replacedQuery = query.replace('_', ' ') | ||||
|  | ||||
|         if(':' in replacedQuery) { | ||||
|         if (':' in replacedQuery) { | ||||
|             val sides = replacedQuery.split(':') | ||||
|             val namespace = sides[0] | ||||
|             var tag = sides[1] | ||||
|  | ||||
|             var area: String? = namespace | ||||
|             var language = "all" | ||||
|             if(namespace == "female" || namespace == "male") { | ||||
|             if (namespace == "female" || namespace == "male") { | ||||
|                 area = "tag" | ||||
|                 tag = replacedQuery | ||||
|             } else if(namespace == "language") { | ||||
|             } else if (namespace == "language") { | ||||
|                 area = null | ||||
|                 language = tag | ||||
|                 tag = "index" | ||||
| @@ -52,7 +56,7 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|         val field = "galleries" | ||||
|  | ||||
|         return getNodeAtAddress(field, 0).flatMap { node -> | ||||
|             if(node == null) { | ||||
|             if (node == null) { | ||||
|                 Single.just(null) | ||||
|             } else { | ||||
|                 BSearch(field, key, node).flatMap { data -> | ||||
| @@ -67,12 +71,12 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|     } | ||||
|  | ||||
|     private fun getGalleryIdsFromData(data: DataPair?): Single<List<Int>> { | ||||
|         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)) | ||||
| @@ -82,7 +86,7 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|                 } | ||||
|                 .onErrorReturn { ByteArray(0) } | ||||
|                 .map { inbuf -> | ||||
|                     if(inbuf.isEmpty()) | ||||
|                     if (inbuf.isEmpty()) | ||||
|                         return@map emptyList<Int>() | ||||
|  | ||||
|                     val view = ByteCursor(inbuf) | ||||
| @@ -90,13 +94,13 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|  | ||||
|                     val expectedLength = numberOfGalleryIds * 4 + 4 | ||||
|  | ||||
|                     if(numberOfGalleryIds > 10000000 | ||||
|                             || numberOfGalleryIds <= 0 | ||||
|                             || inbuf.size != expectedLength) { | ||||
|                     if (numberOfGalleryIds > 10000000 || | ||||
|                             numberOfGalleryIds <= 0 || | ||||
|                             inbuf.size != expectedLength) { | ||||
|                         return@map emptyList<Int>() | ||||
|                     } | ||||
|  | ||||
|                     (1 .. numberOfGalleryIds).map { | ||||
|                     (1..numberOfGalleryIds).map { | ||||
|                         view.nextInt() | ||||
|                     } | ||||
|                 }.toSingle() | ||||
| @@ -105,12 +109,12 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|     private fun BSearch(field: String, key: ByteArray, node: Node?): Single<DataPair?> { | ||||
|         fun compareByteArrays(dv1: ByteArray, dv2: ByteArray): Int { | ||||
|             val top = Math.min(dv1.size, dv2.size) | ||||
|             for(i in 0 until top) { | ||||
|             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 | ||||
| @@ -119,9 +123,9 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|         fun locateKey(key: ByteArray, node: Node): Pair<Boolean, Int> { | ||||
|             var cmpResult = -1 | ||||
|             var lastI = 0 | ||||
|             for(nodeKey in node.keys) { | ||||
|             for (nodeKey in node.keys) { | ||||
|                 cmpResult = compareByteArrays(key, nodeKey) | ||||
|                 if(cmpResult <= 0) break | ||||
|                 if (cmpResult <= 0) break | ||||
|                 lastI++ | ||||
|             } | ||||
|             return (cmpResult == 0) to lastI | ||||
| @@ -133,14 +137,14 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if(node == null || node.keys.isEmpty()) { | ||||
|         if (node == null || node.keys.isEmpty()) { | ||||
|             return Single.just(null) | ||||
|         } | ||||
|  | ||||
|         val (there, where) = locateKey(key, node) | ||||
|         if(there) { | ||||
|         if (there) { | ||||
|             return Single.just(node.datas[where]) | ||||
|         } else if(isLeaf(node)) { | ||||
|         } else if (isLeaf(node)) { | ||||
|             return Single.just(null) | ||||
|         } | ||||
|  | ||||
| @@ -154,20 +158,20 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|  | ||||
|         val numberOfKeys = view.nextInt() | ||||
|  | ||||
|         val keys = (1 .. numberOfKeys).map { | ||||
|         val keys = (1..numberOfKeys).map { | ||||
|             val keySize = view.nextInt() | ||||
|             view.next(keySize) | ||||
|         } | ||||
|  | ||||
|         val numberOfDatas = view.nextInt() | ||||
|         val datas = (1 .. numberOfDatas).map { | ||||
|         val datas = (1..numberOfDatas).map { | ||||
|             val offset = view.nextLong() | ||||
|             val length = view.nextInt() | ||||
|             DataPair(offset, length) | ||||
|         } | ||||
|  | ||||
|         val numberOfSubnodeAddresses = B + 1 | ||||
|         val subnodeAddresses = (1 .. numberOfSubnodeAddresses).map { | ||||
|         val subnodeAddresses = (1..numberOfSubnodeAddresses).map { | ||||
|             view.nextLong() | ||||
|         } | ||||
|  | ||||
| @@ -176,7 +180,7 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|  | ||||
|     private fun getNodeAtAddress(field: String, address: Long): Single<Node?> { | ||||
|         var url = "$LTN_BASE_URL/$INDEX_DIR/$field.$tagIndexVersion.index" | ||||
|         if(field == "galleries") { | ||||
|         if (field == "galleries") { | ||||
|             url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.index" | ||||
|         } | ||||
|  | ||||
| @@ -187,7 +191,7 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|                 } | ||||
|                 .onErrorReturn { ByteArray(0) } | ||||
|                 .map { nodedata -> | ||||
|                     if(nodedata.isNotEmpty()) { | ||||
|                     if (nodedata.isNotEmpty()) { | ||||
|                         decodeNode(nodedata) | ||||
|                     } else null | ||||
|                 }.toSingle() | ||||
| @@ -195,7 +199,7 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|  | ||||
|     fun getGalleryIdsFromNozomi(area: String?, tag: String, language: String): Single<List<Int>> { | ||||
|         var nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$tag-$language$NOZOMI_EXTENSION" | ||||
|         if(area != null) { | ||||
|         if (area != null) { | ||||
|             nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION" | ||||
|         } | ||||
|  | ||||
| @@ -206,7 +210,7 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|                 .map { resp -> | ||||
|                     val body = resp.body!!.bytes() | ||||
|                     val cursor = ByteCursor(body) | ||||
|                     (1 .. body.size / 4).map { | ||||
|                     (1..body.size / 4).map { | ||||
|                         cursor.nextInt() | ||||
|                     } | ||||
|                 }.toSingle() | ||||
| @@ -234,11 +238,10 @@ class HitomiNozomi(private val client: OkHttpClient, | ||||
|                     .build()) | ||||
|         } | ||||
|  | ||||
|  | ||||
|         fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable<Long> { | ||||
|             return httpClient.newCall(GET("$LTN_BASE_URL/$name/version?_=${System.currentTimeMillis()}")) | ||||
|                     .asObservableSuccess() | ||||
|                     .map { it.body!!.string().toLong() } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -9,17 +9,17 @@ class CrashlyticsPrinter(private val logLevel: Int) : Printer { | ||||
|      * Print log in new line. | ||||
|      * | ||||
|      * @param logLevel the level of log | ||||
|      * @param tag      the tag of log | ||||
|      * @param msg      the msg of log | ||||
|      * @param tag the tag of log | ||||
|      * @param msg the msg of log | ||||
|      */ | ||||
|     override fun println(logLevel: Int, tag: String?, msg: String?) { | ||||
|         if(logLevel >= this.logLevel) { | ||||
|         if (logLevel >= this.logLevel) { | ||||
|             try { | ||||
|                 Crashlytics.log(logLevel, tag, msg) | ||||
|             } catch (t: Throwable) { | ||||
|                 // Crash in debug if shit like this happens | ||||
|                 if(BuildConfig.DEBUG) throw t | ||||
|                 if (BuildConfig.DEBUG) throw t | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -4,11 +4,11 @@ import android.content.Context | ||||
| import android.text.Html | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.LinearLayout | ||||
| import android.widget.TextView | ||||
| import com.ms_square.debugoverlay.DataObserver | ||||
| import com.ms_square.debugoverlay.OverlayModule | ||||
| import eu.kanade.tachiyomi.BuildConfig | ||||
| import android.widget.LinearLayout | ||||
| import android.widget.TextView | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| @@ -61,5 +61,5 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule<String>(n | ||||
|         <b>Source blacklist:</b> ${prefs.eh_enableSourceBlacklist().getOrDefault().asEnabledString()} | ||||
|     """.trimIndent() | ||||
|  | ||||
|     private fun Boolean.asEnabledString() = if(this) "enabled" else "disabled" | ||||
| } | ||||
|     private fun Boolean.asEnabledString() = if (this) "enabled" else "disabled" | ||||
| } | ||||
|   | ||||
| @@ -23,4 +23,4 @@ enum class EHLogLevel(val description: String) { | ||||
|             return curLogLevel!! >= requiredLogLevel.ordinal | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package exh.log | ||||
|  | ||||
| import okhttp3.OkHttpClient | ||||
|  | ||||
| fun OkHttpClient.Builder.maybeInjectEHLogger(): OkHttpClient.Builder { //TODO - un-break this | ||||
| fun OkHttpClient.Builder.maybeInjectEHLogger(): OkHttpClient.Builder { // TODO - un-break this | ||||
| /*    if(false &&EHLogLevel.shouldLog(EHLogLevel.EXTREME)) { | ||||
|         val xLogger = XLog.tag("EHNetwork") | ||||
|                 .nst() | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package exh.metadata | ||||
|  | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
| import java.util.Locale | ||||
|  | ||||
| /** | ||||
|  * Metadata utils | ||||
| @@ -35,13 +35,12 @@ fun parseHumanReadableByteCount(arg0: String): Double? { | ||||
|     return null | ||||
| } | ||||
|  | ||||
|  | ||||
| fun String?.nullIfBlank(): String? = if(isNullOrBlank()) | ||||
| fun String?.nullIfBlank(): String? = if (isNullOrBlank()) | ||||
|     null | ||||
| else | ||||
|     this | ||||
|  | ||||
| fun <K,V> Set<Map.Entry<K,V>>.forEach(action: (K, V) -> Unit) { | ||||
| fun <K, V> Set<Map.Entry<K, V>>.forEach(action: (K, V) -> Unit) { | ||||
|     forEach { action(it.key, it.value) } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,12 +4,14 @@ import android.net.Uri | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.metadata.* | ||||
| import exh.metadata.EX_DATE_FORMAT | ||||
| import exh.metadata.ONGOING_SUFFIX | ||||
| import exh.metadata.humanReadableByteCount | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata | ||||
| import exh.plusAssign | ||||
| import java.util.Date | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import java.util.* | ||||
|  | ||||
| class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|     var gId: String? | ||||
| @@ -27,7 +29,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|  | ||||
|     var datePosted: Long? = null | ||||
|     var parent: String? = null | ||||
|     var visible: String? = null //Not a boolean | ||||
|     var visible: String? = null // Not a boolean | ||||
|     var language: String? = null | ||||
|     var translated: Boolean? = null | ||||
|     var size: Long? = null | ||||
| @@ -47,23 +49,23 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|         } | ||||
|         thumbnailUrl?.let { manga.thumbnail_url = it } | ||||
|  | ||||
|         //No title bug? | ||||
|         val titleObj = if(Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault()) | ||||
|         // No title bug? | ||||
|         val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault()) | ||||
|             altTitle ?: title | ||||
|         else | ||||
|             title | ||||
|         titleObj?.let { manga.title = it } | ||||
|  | ||||
|         //Set artist (if we can find one) | ||||
|         // Set artist (if we can find one) | ||||
|         tags.filter { it.namespace == EH_ARTIST_NAMESPACE }.let { | ||||
|             if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name }) | ||||
|             if (it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name }) | ||||
|         } | ||||
|  | ||||
|         //Copy tags -> genres | ||||
|         // Copy tags -> genres | ||||
|         manga.genre = tagsToGenreString() | ||||
|  | ||||
|         //Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes | ||||
|         //We default to completed | ||||
|         // Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes | ||||
|         // We default to completed | ||||
|         manga.status = SManga.COMPLETED | ||||
|         title?.let { t -> | ||||
|             ONGOING_SUFFIX.find { | ||||
| @@ -73,7 +75,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Build a nice looking description out of what we know | ||||
|         // Build a nice looking description out of what we know | ||||
|         val titleDesc = StringBuilder() | ||||
|         title?.let { titleDesc += "Title: $it\n" } | ||||
|         altTitle?.let { titleDesc += "Alternate Title: $it\n" } | ||||
| @@ -85,7 +87,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|         visible?.let { detailsDesc += "Visible: $it\n" } | ||||
|         language?.let { | ||||
|             detailsDesc += "Language: $it" | ||||
|             if(translated == true) detailsDesc += " TR" | ||||
|             if (translated == true) detailsDesc += " TR" | ||||
|             detailsDesc += "\n" | ||||
|         } | ||||
|         size?.let { detailsDesc += "File size: ${humanReadableByteCount(it, true)}\n" } | ||||
| @@ -114,10 +116,10 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|         const val EH_GENRE_NAMESPACE = "genre" | ||||
|         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")) | ||||
|         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('/') | ||||
| @@ -129,10 +131,10 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|         fun galleryToken(url: String) = | ||||
|                 splitGalleryUrl(url)[2] | ||||
|  | ||||
|         fun normalizeUrl(url: String) | ||||
|                 = idAndTokenToUrl(galleryId(url), galleryToken(url)) | ||||
|         fun normalizeUrl(url: String) = | ||||
|                 idAndTokenToUrl(galleryId(url), galleryToken(url)) | ||||
|  | ||||
|         fun idAndTokenToUrl(id: String, token: String) | ||||
|                 = "/g/$id/$token/?nw=always" | ||||
|         fun idAndTokenToUrl(id: String, token: String) = | ||||
|                 "/g/$id/$token/?nw=always" | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -34,7 +34,6 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() { | ||||
|         manga.description = listOf(titleDesc.toString(), tagsDesc.toString()) | ||||
|                 .filter(String::isNotBlank) | ||||
|                 .joinToString(separator = "\n") | ||||
|  | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
| @@ -47,4 +46,4 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() { | ||||
|         const val TAGS_NAMESPACE = "tags" | ||||
|         const val ARTIST_NAMESPACE = "artist" | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() { | ||||
|         } | ||||
|  | ||||
|         // Guess thumbnail URL if manga does not have thumbnail URL | ||||
|         if(manga.thumbnail_url.isNullOrBlank()) { | ||||
|         if (manga.thumbnail_url.isNullOrBlank()) { | ||||
|             manga.thumbnail_url = guessThumbnailUrl(hbId.toString()) | ||||
|         } | ||||
|  | ||||
| @@ -49,4 +49,4 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() { | ||||
|             return "$BASE_URL/thumbnails/${hbid}_1.jpg#guessed" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() { | ||||
|         manga.artist = artist | ||||
|         manga.author = artist | ||||
|  | ||||
|         //Not available | ||||
|         // Not available | ||||
|         manga.status = SManga.UNKNOWN | ||||
|  | ||||
|         val detailsDesc = "Title: $title\n" + | ||||
| @@ -49,7 +49,7 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() { | ||||
|  | ||||
|         const val BASE_URL = "https://hentai.cafe" | ||||
|  | ||||
|         fun hcIdFromUrl(url: String) | ||||
|                 = url.split("/").last { it.isNotBlank() } | ||||
|         fun hcIdFromUrl(url: String) = | ||||
|                 url.split("/").last { it.isNotBlank() } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.metadata.EX_DATE_FORMAT | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata | ||||
| import exh.plusAssign | ||||
| import java.util.* | ||||
| import java.util.Date | ||||
|  | ||||
| class HitomiSearchMetadata: RaisedSearchMetadata() { | ||||
| class HitomiSearchMetadata : RaisedSearchMetadata() { | ||||
|     var url get() = hlId?.let { urlFromHlId(it) } | ||||
|         set(a) { | ||||
|             a?.let { | ||||
| @@ -62,10 +62,10 @@ 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 { | ||||
| @@ -74,7 +74,7 @@ class HitomiSearchMetadata: RaisedSearchMetadata() { | ||||
|  | ||||
|         manga.status = SManga.UNKNOWN | ||||
|  | ||||
|         //Copy tags -> genres | ||||
|         // Copy tags -> genres | ||||
|         manga.genre = tagsToGenreString() | ||||
|  | ||||
|         val tagsDesc = tagsToDescription() | ||||
| @@ -92,10 +92,10 @@ class HitomiSearchMetadata: RaisedSearchMetadata() { | ||||
|         const val LTN_BASE_URL = "https://ltn.hitomi.la" | ||||
|         const val BASE_URL = "https://hitomi.la" | ||||
|  | ||||
|         fun hlIdFromUrl(url: String) | ||||
|                 = url.split('/').last().split('-').last().substringBeforeLast('.') | ||||
|         fun hlIdFromUrl(url: String) = | ||||
|                 url.split('/').last().split('-').last().substringBeforeLast('.') | ||||
|  | ||||
|         fun urlFromHlId(id: String) | ||||
|                 = "$BASE_URL/galleries/$id.html" | ||||
|         fun urlFromHlId(id: String) = | ||||
|                 "$BASE_URL/galleries/$id.html" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,12 +3,14 @@ package exh.metadata.metadata | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.metadata.* | ||||
| import exh.metadata.EX_DATE_FORMAT | ||||
| import exh.metadata.ONGOING_SUFFIX | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata | ||||
| import exh.metadata.nullIfBlank | ||||
| import exh.plusAssign | ||||
| import java.util.Date | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import java.util.* | ||||
|  | ||||
| class NHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|     var url get() = nhId?.let { BASE_URL + nhIdToPath(it) } | ||||
| @@ -39,10 +41,10 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|     override fun copyTo(manga: SManga) { | ||||
|         nhId?.let { manga.url = nhIdToPath(it) } | ||||
|  | ||||
|         if(mediaId != null) { | ||||
|         if (mediaId != null) { | ||||
|             val hqThumbs = Injekt.get<PreferencesHelper>().eh_nh_useHighQualityThumbs().getOrDefault() | ||||
|             typeToExtension(if(hqThumbs) coverImageType else thumbnailImageType)?.let { | ||||
|                 manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if(hqThumbs) | ||||
|             typeToExtension(if (hqThumbs) coverImageType else thumbnailImageType)?.let { | ||||
|                 manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs) | ||||
|                     "cover" | ||||
|                 else "thumb"}.$it" | ||||
|             } | ||||
| @@ -50,21 +52,21 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|  | ||||
|         manga.title = englishTitle ?: japaneseTitle ?: shortTitle!! | ||||
|  | ||||
|         //Set artist (if we can find one) | ||||
|         // Set artist (if we can find one) | ||||
|         tags.filter { it.namespace == NHENTAI_ARTIST_NAMESPACE }.let { | ||||
|             if(it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name }) | ||||
|             if (it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name }) | ||||
|         } | ||||
|  | ||||
|         var category: String? = null | ||||
|         tags.filter { it.namespace == NHENTAI_CATEGORIES_NAMESPACE }.let { | ||||
|             if(it.isNotEmpty()) category = it.joinToString(transform = { it.name }) | ||||
|             if (it.isNotEmpty()) category = it.joinToString(transform = { it.name }) | ||||
|         } | ||||
|  | ||||
|         //Copy tags -> genres | ||||
|         // Copy tags -> genres | ||||
|         manga.genre = tagsToGenreString() | ||||
|  | ||||
|         //Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes | ||||
|         //We default to completed | ||||
|         // Try to automatically identify if it is ongoing, we try not to be too lenient here to avoid making mistakes | ||||
|         // We default to completed | ||||
|         manga.status = SManga.COMPLETED | ||||
|         englishTitle?.let { t -> | ||||
|             ONGOING_SUFFIX.find { | ||||
| @@ -106,14 +108,14 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { | ||||
|         private const val NHENTAI_CATEGORIES_NAMESPACE = "category" | ||||
|  | ||||
|         fun typeToExtension(t: String?) = | ||||
|                 when(t) { | ||||
|                 when (t) { | ||||
|                     "p" -> "png" | ||||
|                     "j" -> "jpg" | ||||
|                     else -> null | ||||
|                 } | ||||
|  | ||||
|         fun nhUrlToId(url: String) | ||||
|                 = url.split("/").last { it.isNotBlank() }.toLong() | ||||
|         fun nhUrlToId(url: String) = | ||||
|                 url.split("/").last { it.isNotBlank() }.toLong() | ||||
|  | ||||
|         fun nhIdToPath(id: Long) = "/g/$id/" | ||||
|     } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ 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" | ||||
| @@ -58,7 +58,7 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { | ||||
|         } | ||||
|  | ||||
|         status?.let { | ||||
|             manga.status = when(it) { | ||||
|             manga.status = when (it) { | ||||
|                 "Ongoing" -> SManga.ONGOING | ||||
|                 "Completed", "Suspended" -> SManga.COMPLETED | ||||
|                 else -> SManga.UNKNOWN | ||||
| @@ -70,7 +70,7 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { | ||||
|             detailsDesc += "Rating: %.2\n".format(it) | ||||
|         } | ||||
|  | ||||
|         //Copy tags -> genres | ||||
|         // Copy tags -> genres | ||||
|         manga.genre = tagsToGenreString() | ||||
|  | ||||
|         val tagsDesc = tagsToDescription() | ||||
| @@ -80,15 +80,14 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { | ||||
|                 .joinToString(separator = "\n") | ||||
|     } | ||||
|  | ||||
|  | ||||
|     companion object { | ||||
|         private const val TITLE_TYPE_MAIN = 0 | ||||
|         private const val TITLE_TYPE_ALT = 1 | ||||
|  | ||||
|         const val TAG_TYPE_DEFAULT = 0 | ||||
|  | ||||
|         private fun splitGalleryUrl(url: String) | ||||
|                 = url.let { | ||||
|         private fun splitGalleryUrl(url: String) = | ||||
|                 url.let { | ||||
|             Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank) | ||||
|         } | ||||
|  | ||||
| @@ -97,13 +96,13 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() { | ||||
| } | ||||
|  | ||||
| enum class PervEdenLang(val id: Long) { | ||||
|     //DO NOT RENAME THESE TO CAPITAL LETTERS! The enum names are used to build URLs | ||||
|     // DO NOT RENAME THESE TO CAPITAL LETTERS! The enum names are used to build URLs | ||||
|     en(PERV_EDEN_EN_SOURCE_ID), | ||||
|     it(PERV_EDEN_IT_SOURCE_ID); | ||||
|  | ||||
|     companion object { | ||||
|         fun source(id: Long) | ||||
|                 = values().find { it.id == id } | ||||
|         fun source(id: Long) = | ||||
|                 values().find { it.id == id } | ||||
|                 ?: throw IllegalArgumentException("Unknown source ID: $id!") | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class PururinSearchMetadata : RaisedSearchMetadata() { | ||||
|         altTitle?.let { titleDesc += "Japanese Title: $it\n" } | ||||
|  | ||||
|         val detailsDesc = StringBuilder() | ||||
|         (uploaderDisp ?: uploader)?.let { detailsDesc += "Uploader: $it\n"} | ||||
|         (uploaderDisp ?: uploader)?.let { detailsDesc += "Uploader: $it\n" } | ||||
|         pages?.let { detailsDesc += "Length: $it pages\n" } | ||||
|         fileSize?.let { detailsDesc += "Size: $it\n" } | ||||
|         ratingCount?.let { detailsDesc += "Rating: $averageRating ($ratingCount)\n" } | ||||
| @@ -69,4 +69,4 @@ class PururinSearchMetadata : RaisedSearchMetadata() { | ||||
|  | ||||
|         val BASE_URL = "https://pururin.io" | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.metadata.EX_DATE_FORMAT | ||||
| import exh.metadata.metadata.base.RaisedSearchMetadata | ||||
| import exh.plusAssign | ||||
| import java.util.* | ||||
| import java.util.Date | ||||
|  | ||||
| class TsuminoSearchMetadata : RaisedSearchMetadata() { | ||||
|     var tmId: Int? = null | ||||
| @@ -51,15 +51,15 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { | ||||
|         collection?.let { detailsDesc += "Collection: $it\n" } | ||||
|         group?.let { detailsDesc += "Group: $it\n" } | ||||
|         val parodiesString = parody.joinToString() | ||||
|         if(parodiesString.isNotEmpty()) { | ||||
|         if (parodiesString.isNotEmpty()) { | ||||
|             detailsDesc += "Parody: $parodiesString\n" | ||||
|         } | ||||
|         val charactersString = character.joinToString() | ||||
|         if(charactersString.isNotEmpty()) { | ||||
|         if (charactersString.isNotEmpty()) { | ||||
|             detailsDesc += "Character: $charactersString\n" | ||||
|         } | ||||
|  | ||||
|         //Copy tags -> genres | ||||
|         // Copy tags -> genres | ||||
|         manga.genre = tagsToGenreString() | ||||
|  | ||||
|         val tagsDesc = tagsToDescription() | ||||
| @@ -76,8 +76,8 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { | ||||
|  | ||||
|         val BASE_URL = "https://www.tsumino.com" | ||||
|  | ||||
|         fun tmIdFromUrl(url: String) | ||||
|                 = Uri.parse(url).lastPathSegment | ||||
|         fun tmIdFromUrl(url: String) = | ||||
|                 Uri.parse(url).lastPathSegment | ||||
|  | ||||
|         fun mangaUrlFromId(id: String) = "/Book/Info/$id" | ||||
|  | ||||
|   | ||||
| @@ -5,19 +5,19 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import exh.metadata.sql.models.SearchMetadata | ||||
| import exh.metadata.sql.models.SearchTag | ||||
| import exh.metadata.sql.models.SearchTitle | ||||
| import kotlin.reflect.KClass | ||||
| import rx.Completable | ||||
| import rx.Single | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| data class FlatMetadata( | ||||
|         val metadata: SearchMetadata, | ||||
|         val tags: List<SearchTag>, | ||||
|         val titles: List<SearchTitle> | ||||
|     val metadata: SearchMetadata, | ||||
|     val tags: List<SearchTag>, | ||||
|     val titles: List<SearchTitle> | ||||
| ) { | ||||
|     inline fun <reified T : RaisedSearchMetadata> raise(): T = raise(T::class) | ||||
|  | ||||
|     fun <T : RaisedSearchMetadata> raise(clazz: KClass<T>) | ||||
|         = RaisedSearchMetadata.raiseFlattenGson | ||||
|     fun <T : RaisedSearchMetadata> raise(clazz: KClass<T>) = | ||||
|         RaisedSearchMetadata.raiseFlattenGson | ||||
|                 .fromJson(metadata.extra, clazz.java).apply { | ||||
|                     fillBaseFields(this@FlatMetadata) | ||||
|                 } | ||||
| @@ -27,7 +27,7 @@ fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<Fla | ||||
|     // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions | ||||
|     val single = Single.fromCallable { | ||||
|         val meta = getSearchMetadataForManga(mangaId).executeAsBlocking() | ||||
|         if(meta != null) { | ||||
|         if (meta != null) { | ||||
|             val tags = getSearchTagsForManga(mangaId).executeAsBlocking() | ||||
|             val titles = getSearchTitlesForManga(mangaId).executeAsBlocking() | ||||
|  | ||||
| @@ -92,4 +92,4 @@ fun DatabaseHelper.insertFlatMetadata(flatMetadata: FlatMetadata) = Completable. | ||||
|         setSearchTagsForManga(flatMetadata.metadata.mangaId, flatMetadata.tags) | ||||
|         setSearchTitlesForManga(flatMetadata.metadata.mangaId, flatMetadata.titles) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -30,18 +30,18 @@ abstract class RaisedSearchMetadata { | ||||
|  | ||||
|     fun replaceTitleOfType(type: Int, newTitle: String?) { | ||||
|         titles.removeAll { it.type == type } | ||||
|         if(newTitle != null) titles += RaisedTitle(newTitle, type) | ||||
|         if (newTitle != null) titles += RaisedTitle(newTitle, type) | ||||
|     } | ||||
|  | ||||
|     abstract fun copyTo(manga: SManga) | ||||
|  | ||||
|     fun tagsToGenreString() | ||||
|             = tags.filter { it.type != TAG_TYPE_VIRTUAL } | ||||
|             .joinToString { (if(it.namespace != null) "${it.namespace}: " else "") + it.name } | ||||
|     fun tagsToGenreString() = | ||||
|             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' | ||||
|     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 | ||||
| @@ -49,7 +49,7 @@ abstract class RaisedSearchMetadata { | ||||
|         groupedTags.forEach { namespace, tags -> | ||||
|             if (tags.isNotEmpty()) { | ||||
|                 val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" }) | ||||
|                 if(namespace != null) { | ||||
|                 if (namespace != null) { | ||||
|                     this += "▪ " | ||||
|                     this += namespace | ||||
|                     this += ": " | ||||
| @@ -125,8 +125,8 @@ abstract class RaisedSearchMetadata { | ||||
|              * @param property the metadata for the property. | ||||
|              * @return the property value. | ||||
|              */ | ||||
|             override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) | ||||
|                     = thisRef.getTitleOfType(type) | ||||
|             override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) = | ||||
|                     thisRef.getTitleOfType(type) | ||||
|  | ||||
|             /** | ||||
|              * Sets the value of the property for the given object. | ||||
| @@ -134,8 +134,8 @@ abstract class RaisedSearchMetadata { | ||||
|              * @param property the metadata for the property. | ||||
|              * @param value the value to set. | ||||
|              */ | ||||
|             override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) | ||||
|                     = thisRef.replaceTitleOfType(type, value) | ||||
|             override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) = | ||||
|                     thisRef.replaceTitleOfType(type, value) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package exh.metadata.metadata.base | ||||
|  | ||||
| data class RaisedTag(val namespace: String?, | ||||
|                      val name: String, | ||||
|                      val type: Int) | ||||
| data class RaisedTag( | ||||
|     val namespace: String?, | ||||
|     val name: String, | ||||
|     val type: Int | ||||
| ) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package exh.metadata.metadata.base | ||||
|  | ||||
| data class RaisedTitle( | ||||
|         val title: String, | ||||
|         val type: Int = 0 | ||||
| ) | ||||
|     val title: String, | ||||
|     val type: Int = 0 | ||||
| ) | ||||
|   | ||||
| @@ -2,20 +2,20 @@ package exh.metadata.sql.models | ||||
|  | ||||
| data class SearchMetadata( | ||||
|         // Manga ID this gallery is linked to | ||||
|         val mangaId: Long, | ||||
|     val mangaId: Long, | ||||
|  | ||||
|         // Gallery uploader | ||||
|         val uploader: String?, | ||||
|     val uploader: String?, | ||||
|  | ||||
|         // Extra data attached to this metadata, in JSON format | ||||
|         val extra: String, | ||||
|     val extra: String, | ||||
|  | ||||
|         // Indexed extra data attached to this metadata | ||||
|         val indexedExtra: String?, | ||||
|     val indexedExtra: String?, | ||||
|  | ||||
|         // The version of this metadata's extra. Used to track changes to the 'extra' field's schema | ||||
|         val extraVersion: Int | ||||
|     val extraVersion: Int | ||||
| ) { | ||||
|     // Transient information attached to this piece of metadata, useful for caching | ||||
|     var transientCache: Map<String, Any>? = null | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -2,17 +2,17 @@ package exh.metadata.sql.models | ||||
|  | ||||
| data class SearchTag( | ||||
|         // Tag identifier, unique | ||||
|         val id: Long?, | ||||
|     val id: Long?, | ||||
|  | ||||
|         // Metadata this tag is attached to | ||||
|         val mangaId: Long, | ||||
|     val mangaId: Long, | ||||
|  | ||||
|         // Tag namespace | ||||
|         val namespace: String?, | ||||
|     val namespace: String?, | ||||
|  | ||||
|         // Tag name | ||||
|         val name: String, | ||||
|     val name: String, | ||||
|  | ||||
|         // Tag type | ||||
|         val type: Int | ||||
| ) | ||||
|     val type: Int | ||||
| ) | ||||
|   | ||||
| @@ -2,14 +2,14 @@ package exh.metadata.sql.models | ||||
|  | ||||
| data class SearchTitle( | ||||
|         // Title identifier, unique | ||||
|         val id: Long?, | ||||
|     val id: Long?, | ||||
|  | ||||
|         // Metadata this title is attached to | ||||
|         val mangaId: Long, | ||||
|     val mangaId: Long, | ||||
|  | ||||
|         // Title | ||||
|         val title: String, | ||||
|     val title: String, | ||||
|  | ||||
|         // Title type, useful for distinguishing between main/alt titles | ||||
|         val type: Int | ||||
|     val type: Int | ||||
| ) | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package exh.metadata.sql.queries | ||||
| import com.pushtorefresh.storio.sqlite.queries.DeleteQuery | ||||
| import com.pushtorefresh.storio.sqlite.queries.Query | ||||
| import eu.kanade.tachiyomi.data.database.DbProvider | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import exh.metadata.sql.models.SearchMetadata | ||||
| import exh.metadata.sql.tables.SearchMetadataTable | ||||
|  | ||||
| @@ -42,4 +41,4 @@ interface SearchMetadataQueries : DbProvider { | ||||
|             .table(SearchMetadataTable.TABLE) | ||||
|             .build()) | ||||
|             .prepare() | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -44,4 +44,4 @@ interface SearchTagQueries : DbProvider { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -4,8 +4,6 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery | ||||
| import com.pushtorefresh.storio.sqlite.queries.Query | ||||
| import eu.kanade.tachiyomi.data.database.DbProvider | ||||
| import eu.kanade.tachiyomi.data.database.inTransaction | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import exh.metadata.sql.models.SearchMetadata | ||||
| import exh.metadata.sql.models.SearchTitle | ||||
| import exh.metadata.sql.tables.SearchTitleTable | ||||
|  | ||||
| @@ -46,4 +44,4 @@ interface SearchTitleQueries : DbProvider { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
|  | ||||
| val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId -> | ||||
|     if(!response.isSuccessful) { | ||||
|     if (!response.isSuccessful) { | ||||
|         response.interceptAsHtml { doc -> | ||||
|             // Find captcha | ||||
|             if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) { | ||||
| @@ -20,4 +20,4 @@ val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId -> | ||||
|             } | ||||
|         } | ||||
|     } else response | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| package exh.search | ||||
|  | ||||
| class Namespace(var namespace: String, | ||||
|                 var tag: Text? = null) : QueryComponent() | ||||
| class Namespace( | ||||
|     var namespace: String, | ||||
|     var tag: Text? = null | ||||
| ) : QueryComponent() | ||||
|   | ||||
| @@ -3,4 +3,4 @@ package exh.search | ||||
| open class QueryComponent { | ||||
|     var excluded = false | ||||
|     var exact = false | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -7,8 +7,10 @@ import exh.metadata.sql.tables.SearchTitleTable | ||||
| class SearchEngine { | ||||
|     private val queryCache = mutableMapOf<String, List<QueryComponent>>() | ||||
|  | ||||
|     fun textToSubQueries(namespace: String?, | ||||
|                          component: Text?): Pair<String, List<String>>? { | ||||
|     fun textToSubQueries( | ||||
|         namespace: String?, | ||||
|         component: Text? | ||||
|     ): Pair<String, List<String>>? { | ||||
|         val maybeLenientComponent = component?.let { | ||||
|             if (!it.exact) | ||||
|                     it.asLenientTagQueries() | ||||
| @@ -22,20 +24,20 @@ class SearchEngine { | ||||
|                 "${SearchTagTable.TABLE}.${SearchTagTable.COL_NAME} LIKE ?" | ||||
|             }.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params | ||||
|         } | ||||
|         return if(namespace != null) { | ||||
|         return if (namespace != null) { | ||||
|             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() | ||||
|             val params = mutableListOf(escapeLike(namespace)) | ||||
|             if(componentTagQuery != null) { | ||||
|             if (componentTagQuery != null) { | ||||
|                 query += "\n    AND ${componentTagQuery.first}" | ||||
|                 params += componentTagQuery.second | ||||
|             } | ||||
|  | ||||
|             "$query)" to params | ||||
|         } else if(component != null) { | ||||
|         } else if (component != null) { | ||||
|             // Match title + tags | ||||
|             val tagQuery = """ | ||||
|                 SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE} | ||||
| @@ -59,27 +61,27 @@ class SearchEngine { | ||||
|         val include = mutableListOf<Pair<String, List<String>>>() | ||||
|         val exclude = mutableListOf<Pair<String, List<String>>>() | ||||
|  | ||||
|         for(component in q) { | ||||
|             val query = if(component is Text) { | ||||
|         for (component in q) { | ||||
|             val query = if (component is Text) { | ||||
|                 textToSubQueries(null, component) | ||||
|             } else if(component is Namespace) { | ||||
|                 if(component.namespace == "uploader") { | ||||
|             } else if (component is Namespace) { | ||||
|                 if (component.namespace == "uploader") { | ||||
|                     wheres += "meta.${SearchMetadataTable.COL_UPLOADER} LIKE ?" | ||||
|                     whereParams += component.tag!!.rawTextEscapedForLike() | ||||
|                     null | ||||
|                 } else { | ||||
|                     if(component.tag!!.components.size > 0) { | ||||
|                         //Match namespace + tags | ||||
|                     if (component.tag!!.components.size > 0) { | ||||
|                         // Match namespace + tags | ||||
|                         textToSubQueries(component.namespace, component.tag) | ||||
|                     } else { | ||||
|                         //Perform namespace search | ||||
|                         // Perform namespace search | ||||
|                         textToSubQueries(component.namespace, null) | ||||
|                     } | ||||
|                 } | ||||
|             } else error("Unknown query component!") | ||||
|  | ||||
|             if(query != null) { | ||||
|                 (if(component.excluded) exclude else include) += query | ||||
|             if (query != null) { | ||||
|                 (if (component.excluded) exclude else include) += query | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -97,14 +99,13 @@ class SearchEngine { | ||||
|             completeParams += pair.second | ||||
|         } | ||||
|  | ||||
|  | ||||
|         exclude.forEach { | ||||
|             wheres += """ | ||||
|                 (meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first}) | ||||
|             """.trimIndent() | ||||
|             whereParams += it.second | ||||
|         } | ||||
|         if(wheres.isNotEmpty()) { | ||||
|         if (wheres.isNotEmpty()) { | ||||
|             completeParams += whereParams | ||||
|             baseQuery += "\nWHERE\n" | ||||
|             baseQuery += wheres.joinToString("\nAND\n") | ||||
| @@ -126,7 +127,7 @@ class SearchEngine { | ||||
|         var nextIsExact = false | ||||
|  | ||||
|         fun flushText() { | ||||
|             if(queuedRawText.isNotEmpty()) { | ||||
|             if (queuedRawText.isNotEmpty()) { | ||||
|                 queuedText += StringTextComponent(queuedRawText.toString()) | ||||
|                 queuedRawText.setLength(0) | ||||
|             } | ||||
| @@ -150,24 +151,24 @@ class SearchEngine { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for(char in query.toLowerCase()) { | ||||
|             if(char == '"') { | ||||
|         for (char in query.toLowerCase()) { | ||||
|             if (char == '"') { | ||||
|                 inQuotes = !inQuotes | ||||
|             } else if(enableWildcard && (char == '?' || char == '_')) { | ||||
|             } else if (enableWildcard && (char == '?' || char == '_')) { | ||||
|                 flushText() | ||||
|                 queuedText.add(SingleWildcard(char.toString())) | ||||
|             } else if(enableWildcard && (char == '*' || char == '%')) { | ||||
|             } else if (enableWildcard && (char == '*' || char == '%')) { | ||||
|                 flushText() | ||||
|                 queuedText.add(MultiWildcard(char.toString())) | ||||
|             } else if(char == '-') { | ||||
|             } else if (char == '-') { | ||||
|                 nextIsExcluded = true | ||||
|             } else if(char == '$') { | ||||
|             } else if (char == '$') { | ||||
|                 nextIsExact = true | ||||
|             } else if(char == ':') { | ||||
|             } else if (char == ':') { | ||||
|                 flushText() | ||||
|                 var flushed = flushToText().rawTextOnly() | ||||
|                 //Map tag aliases | ||||
|                 flushed = when(flushed) { | ||||
|                 // Map tag aliases | ||||
|                 flushed = when (flushed) { | ||||
|                     "a" -> "artist" | ||||
|                     "c", "char" -> "character" | ||||
|                     "f" -> "female" | ||||
| @@ -179,7 +180,7 @@ class SearchEngine { | ||||
|                     else -> flushed | ||||
|                 } | ||||
|                 namespace = Namespace(flushed, null) | ||||
|             } else if(char == ' ' && !inQuotes) { | ||||
|             } else if (char == ' ' && !inQuotes) { | ||||
|                 flushAll() | ||||
|             } else { | ||||
|                 queuedRawText.append(char) | ||||
| @@ -197,7 +198,6 @@ class SearchEngine { | ||||
|             return string.replace("\\", "\\\\") | ||||
|                     .replace("_", "\\_") | ||||
|                     .replace("%", "\\%") | ||||
|  | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ package exh.search | ||||
| import exh.plusAssign | ||||
| import exh.search.SearchEngine.Companion.escapeLike | ||||
|  | ||||
| class Text: QueryComponent() { | ||||
| class Text : QueryComponent() { | ||||
|     val components = mutableListOf<TextComponent>() | ||||
|  | ||||
|     private var query: String? = null | ||||
| @@ -12,26 +12,26 @@ class Text: QueryComponent() { | ||||
|     private var rawText: String? = null | ||||
|  | ||||
|     fun asQuery(): String { | ||||
|         if(query == null) { | ||||
|         if (query == null) { | ||||
|             query = rBaseBuilder().toString() | ||||
|         } | ||||
|         return query!! | ||||
|     } | ||||
|  | ||||
|     fun asLenientTitleQuery(): String { | ||||
|         if(lenientTitleQuery == null) { | ||||
|         if (lenientTitleQuery == null) { | ||||
|             lenientTitleQuery = StringBuilder("%").append(rBaseBuilder()).append("%").toString() | ||||
|         } | ||||
|         return lenientTitleQuery!! | ||||
|     } | ||||
|  | ||||
|     fun asLenientTagQueries(): List<String> { | ||||
|         if(lenientTagQueries == null) { | ||||
|         if (lenientTagQueries == null) { | ||||
|             lenientTagQueries = listOf( | ||||
|                     //Match beginning of tag | ||||
|                     // Match beginning of tag | ||||
|                     rBaseBuilder().append("%").toString(), | ||||
|                     //Tag word matcher (that matches multiple words) | ||||
|                     //Can't make it match a single word in Realm :( | ||||
|                     // 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() | ||||
| @@ -42,8 +42,8 @@ class Text: QueryComponent() { | ||||
|  | ||||
|     fun rBaseBuilder(): StringBuilder { | ||||
|         val builder = StringBuilder() | ||||
|         for(component in components) { | ||||
|             when(component) { | ||||
|         for (component in components) { | ||||
|             when (component) { | ||||
|                 is StringTextComponent -> builder += escapeLike(component.value) | ||||
|                 is SingleWildcard -> builder += "_" | ||||
|                 is MultiWildcard -> builder += "%" | ||||
| @@ -52,7 +52,7 @@ class Text: QueryComponent() { | ||||
|         return builder | ||||
|     } | ||||
|  | ||||
|     fun rawTextOnly() = if(rawText != null) | ||||
|     fun rawTextOnly() = if (rawText != null) | ||||
|         rawText!! | ||||
|     else { | ||||
|         rawText = components | ||||
|   | ||||
| @@ -8,13 +8,19 @@ import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.ui.smartsearch.SmartSearchPresenter | ||||
| import exh.util.await | ||||
| import info.debatty.java.stringsimilarity.NormalizedLevenshtein | ||||
| import kotlinx.coroutines.* | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.async | ||||
| import kotlinx.coroutines.supervisorScope | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import kotlin.coroutines.CoroutineContext | ||||
|  | ||||
| class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|                         val extraSearchParams: String? = null): CoroutineScope { | ||||
| class SmartSearchEngine( | ||||
|     parentContext: CoroutineContext, | ||||
|     val extraSearchParams: String? = null | ||||
| ) : CoroutineScope { | ||||
|     override val coroutineContext: CoroutineContext = parentContext + Job() + Dispatchers.Default | ||||
|  | ||||
|     private val db: DatabaseHelper by injectLazy() | ||||
| @@ -29,7 +35,7 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|         val eligibleManga = supervisorScope { | ||||
|             queries.map { query -> | ||||
|                 async(Dispatchers.Default) { | ||||
|                     val builtQuery = if(extraSearchParams != null) { | ||||
|                     val builtQuery = if (extraSearchParams != null) { | ||||
|                         "$query ${extraSearchParams.trim()}" | ||||
|                     } else query | ||||
|  | ||||
| @@ -51,7 +57,7 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|  | ||||
|     suspend fun normalSearch(source: CatalogueSource, title: String): SManga? { | ||||
|         val eligibleManga = supervisorScope { | ||||
|             val searchQuery = if(extraSearchParams != null) { | ||||
|             val searchQuery = if (extraSearchParams != null) { | ||||
|                 "$title ${extraSearchParams.trim()}" | ||||
|             } else title | ||||
|             val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io()) | ||||
| @@ -71,7 +77,7 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|         val splitCleanedTitle = cleanedTitle.split(" ") | ||||
|         val splitSortedByLargest = splitCleanedTitle.sortedByDescending { it.length } | ||||
|  | ||||
|         if(splitCleanedTitle.isEmpty()) { | ||||
|         if (splitCleanedTitle.isEmpty()) { | ||||
|             return emptyList() | ||||
|         } | ||||
|  | ||||
| @@ -99,7 +105,7 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|  | ||||
|         // Remove text in brackets | ||||
|         var cleanedTitle = removeTextInBrackets(preTitle, true) | ||||
|         if(cleanedTitle.length <= 5) { // Title is suspiciously short, try parsing it backwards | ||||
|         if (cleanedTitle.length <= 5) { // Title is suspiciously short, try parsing it backwards | ||||
|             cleanedTitle = removeTextInBrackets(preTitle, false) | ||||
|         } | ||||
|  | ||||
| @@ -127,7 +133,7 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|         }.toMap() | ||||
|  | ||||
|         // Reverse pairs if reading backwards | ||||
|         if(!readForward) { | ||||
|         if (!readForward) { | ||||
|             val tmp = openingBracketPairs | ||||
|             openingBracketPairs = closingBracketPairs | ||||
|             closingBracketPairs = tmp | ||||
| @@ -136,16 +142,16 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|         val depthPairs = bracketPairs.map { 0 }.toMutableList() | ||||
|  | ||||
|         val result = StringBuilder() | ||||
|         for(c in if(readForward) text else text.reversed()) { | ||||
|         for (c in if (readForward) text else text.reversed()) { | ||||
|             val openingBracketDepthIndex = openingBracketPairs[c] | ||||
|             if(openingBracketDepthIndex != null) { | ||||
|             if (openingBracketDepthIndex != null) { | ||||
|                 depthPairs[openingBracketDepthIndex]++ | ||||
|             } else { | ||||
|                 val closingBracketDepthIndex = closingBracketPairs[c] | ||||
|                 if(closingBracketDepthIndex != null) { | ||||
|                 if (closingBracketDepthIndex != null) { | ||||
|                     depthPairs[closingBracketDepthIndex]-- | ||||
|                 } else { | ||||
|                     if(depthPairs.all { it <= 0 }) { | ||||
|                     if (depthPairs.all { it <= 0 }) { | ||||
|                         result.append(c) | ||||
|                     } else { | ||||
|                         // In brackets, do not append to result | ||||
| @@ -183,4 +189,4 @@ class SmartSearchEngine(parentContext: CoroutineContext, | ||||
|         private val titleRegex = Regex("[^a-zA-Z0-9- ]") | ||||
|         private val consecutiveSpacesRegex = Regex(" +") | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,29 +1,32 @@ | ||||
| package exh.source | ||||
|  | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import rx.Observable | ||||
| import java.lang.RuntimeException | ||||
|  | ||||
| abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() { | ||||
| abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { | ||||
|     /** | ||||
|      * Returns the request for the popular manga given the page. | ||||
|      * | ||||
|      * @param page the page number to retrieve. | ||||
|      */ | ||||
|     override fun popularMangaRequest(page: Int) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun popularMangaRequest(page: Int) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a [MangasPage] object. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun popularMangaParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun popularMangaParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Returns the request for the search manga given the page. | ||||
| @@ -32,64 +35,64 @@ abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() { | ||||
|      * @param query the search query. | ||||
|      * @param filters the list of filters to apply. | ||||
|      */ | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a [MangasPage] object. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun searchMangaParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun searchMangaParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Returns the request for latest manga given the page. | ||||
|      * | ||||
|      * @param page the page number to retrieve. | ||||
|      */ | ||||
|     override fun latestUpdatesRequest(page: Int) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun latestUpdatesRequest(page: Int) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a [MangasPage] object. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun latestUpdatesParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun latestUpdatesParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns the details of a manga. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun mangaDetailsParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun mangaDetailsParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a list of chapters. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun chapterListParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun chapterListParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a list of pages. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun pageListParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun pageListParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns the absolute url to the source image. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun imageUrlParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun imageUrlParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Base url of the website without the trailing slash, like: http://mysite.com | ||||
| @@ -236,8 +239,8 @@ abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() { | ||||
|     override fun getFilterList() = delegate.getFilterList() | ||||
|  | ||||
|     private fun ensureDelegateCompatible() { | ||||
|         if(versionId != delegate.versionId | ||||
|                 || lang != delegate.lang) { | ||||
|         if (versionId != delegate.versionId || | ||||
|                 lang != delegate.lang) { | ||||
|             throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!") | ||||
|         } | ||||
|     } | ||||
| @@ -247,4 +250,4 @@ abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() { | ||||
|     init { | ||||
|         delegate.bindDelegate(this) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -2,13 +2,19 @@ package exh.source | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.MangasPage | ||||
| import eu.kanade.tachiyomi.source.model.Page | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import okhttp3.Response | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class EnhancedHttpSource(val originalSource: HttpSource, | ||||
|                          val enchancedSource: HttpSource): HttpSource() { | ||||
| class EnhancedHttpSource( | ||||
|     val originalSource: HttpSource, | ||||
|     val enchancedSource: HttpSource | ||||
| ) : HttpSource() { | ||||
|     private val prefs: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     /** | ||||
| @@ -16,16 +22,16 @@ class EnhancedHttpSource(val originalSource: HttpSource, | ||||
|      * | ||||
|      * @param page the page number to retrieve. | ||||
|      */ | ||||
|     override fun popularMangaRequest(page: Int) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun popularMangaRequest(page: Int) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a [MangasPage] object. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun popularMangaParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun popularMangaParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Returns the request for the search manga given the page. | ||||
| @@ -34,64 +40,64 @@ class EnhancedHttpSource(val originalSource: HttpSource, | ||||
|      * @param query the search query. | ||||
|      * @param filters the list of filters to apply. | ||||
|      */ | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a [MangasPage] object. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun searchMangaParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun searchMangaParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Returns the request for latest manga given the page. | ||||
|      * | ||||
|      * @param page the page number to retrieve. | ||||
|      */ | ||||
|     override fun latestUpdatesRequest(page: Int) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun latestUpdatesRequest(page: Int) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a [MangasPage] object. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun latestUpdatesParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun latestUpdatesParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns the details of a manga. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun mangaDetailsParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun mangaDetailsParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a list of chapters. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun chapterListParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun chapterListParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns a list of pages. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun pageListParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun pageListParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Parses the response from the site and returns the absolute url to the source image. | ||||
|      * | ||||
|      * @param response the response from the site. | ||||
|      */ | ||||
|     override fun imageUrlParse(response: Response) | ||||
|             = throw UnsupportedOperationException("Should never be called!") | ||||
|     override fun imageUrlParse(response: Response) = | ||||
|             throw UnsupportedOperationException("Should never be called!") | ||||
|  | ||||
|     /** | ||||
|      * Base url of the website without the trailing slash, like: http://mysite.com | ||||
| @@ -146,8 +152,8 @@ class EnhancedHttpSource(val originalSource: HttpSource, | ||||
|      * @param query the search query. | ||||
|      * @param filters the list of filters to apply. | ||||
|      */ | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) | ||||
|             = source().fetchSearchManga(page, query, filters) | ||||
|     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = | ||||
|             source().fetchSearchManga(page, query, filters) | ||||
|  | ||||
|     /** | ||||
|      * Returns an observable containing a page with a list of latest manga updates. | ||||
| @@ -202,8 +208,8 @@ class EnhancedHttpSource(val originalSource: HttpSource, | ||||
|      * @param chapter the chapter to be added. | ||||
|      * @param manga the manga of the chapter. | ||||
|      */ | ||||
|     override fun prepareNewChapter(chapter: SChapter, manga: SManga) | ||||
|             = source().prepareNewChapter(chapter, manga) | ||||
|     override fun prepareNewChapter(chapter: SChapter, manga: SManga) = | ||||
|             source().prepareNewChapter(chapter, manga) | ||||
|  | ||||
|     /** | ||||
|      * Returns the list of filters for the source. | ||||
| @@ -211,10 +217,10 @@ class EnhancedHttpSource(val originalSource: HttpSource, | ||||
|     override fun getFilterList() = source().getFilterList() | ||||
|  | ||||
|     private fun source(): HttpSource { | ||||
|         return if(prefs.eh_delegateSources().getOrDefault()) { | ||||
|         return if (prefs.eh_delegateSources().getOrDefault()) { | ||||
|             enchancedSource | ||||
|         } else { | ||||
|             originalSource | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -7,14 +7,14 @@ import com.afollestad.materialdialogs.MaterialDialog | ||||
| import eu.kanade.tachiyomi.ui.base.controller.DialogController | ||||
| import eu.kanade.tachiyomi.util.lang.launchUI | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import timber.log.Timber | ||||
| import kotlin.concurrent.thread | ||||
| import timber.log.Timber | ||||
|  | ||||
| class ConfiguringDialogController : DialogController() { | ||||
|     private var materialDialog: MaterialDialog? = null | ||||
|  | ||||
|     override fun onCreateDialog(savedViewState: Bundle?): Dialog { | ||||
|         if(savedViewState == null) | ||||
|         if (savedViewState == null) | ||||
|             thread { | ||||
|                 try { | ||||
|                     EHConfigurator().configureAll() | ||||
| @@ -62,4 +62,3 @@ class ConfiguringDialogController : DialogController() { | ||||
|         router.popController(this) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -24,11 +24,13 @@ class EHConfigurator { | ||||
|     private fun EHentai.requestWithCreds(sp: Int = 1) = Request.Builder() | ||||
|             .addHeader("Cookie", cookiesHeader(sp)) | ||||
|  | ||||
|     private fun EHentai.execProfileActions(action: String, | ||||
|                                            name: String, | ||||
|                                            set: String, | ||||
|                                            sp: Int) | ||||
|         = configuratorClient.newCall(requestWithCreds(sp) | ||||
|     private fun EHentai.execProfileActions( | ||||
|         action: String, | ||||
|         name: String, | ||||
|         set: String, | ||||
|         sp: Int | ||||
|     ) = | ||||
|         configuratorClient.newCall(requestWithCreds(sp) | ||||
|                 .url(uconfigUrl) | ||||
|                 .post(FormBody.Builder() | ||||
|                         .add("profile_action", action) | ||||
| @@ -44,7 +46,7 @@ class EHConfigurator { | ||||
|         val ehSource = sources.get(EH_SOURCE_ID) as EHentai | ||||
|         val exhSource = sources.get(EXH_SOURCE_ID) as EHentai | ||||
|  | ||||
|         //Get hath perks | ||||
|         // Get hath perks | ||||
|         val perksPage = configuratorClient.newCall(ehSource.requestWithCreds() | ||||
|                 .url(HATH_PERKS_URL) | ||||
|                 .build()) | ||||
| @@ -56,13 +58,13 @@ class EHConfigurator { | ||||
|             val name = it.child(0).text().toLowerCase() | ||||
|             val purchased = it.child(2).getElementsByTag("form").isEmpty() | ||||
|  | ||||
|             when(name) { | ||||
|                 //Thumbnail rows | ||||
|             when (name) { | ||||
|                 // Thumbnail rows | ||||
|                 "more thumbs" -> hathPerks.moreThumbs = purchased | ||||
|                 "thumbs up" -> hathPerks.thumbsUp = purchased | ||||
|                 "all thumbs" -> hathPerks.allThumbs = purchased | ||||
|  | ||||
|                 //Pagination sizing | ||||
|                 // Pagination sizing | ||||
|                 "paging enlargement i" -> hathPerks.pagingEnlargementI = purchased | ||||
|                 "paging enlargement ii" -> hathPerks.pagingEnlargementII = purchased | ||||
|                 "paging enlargement iii" -> hathPerks.pagingEnlargementIII = purchased | ||||
| @@ -76,45 +78,45 @@ class EHConfigurator { | ||||
|     } | ||||
|  | ||||
|     fun configure(source: EHentai, hathPerks: EHHathPerksResponse) { | ||||
|         //Delete old app profiles | ||||
|         // Delete old app profiles | ||||
|         val scanReq = source.requestWithCreds().url(source.uconfigUrl).build() | ||||
|         val resp = configuratorClient.newCall(scanReq).execute().asJsoup() | ||||
|         var lastDoc = resp | ||||
|         resp.select(PROFILE_SELECTOR).forEach { | ||||
|             if(it.text() == PROFILE_NAME) { | ||||
|             if (it.text() == PROFILE_NAME) { | ||||
|                 val id = it.attr("value") | ||||
|                 //Delete old profile | ||||
|                 // Delete old profile | ||||
|                 lastDoc = source.execProfileActions("delete", "", id, id.toInt()).asJsoup() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Find available profile slot | ||||
|         val availableProfiles = (1 .. 3).toMutableList() | ||||
|         // Find available profile slot | ||||
|         val availableProfiles = (1..3).toMutableList() | ||||
|         lastDoc.select(PROFILE_SELECTOR).forEach { | ||||
|             availableProfiles.remove(it.attr("value").toInt()) | ||||
|         } | ||||
|  | ||||
|         //No profile slots left :( | ||||
|         if(availableProfiles.isEmpty()) | ||||
|         // No profile slots left :( | ||||
|         if (availableProfiles.isEmpty()) | ||||
|             throw IllegalStateException("You are out of profile slots on ${source.name}, please delete a profile!") | ||||
|  | ||||
|         //Create profile in available slot | ||||
|         // Create profile in available slot | ||||
|         val slot = availableProfiles.first() | ||||
|         val response = source.execProfileActions("create", | ||||
|                 PROFILE_NAME, | ||||
|                 slot.toString(), | ||||
|                 1) | ||||
|  | ||||
|         //Build new profile | ||||
|         // Build new profile | ||||
|         val form = EhUConfigBuilder().build(hathPerks) | ||||
|  | ||||
|         //Send new profile to server | ||||
|         // Send new profile to server | ||||
|         configuratorClient.newCall(source.requestWithCreds(sp = slot) | ||||
|                 .url(source.uconfigUrl) | ||||
|                 .post(form) | ||||
|                 .build()).execute() | ||||
|  | ||||
|         //Persist slot + sk | ||||
|         // Persist slot + sk | ||||
|         source.spPref().set(slot) | ||||
|  | ||||
|         val keyCookie = response.headers.toMultimap()["Set-Cookie"]?.find { | ||||
| @@ -127,19 +129,19 @@ 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 { | ||||
|         private const val PROFILE_NAME = "TachiyomiEH App" | ||||
|         private const val UCONFIG_URL = "/uconfig.php" | ||||
|         //Always use E-H here as EXH does not have a perks page | ||||
|         // Always use E-H here as EXH does not have a perks page | ||||
|         private const val HATH_PERKS_URL = "https://e-hentai.org/hathperks.php" | ||||
|         private const val PROFILE_SELECTOR = "[name=profile_set] > option" | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,6 @@ class EHHathPerksResponse { | ||||
|     var pagingEnlargementII = false | ||||
|     var pagingEnlargementIII = false | ||||
|  | ||||
|     override fun toString() | ||||
|         = "EHHathPerksResponse(moreThumbs=$moreThumbs, thumbsUp=$thumbsUp, allThumbs=$allThumbs, pagingEnlargementI=$pagingEnlargementI, pagingEnlargementII=$pagingEnlargementII, pagingEnlargementIII=$pagingEnlargementIII)" | ||||
|     override fun toString() = | ||||
|         "EHHathPerksResponse(moreThumbs=$moreThumbs, thumbsUp=$thumbsUp, allThumbs=$allThumbs, pagingEnlargementI=$pagingEnlargementI, pagingEnlargementII=$pagingEnlargementII, pagingEnlargementIII=$pagingEnlargementIII)" | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ class EhUConfigBuilder { | ||||
|     fun build(hathPerks: EHHathPerksResponse): FormBody { | ||||
|         val configItems = mutableListOf<ConfigItem>() | ||||
|  | ||||
|         configItems += when(prefs.imageQuality() | ||||
|         configItems += when (prefs.imageQuality() | ||||
|                 .getOrDefault() | ||||
|                 .toLowerCase()) { | ||||
|             "ovrs_2400" -> Entry.ImageSize.`2400` | ||||
| @@ -23,17 +23,17 @@ class EhUConfigBuilder { | ||||
|             else -> Entry.ImageSize.AUTO | ||||
|         } | ||||
|  | ||||
|         configItems += if(prefs.useHentaiAtHome().getOrDefault()) | ||||
|         configItems += if (prefs.useHentaiAtHome().getOrDefault()) | ||||
|             Entry.UseHentaiAtHome.YES | ||||
|         else | ||||
|             Entry.UseHentaiAtHome.NO | ||||
|  | ||||
|         configItems += if(prefs.useJapaneseTitle().getOrDefault()) | ||||
|         configItems += if (prefs.useJapaneseTitle().getOrDefault()) | ||||
|             Entry.TitleDisplayLanguage.JAPANESE | ||||
|         else | ||||
|             Entry.TitleDisplayLanguage.DEFAULT | ||||
|  | ||||
|         configItems += if(prefs.eh_useOriginalImages().getOrDefault()) | ||||
|         configItems += if (prefs.eh_useOriginalImages().getOrDefault()) | ||||
|             Entry.UseOriginalImages.YES | ||||
|         else | ||||
|             Entry.UseOriginalImages.NO | ||||
| @@ -56,7 +56,7 @@ class EhUConfigBuilder { | ||||
|         configItems += Entry.UseMPV() | ||||
|         configItems += Entry.ShowPopularRightNowPane() | ||||
|  | ||||
|         //Actually build form body | ||||
|         // Actually build form body | ||||
|         val formBody = FormBody.Builder() | ||||
|         configItems.forEach { | ||||
|             formBody.add(it.key, it.value) | ||||
| @@ -67,14 +67,14 @@ class EhUConfigBuilder { | ||||
| } | ||||
|  | ||||
| object Entry { | ||||
|     enum class UseHentaiAtHome(override val value: String): ConfigItem { | ||||
|     enum class UseHentaiAtHome(override val value: String) : ConfigItem { | ||||
|         YES("0"), | ||||
|         NO("1"); | ||||
|  | ||||
|         override val key = "uh" | ||||
|     } | ||||
|  | ||||
|     enum class ImageSize(override val value: String): ConfigItem { | ||||
|     enum class ImageSize(override val value: String) : ConfigItem { | ||||
|         AUTO("0"), | ||||
|         `2400`("5"), | ||||
|         `1600`("4"), | ||||
| @@ -85,20 +85,20 @@ object Entry { | ||||
|         override val key = "xr" | ||||
|     } | ||||
|  | ||||
|     enum class TitleDisplayLanguage(override val value: String): ConfigItem { | ||||
|     enum class TitleDisplayLanguage(override val value: String) : ConfigItem { | ||||
|         DEFAULT("0"), | ||||
|         JAPANESE("1"); | ||||
|  | ||||
|         override val key = "tl" | ||||
|     } | ||||
|  | ||||
|     //Locked to extended mode as that's what the parser and toplists use | ||||
|     class DisplayMode: ConfigItem { | ||||
|     // Locked to extended mode as that's what the parser and toplists use | ||||
|     class DisplayMode : ConfigItem { | ||||
|         override val key = "dm" | ||||
|         override val value = "2" | ||||
|     } | ||||
|  | ||||
|     enum class SearchResultsCount(override val value: String): ConfigItem { | ||||
|     enum class SearchResultsCount(override val value: String) : ConfigItem { | ||||
|         `25`("0"), | ||||
|         `50`("1"), | ||||
|         `100`("2"), | ||||
| @@ -107,7 +107,7 @@ object Entry { | ||||
|         override val key = "rc" | ||||
|     } | ||||
|  | ||||
|     enum class ThumbnailRows(override val value: String): ConfigItem { | ||||
|     enum class ThumbnailRows(override val value: String) : ConfigItem { | ||||
|         `4`("0"), | ||||
|         `10`("1"), | ||||
|         `20`("2"), | ||||
| @@ -116,21 +116,21 @@ object Entry { | ||||
|         override val key = "tr" | ||||
|     } | ||||
|  | ||||
|     enum class UseOriginalImages(override val value: String): ConfigItem { | ||||
|     enum class UseOriginalImages(override val value: String) : ConfigItem { | ||||
|         NO("0"), | ||||
|         YES("1"); | ||||
|  | ||||
|         override val key = "oi" | ||||
|     } | ||||
|  | ||||
|     //Locked to no MPV as that's what the parser uses | ||||
|     class UseMPV: ConfigItem { | ||||
|     // Locked to no MPV as that's what the parser uses | ||||
|     class UseMPV : ConfigItem { | ||||
|         override val key = "qb" | ||||
|         override val value = "0" | ||||
|     } | ||||
|  | ||||
|     //Locked to no popular pane as we can't parse it | ||||
|     class ShowPopularRightNowPane: ConfigItem { | ||||
|     // Locked to no popular pane as we can't parse it | ||||
|     class ShowPopularRightNowPane : ConfigItem { | ||||
|         override val key = "pp" | ||||
|         override val value = "1" | ||||
|     } | ||||
| @@ -139,4 +139,4 @@ object Entry { | ||||
| interface ConfigItem { | ||||
|     val key: String | ||||
|     val value: String | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -32,10 +32,10 @@ class WarnConfigureDialogController : DialogController() { | ||||
|  | ||||
|     companion object { | ||||
|         fun uploadSettings(router: Router) { | ||||
|             if(Injekt.get<PreferencesHelper>().eh_showSettingsUploadWarning().getOrDefault()) | ||||
|             if (Injekt.get<PreferencesHelper>().eh_showSettingsUploadWarning().getOrDefault()) | ||||
|                 WarnConfigureDialogController().showDialog(router) | ||||
|             else | ||||
|                 ConfiguringDialogController().showDialog(router) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| package exh.ui | ||||
|  | ||||
| import java.util.UUID | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlin.coroutines.EmptyCoroutineContext | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import java.util.* | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlin.coroutines.EmptyCoroutineContext | ||||
|  | ||||
| typealias LoadingHandle = String | ||||
|  | ||||
| /** | ||||
|  * Class used to manage loader UIs | ||||
|  */ | ||||
| class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext): CoroutineScope { | ||||
| class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope { | ||||
|     override val coroutineContext = Dispatchers.Main + parentContext | ||||
|  | ||||
|     private val openLoadingHandles = mutableListOf<LoadingHandle>() | ||||
| @@ -25,7 +25,7 @@ class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext): Co | ||||
|             handle to (openLoadingHandles.size == 1) | ||||
|         } | ||||
|  | ||||
|         if(shouldUpdateLoadingStatus) { | ||||
|         if (shouldUpdateLoadingStatus) { | ||||
|             launch { | ||||
|                 updateLoadingStatus(true) | ||||
|             } | ||||
| @@ -36,13 +36,13 @@ class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext): Co | ||||
|  | ||||
|     @Synchronized | ||||
|     fun closeProgressBar(handle: LoadingHandle?) { | ||||
|         if(handle == null) return | ||||
|         if (handle == null) return | ||||
|  | ||||
|         val shouldUpdateLoadingStatus = synchronized(this) { | ||||
|             openLoadingHandles.remove(handle) && openLoadingHandles.isEmpty() | ||||
|         } | ||||
|  | ||||
|         if(shouldUpdateLoadingStatus) { | ||||
|         if (shouldUpdateLoadingStatus) { | ||||
|             launch { | ||||
|                 updateLoadingStatus(false) | ||||
|             } | ||||
|   | ||||
| @@ -6,11 +6,11 @@ import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.annotation.LayoutRes | ||||
| import eu.kanade.tachiyomi.ui.base.controller.BaseController | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.cancel | ||||
| import kotlin.coroutines.CoroutineContext | ||||
|  | ||||
| abstract class BaseExhController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope { | ||||
|     abstract val layoutId: Int | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package exh.ui.batchadd | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| @@ -44,14 +43,14 @@ class BatchAddController : NucleusController<BatchAddPresenter>() { | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|                     .subscribeUntilDestroy { | ||||
|                         progressSubscriptions.clear() | ||||
|                         if(it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) { | ||||
|                         if (it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) { | ||||
|                             showProgress(this) | ||||
|                             progressSubscriptions += presenter.progressRelay | ||||
|                                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|                                     .combineLatest(presenter.progressTotalRelay, { progress, total -> | ||||
|                                         //Show hide dismiss button | ||||
|                                         // Show hide dismiss button | ||||
|                                         progress_dismiss_btn.visibility = | ||||
|                                                 if(progress == total) | ||||
|                                                 if (progress == total) | ||||
|                                                     View.VISIBLE | ||||
|                                                 else View.GONE | ||||
|  | ||||
| @@ -79,7 +78,7 @@ class BatchAddController : NucleusController<BatchAddPresenter>() { | ||||
|                                     }?.let { | ||||
|                                 progressSubscriptions += it | ||||
|                             } | ||||
|                         } else if(it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) { | ||||
|                         } else if (it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) { | ||||
|                             hideProgress(this) | ||||
|                             presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE) | ||||
|                         } | ||||
| @@ -124,8 +123,8 @@ class BatchAddController : NucleusController<BatchAddPresenter>() { | ||||
|     private fun formatProgress(progress: Int, total: Int) = "$progress/$total" | ||||
|  | ||||
|     private fun addGalleries(galleries: String) { | ||||
|         //Check text box has content | ||||
|         if(galleries.isBlank()) { | ||||
|         // Check text box has content | ||||
|         if (galleries.isBlank()) { | ||||
|             noGalleriesSpecified() | ||||
|             return | ||||
|         } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import exh.GalleryAdder | ||||
| import exh.metadata.nullIfBlank | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| class BatchAddPresenter: BasePresenter<BatchAddController>() { | ||||
| class BatchAddPresenter : BasePresenter<BatchAddController>() { | ||||
|  | ||||
|     private val galleryAdder by lazy { GalleryAdder() } | ||||
|  | ||||
| @@ -34,7 +34,7 @@ class BatchAddPresenter: BasePresenter<BatchAddController>() { | ||||
|  | ||||
|             splitGalleries.forEachIndexed { i, s -> | ||||
|                 val result = galleryAdder.addGallery(s, true) | ||||
|                 if(result is GalleryAddEvent.Success) { | ||||
|                 if (result is GalleryAddEvent.Success) { | ||||
|                     succeeded.add(s) | ||||
|                 } else { | ||||
|                     failed.add(s) | ||||
| @@ -46,7 +46,7 @@ class BatchAddPresenter: BasePresenter<BatchAddController>() { | ||||
|                 }) + " " + result.logMessage) | ||||
|             } | ||||
|  | ||||
|             //Show report | ||||
|             // Show report | ||||
|             val summary = "\nSummary:\nAdded: ${succeeded.size} gallerie(s)\nFailed: ${failed.size} gallerie(s)" | ||||
|             eventRelay?.call(summary) | ||||
|         } | ||||
|   | ||||
| @@ -7,21 +7,23 @@ import android.webkit.WebView | ||||
| import androidx.annotation.RequiresApi | ||||
| import eu.kanade.tachiyomi.util.system.asJsoup | ||||
| import exh.ui.captcha.BrowserActionActivity.Companion.CROSS_WINDOW_SCRIPT_INNER | ||||
| import java.nio.charset.Charset | ||||
| import org.jsoup.nodes.DataNode | ||||
| import org.jsoup.nodes.Element | ||||
| import java.nio.charset.Charset | ||||
|  | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| class AutoSolvingWebViewClient(activity: BrowserActionActivity, | ||||
|                                verifyComplete: (String) -> Boolean, | ||||
|                                injectScript: String?, | ||||
|                                headers: Map<String, String>) | ||||
|     : HeadersInjectingWebViewClient(activity, verifyComplete, injectScript, headers) { | ||||
| class AutoSolvingWebViewClient( | ||||
|     activity: BrowserActionActivity, | ||||
|     verifyComplete: (String) -> Boolean, | ||||
|     injectScript: String?, | ||||
|     headers: Map<String, String> | ||||
| ) : | ||||
|     HeadersInjectingWebViewClient(activity, verifyComplete, injectScript, headers) { | ||||
|  | ||||
|     override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { | ||||
|         // Inject our custom script into the recaptcha iframes | ||||
|         val lastPathSegment = request.url.pathSegments.lastOrNull() | ||||
|         if(lastPathSegment == "anchor" ||  lastPathSegment == "bframe") { | ||||
|         if (lastPathSegment == "anchor" || lastPathSegment == "bframe") { | ||||
|             val oReq = request.toOkHttpRequest() | ||||
|             val response = activity.httpClient.newCall(oReq).execute() | ||||
|             val doc = response.asJsoup() | ||||
| @@ -34,4 +36,4 @@ class AutoSolvingWebViewClient(activity: BrowserActionActivity, | ||||
|         } | ||||
|         return super.shouldInterceptRequest(view, request) | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -4,17 +4,19 @@ import android.os.Build | ||||
| import android.webkit.WebView | ||||
| import android.webkit.WebViewClient | ||||
|  | ||||
| open class BasicWebViewClient(protected val activity: BrowserActionActivity, | ||||
|                               protected val verifyComplete: (String) -> Boolean, | ||||
|                               private val injectScript: String?) : WebViewClient() { | ||||
| open class BasicWebViewClient( | ||||
|     protected val activity: BrowserActionActivity, | ||||
|     protected val verifyComplete: (String) -> Boolean, | ||||
|     private val injectScript: String? | ||||
| ) : WebViewClient() { | ||||
|     override fun onPageFinished(view: WebView, url: String) { | ||||
|         super.onPageFinished(view, url) | ||||
|  | ||||
|         if(verifyComplete(url)) { | ||||
|         if (verifyComplete(url)) { | ||||
|             activity.finish() | ||||
|         } else { | ||||
|             if(injectScript != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) | ||||
|             if (injectScript != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) | ||||
|                 view.evaluateJavascript("(function() {$injectScript})();", null) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,12 @@ import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.os.SystemClock | ||||
| import android.view.MotionEvent | ||||
| import android.webkit.* | ||||
| import android.webkit.CookieManager | ||||
| import android.webkit.CookieSyncManager | ||||
| import android.webkit.JavascriptInterface | ||||
| import android.webkit.JsResult | ||||
| import android.webkit.WebChromeClient | ||||
| import android.webkit.WebView | ||||
| import androidx.annotation.RequiresApi | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| @@ -22,6 +27,9 @@ import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import exh.source.DelegatedHttpSource | ||||
| import exh.util.melt | ||||
| import java.io.Serializable | ||||
| import java.net.URL | ||||
| import java.util.UUID | ||||
| import kotlinx.android.synthetic.main.eh_activity_captcha.* | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||
| @@ -33,10 +41,6 @@ import rx.Single | ||||
| import rx.schedulers.Schedulers | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.io.Serializable | ||||
| import java.net.URL | ||||
| import java.util.* | ||||
| import kotlin.collections.HashMap | ||||
|  | ||||
| class BrowserActionActivity : AppCompatActivity() { | ||||
|     private val sourceManager: SourceManager by injectLazy() | ||||
| @@ -58,8 +62,8 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|         setContentView(eu.kanade.tachiyomi.R.layout.eh_activity_captcha) | ||||
|  | ||||
|         val sourceId = intent.getLongExtra(SOURCE_ID_EXTRA, -1) | ||||
|         val originalSource = if(sourceId != -1L) sourceManager.get(sourceId) else null | ||||
|         val source = if(originalSource != null) { | ||||
|         val originalSource = if (sourceId != -1L) sourceManager.get(sourceId) else null | ||||
|         val source = if (originalSource != null) { | ||||
|             originalSource as? ActionCompletionVerifier | ||||
|                     ?: run { | ||||
|                         (originalSource as? HttpSource)?.let { | ||||
| @@ -72,24 +76,24 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|             it.value.joinToString(",") | ||||
|         } ?: emptyMap()) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap()) | ||||
|  | ||||
|         val cookies: HashMap<String, String>? | ||||
|                 = intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String> | ||||
|         val cookies: HashMap<String, String>? = | ||||
|                 intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String> | ||||
|         val script: String? = intent.getStringExtra(SCRIPT_EXTRA) | ||||
|         val url: String? = intent.getStringExtra(URL_EXTRA) | ||||
|         val actionName = intent.getStringExtra(ACTION_NAME_EXTRA) | ||||
|  | ||||
|         val verifyComplete = if(source != null) { | ||||
|         val verifyComplete = if (source != null) { | ||||
|             source::verifyComplete!! | ||||
|         } else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean | ||||
|  | ||||
|         if(verifyComplete == null || url == null) { | ||||
|         if (verifyComplete == null || url == null) { | ||||
|             finish() | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         val actionStr = actionName ?: "Solve captcha" | ||||
|  | ||||
|         toolbar.title = if(source != null) { | ||||
|         toolbar.title = if (source != null) { | ||||
|             "${source.name}: $actionStr" | ||||
|         } else actionStr | ||||
|  | ||||
| @@ -115,13 +119,13 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|  | ||||
|         webview.webChromeClient = object : WebChromeClient() { | ||||
|             override fun onJsAlert(view: WebView?, url: String?, message: String, result: JsResult): Boolean { | ||||
|                 if(message.startsWith("exh-")) { | ||||
|                 if (message.startsWith("exh-")) { | ||||
|                     loadedInners++ | ||||
|                     // Wait for both inner scripts to be loaded | ||||
|                     if(loadedInners >= 2) { | ||||
|                     if (loadedInners >= 2) { | ||||
|                         // Attempt to autosolve captcha | ||||
|                         if(preferencesHelper.eh_autoSolveCaptchas().getOrDefault() | ||||
|                                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||
|                         if (preferencesHelper.eh_autoSolveCaptchas().getOrDefault() && | ||||
|                                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||
|                             webview.post { | ||||
|                                 // 10 seconds to auto-solve captcha | ||||
|                                 strictValidationStartTime = System.currentTimeMillis() + 1000 * 10 | ||||
| @@ -141,7 +145,7 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|         } | ||||
|  | ||||
|         webview.webViewClient = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||
|             if(actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) { | ||||
|             if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) { | ||||
|                 // Fetch auto-solve credentials early for speed | ||||
|                 credentialsObservable = httpClient.newCall(Request.Builder() | ||||
|                         // Rob demo credentials | ||||
| @@ -196,11 +200,11 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|     @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     @JavascriptInterface | ||||
|     fun callback(result: String?, loopId: String, stage: Int) { | ||||
|         if(loopId != currentLoopId) return | ||||
|         if (loopId != currentLoopId) return | ||||
|  | ||||
|         when(stage) { | ||||
|         when (stage) { | ||||
|             STAGE_CHECKBOX -> { | ||||
|                 if(result!!.toBoolean()) { | ||||
|                 if (result!!.toBoolean()) { | ||||
|                     webview.postDelayed({ | ||||
|                         getAudioButtonLocation(loopId) | ||||
|                     }, 250) | ||||
| @@ -211,7 +215,7 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|                 } | ||||
|             } | ||||
|             STAGE_GET_AUDIO_BTN_LOCATION -> { | ||||
|                 if(result != null) { | ||||
|                 if (result != null) { | ||||
|                     val splitResult = result.split(" ").map { it.toFloat() } | ||||
|                     val origX = splitResult[0] | ||||
|                     val origY = splitResult[1] | ||||
| @@ -231,11 +235,11 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|                 } | ||||
|             } | ||||
|             STAGE_DOWNLOAD_AUDIO -> { | ||||
|                 if(result != null) { | ||||
|                 if (result != null) { | ||||
|                     Timber.d("Got audio URL: $result") | ||||
|                     performRecognize(result) | ||||
|                             .observeOn(Schedulers.io()) | ||||
|                             .subscribe ({ | ||||
|                             .subscribe({ | ||||
|                                 Timber.d("Got audio transcript: $it") | ||||
|                                 webview.post { | ||||
|                                     typeResult(loopId, it!! | ||||
| @@ -253,7 +257,7 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|                 } | ||||
|             } | ||||
|             STAGE_TYPE_RESULT -> { | ||||
|                 if(result!!.toBoolean()) { | ||||
|                 if (result!!.toBoolean()) { | ||||
|                     // Fail if captcha still not solved after 1.5s | ||||
|                     strictValidationStartTime = System.currentTimeMillis() + 1500 | ||||
|                 } else { | ||||
| @@ -293,7 +297,7 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|  | ||||
|     @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     fun doStageCheckbox(loopId: String) { | ||||
|         if(loopId != currentLoopId) return | ||||
|         if (loopId != currentLoopId) return | ||||
|  | ||||
|         webview.evaluateJavascript(""" | ||||
|             (function() { | ||||
| @@ -415,27 +419,26 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|         doStageCheckbox(loopId) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     @JavascriptInterface | ||||
|     fun validateCaptchaCallback(result: Boolean, loopId: String) { | ||||
|         if(loopId != validateCurrentLoopId) return | ||||
|         if (loopId != validateCurrentLoopId) return | ||||
|  | ||||
|         if(result) { | ||||
|         if (result) { | ||||
|             Timber.d("Captcha solved!") | ||||
|             webview.post { | ||||
|                 webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE, null) | ||||
|             } | ||||
|             val asbtn = intent.getStringExtra(ASBTN_EXTRA) | ||||
|             if(asbtn != null) { | ||||
|             if (asbtn != null) { | ||||
|                 webview.post { | ||||
|                     webview.evaluateJavascript("(function() {document.querySelector('$asbtn').click();})();", null) | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             val savedStrictValidationStartTime = strictValidationStartTime | ||||
|             if(savedStrictValidationStartTime != null | ||||
|                     && System.currentTimeMillis() > savedStrictValidationStartTime) { | ||||
|             if (savedStrictValidationStartTime != null && | ||||
|                     System.currentTimeMillis() > savedStrictValidationStartTime) { | ||||
|                 captchaSolveFail() | ||||
|             } else { | ||||
|                 webview.postDelayed({ | ||||
| @@ -447,7 +450,7 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|  | ||||
|     @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     fun runValidateCaptcha(loopId: String) { | ||||
|         if(loopId != validateCurrentLoopId) return | ||||
|         if (loopId != validateCurrentLoopId) return | ||||
|  | ||||
|         webview.evaluateJavascript(""" | ||||
|             (function() { | ||||
| @@ -624,12 +627,14 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|                     addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | ||||
|                 } | ||||
|  | ||||
|         fun launchCaptcha(context: Context, | ||||
|                           source: ActionCompletionVerifier, | ||||
|                           cookies: Map<String, String>, | ||||
|                           script: String?, | ||||
|                           url: String, | ||||
|                           autoSolveSubmitBtnSelector: String? = null) { | ||||
|         fun launchCaptcha( | ||||
|             context: Context, | ||||
|             source: ActionCompletionVerifier, | ||||
|             cookies: Map<String, String>, | ||||
|             script: String?, | ||||
|             url: String, | ||||
|             autoSolveSubmitBtnSelector: String? = null | ||||
|         ) { | ||||
|             val intent = baseIntent(context).apply { | ||||
|                 putExtra(SOURCE_ID_EXTRA, source.id) | ||||
|                 putExtra(COOKIES_EXTRA, HashMap(cookies)) | ||||
| @@ -641,9 +646,11 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|             context.startActivity(intent) | ||||
|         } | ||||
|  | ||||
|         fun launchUniversal(context: Context, | ||||
|                             source: HttpSource, | ||||
|                             url: String) { | ||||
|         fun launchUniversal( | ||||
|             context: Context, | ||||
|             source: HttpSource, | ||||
|             url: String | ||||
|         ) { | ||||
|             val intent = baseIntent(context).apply { | ||||
|                 putExtra(SOURCE_ID_EXTRA, source.id) | ||||
|                 putExtra(URL_EXTRA, url) | ||||
| @@ -652,9 +659,11 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|             context.startActivity(intent) | ||||
|         } | ||||
|  | ||||
|         fun launchUniversal(context: Context, | ||||
|                             sourceId: Long, | ||||
|                             url: String) { | ||||
|         fun launchUniversal( | ||||
|             context: Context, | ||||
|             sourceId: Long, | ||||
|             url: String | ||||
|         ) { | ||||
|             val intent = baseIntent(context).apply { | ||||
|                 putExtra(SOURCE_ID_EXTRA, sourceId) | ||||
|                 putExtra(URL_EXTRA, url) | ||||
| @@ -663,11 +672,13 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|             context.startActivity(intent) | ||||
|         } | ||||
|  | ||||
|         fun launchAction(context: Context, | ||||
|                          completionVerifier: ActionCompletionVerifier, | ||||
|                          script: String?, | ||||
|                          url: String, | ||||
|                          actionName: String) { | ||||
|         fun launchAction( | ||||
|             context: Context, | ||||
|             completionVerifier: ActionCompletionVerifier, | ||||
|             script: String?, | ||||
|             url: String, | ||||
|             actionName: String | ||||
|         ) { | ||||
|             val intent = baseIntent(context).apply { | ||||
|                 putExtra(SOURCE_ID_EXTRA, completionVerifier.id) | ||||
|                 putExtra(SCRIPT_EXTRA, script) | ||||
| @@ -678,12 +689,14 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|             context.startActivity(intent) | ||||
|         } | ||||
|  | ||||
|         fun launchAction(context: Context, | ||||
|                          completionVerifier: (String) -> Boolean, | ||||
|                          script: String?, | ||||
|                          url: String, | ||||
|                          actionName: String, | ||||
|                          headers: Map<String, String>? = emptyMap()) { | ||||
|         fun launchAction( | ||||
|             context: Context, | ||||
|             completionVerifier: (String) -> Boolean, | ||||
|             script: String?, | ||||
|             url: String, | ||||
|             actionName: String, | ||||
|             headers: Map<String, String>? = emptyMap() | ||||
|         ) { | ||||
|             val intent = baseIntent(context).apply { | ||||
|                 putExtra(HEADERS_EXTRA, HashMap(headers)) | ||||
|                 putExtra(VERIFY_LAMBDA_EXTRA, completionVerifier as Serializable) | ||||
| @@ -697,7 +710,7 @@ class BrowserActionActivity : AppCompatActivity() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| class NoopActionCompletionVerifier(private val source: HttpSource): DelegatedHttpSource(source), | ||||
| class NoopActionCompletionVerifier(private val source: HttpSource) : DelegatedHttpSource(source), | ||||
|         ActionCompletionVerifier { | ||||
|     override val versionId get() = source.versionId | ||||
|     override val lang: String get() = source.lang | ||||
| @@ -708,4 +721,3 @@ class NoopActionCompletionVerifier(private val source: HttpSource): DelegatedHtt | ||||
| interface ActionCompletionVerifier : Source { | ||||
|     fun verifyComplete(url: String): Boolean | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -7,11 +7,13 @@ import android.webkit.WebView | ||||
| import androidx.annotation.RequiresApi | ||||
|  | ||||
| @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | ||||
| open class HeadersInjectingWebViewClient(activity: BrowserActionActivity, | ||||
|                                          verifyComplete: (String) -> Boolean, | ||||
|                                          injectScript: String?, | ||||
|                                          private val headers: Map<String, String>) | ||||
|     : BasicWebViewClient(activity, verifyComplete, injectScript) { | ||||
| open class HeadersInjectingWebViewClient( | ||||
|     activity: BrowserActionActivity, | ||||
|     verifyComplete: (String) -> Boolean, | ||||
|     injectScript: String?, | ||||
|     private val headers: Map<String, String> | ||||
| ) : | ||||
|     BasicWebViewClient(activity, verifyComplete, injectScript) { | ||||
|  | ||||
|     override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { | ||||
|         // Temp disabled as it's unreliable | ||||
|   | ||||
| @@ -16,4 +16,4 @@ fun WebResourceRequest.toOkHttpRequest(): Request { | ||||
|     } | ||||
|  | ||||
|     return request.build() | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class InterceptActivity : BaseRxActivity<InterceptActivityPresenter>() { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         setContentView(R.layout.eh_activity_intercept) | ||||
|  | ||||
|         //Show back button | ||||
|         // Show back button | ||||
|         setSupportActionBar(toolbar) | ||||
|         supportActionBar?.setDisplayHomeAsUpEnabled(true) | ||||
|  | ||||
| @@ -31,7 +31,7 @@ class InterceptActivity : BaseRxActivity<InterceptActivityPresenter>() { | ||||
|     } | ||||
|  | ||||
|     private fun processLink() { | ||||
|         if(Intent.ACTION_VIEW == intent.action) { | ||||
|         if (Intent.ACTION_VIEW == intent.action) { | ||||
|             intercept_progress.visible() | ||||
|             intercept_status.text = "Loading gallery..." | ||||
|             presenter.loadGallery(intent.dataString) | ||||
| @@ -52,7 +52,7 @@ class InterceptActivity : BaseRxActivity<InterceptActivityPresenter>() { | ||||
|         statusSubscription = presenter.status | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     when(it) { | ||||
|                     when (it) { | ||||
|                         is InterceptResult.Success -> { | ||||
|                             intercept_progress.gone() | ||||
|                             intercept_status.text = "Launching app..." | ||||
|   | ||||
| @@ -3,8 +3,8 @@ package exh.ui.intercept | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import exh.GalleryAddEvent | ||||
| import exh.GalleryAdder | ||||
| import rx.subjects.BehaviorSubject | ||||
| import kotlin.concurrent.thread | ||||
| import rx.subjects.BehaviorSubject | ||||
|  | ||||
| class InterceptActivityPresenter : BasePresenter<InterceptActivity>() { | ||||
|     private val galleryAdder = GalleryAdder() | ||||
| @@ -13,11 +13,11 @@ class InterceptActivityPresenter : BasePresenter<InterceptActivity>() { | ||||
|  | ||||
|     @Synchronized | ||||
|     fun loadGallery(gallery: String) { | ||||
|         //Do not load gallery if already loading | ||||
|         if(status.value is InterceptResult.Idle) { | ||||
|         // Do not load gallery if already loading | ||||
|         if (status.value is InterceptResult.Idle) { | ||||
|             status.onNext(InterceptResult.Loading()) | ||||
|  | ||||
|             //Load gallery async | ||||
|             // Load gallery async | ||||
|             thread { | ||||
|                 val result = galleryAdder.addGallery(gallery) | ||||
|  | ||||
| @@ -35,6 +35,6 @@ class InterceptActivityPresenter : BasePresenter<InterceptActivity>() { | ||||
| sealed class InterceptResult { | ||||
|     class Idle : InterceptResult() | ||||
|     class Loading : InterceptResult() | ||||
|     data class Success(val mangaId: Long): InterceptResult() | ||||
|     data class Failure(val reason: String): InterceptResult() | ||||
| } | ||||
|     data class Success(val mangaId: Long) : InterceptResult() | ||||
|     data class Failure(val reason: String) : InterceptResult() | ||||
| } | ||||
|   | ||||
| @@ -28,21 +28,21 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At | ||||
|     val prefs: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     val fingerprintSupported | ||||
|         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M | ||||
|                 && Reprint.isHardwarePresent() | ||||
|                 && Reprint.hasFingerprintRegistered() | ||||
|         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && | ||||
|                 Reprint.isHardwarePresent() && | ||||
|                 Reprint.hasFingerprintRegistered() | ||||
|  | ||||
|     val useFingerprint | ||||
|         get() = fingerprintSupported | ||||
|                 && prefs.eh_lockUseFingerprint().getOrDefault() | ||||
|         get() = fingerprintSupported && | ||||
|                 prefs.eh_lockUseFingerprint().getOrDefault() | ||||
|  | ||||
|     @SuppressLint("NewApi") | ||||
|     override fun onAttached() { | ||||
|         super.onAttached() | ||||
|         if(fingerprintSupported) { | ||||
|         if (fingerprintSupported) { | ||||
|             updateSummary() | ||||
|             onChange { | ||||
|                 if(it as Boolean) | ||||
|                 if (it as Boolean) | ||||
|                     tryChange() | ||||
|                 else | ||||
|                     prefs.eh_lockUseFingerprint().set(false) | ||||
| @@ -51,7 +51,7 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At | ||||
|         } else { | ||||
|             title = "Fingerprint unsupported" | ||||
|             shouldDisableView = true | ||||
|             summary = if(!Reprint.hasFingerprintRegistered()) | ||||
|             summary = if (!Reprint.hasFingerprintRegistered()) | ||||
|                 "No fingerprints enrolled!" | ||||
|             else | ||||
|                 "Fingerprint unlock is unsupported on this device!" | ||||
| @@ -61,7 +61,7 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At | ||||
|  | ||||
|     private fun updateSummary() { | ||||
|         isChecked = useFingerprint | ||||
|         title = if(isChecked) | ||||
|         title = if (isChecked) | ||||
|             "Fingerprint enabled" | ||||
|         else | ||||
|             "Fingerprint disabled" | ||||
| @@ -146,4 +146,4 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At | ||||
|             subscription.unsubscribe() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package exh.ui.lock | ||||
|  | ||||
| import android.content.Intent | ||||
| import android.view.WindowManager | ||||
| import androidx.fragment.app.FragmentActivity | ||||
| import com.bluelinelabs.conductor.Router | ||||
| @@ -8,7 +7,6 @@ import com.bluelinelabs.conductor.RouterTransaction | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.util.Date | ||||
|  | ||||
| object LockActivityDelegate { | ||||
|     private val preferences by injectLazy<PreferencesHelper>() | ||||
| @@ -20,7 +18,6 @@ object LockActivityDelegate { | ||||
|             .popChangeHandler(LockChangeHandler(animate))) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     fun onCreate(activity: FragmentActivity) { | ||||
|         preferences.secureScreen().asObservable() | ||||
|                 .subscribe { | ||||
| @@ -42,5 +39,4 @@ object LockActivityDelegate { | ||||
|     private fun isAppLocked(router: Router): Boolean { | ||||
|         return router.backstack.lastOrNull()?.controller() is LockController | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -7,10 +7,10 @@ import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import com.bluelinelabs.conductor.ControllerChangeHandler | ||||
| import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler | ||||
| import java.util.* | ||||
| import java.util.ArrayList | ||||
|  | ||||
| class LockChangeHandler : AnimatorChangeHandler { | ||||
|     constructor(): super() | ||||
|     constructor() : super() | ||||
|  | ||||
|     constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush) | ||||
|  | ||||
| @@ -36,6 +36,4 @@ class LockChangeHandler : AnimatorChangeHandler { | ||||
|  | ||||
|     override fun copy(): ControllerChangeHandler = | ||||
|             LockChangeHandler(animationDuration, removesFromViewOnPush()) | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user