Allow glide to use source's network client. Catalogue fixes
This commit is contained in:
parent
dd8cab4562
commit
ad6cdc9017
6 changed files with 51 additions and 74 deletions
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.glide
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
import com.bumptech.glide.load.model.*
|
import com.bumptech.glide.load.model.*
|
||||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
||||||
|
@ -89,15 +90,18 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.startsWith("http")) {
|
if (url.startsWith("http")) {
|
||||||
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
|
|
||||||
// Obtain the request url and the file for this url from the LRU cache, or calculate it
|
// Obtain the request url and the file for this url from the LRU cache, or calculate it
|
||||||
// and add them to the cache.
|
// and add them to the cache.
|
||||||
val (glideUrl, file) = lruCache.get(url) ?:
|
val (glideUrl, file) = lruCache.get(url) ?:
|
||||||
Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url)).apply {
|
Pair(GlideUrl(url, getHeaders(manga, source)), coverCache.getCoverFile(url)).apply {
|
||||||
lruCache.put(url, this)
|
lruCache.put(url, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the resource fetcher for this request url.
|
// Get the resource fetcher for this request url.
|
||||||
val networkFetcher = baseUrlLoader.getResourceFetcher(glideUrl, width, height)
|
val networkFetcher = source?.let { OkHttpStreamFetcher(it.client, glideUrl) }
|
||||||
|
?: baseUrlLoader.getResourceFetcher(glideUrl, width, height)
|
||||||
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
// Return an instance of the fetcher providing the needed elements.
|
||||||
return MangaUrlFetcher(networkFetcher, file, manga)
|
return MangaUrlFetcher(networkFetcher, file, manga)
|
||||||
|
@ -118,8 +122,9 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
*
|
*
|
||||||
* @param manga the model.
|
* @param manga the model.
|
||||||
*/
|
*/
|
||||||
fun getHeaders(manga: Manga): Headers {
|
fun getHeaders(manga: Manga, source: HttpSource?): Headers {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return LazyHeaders.DEFAULT
|
if (source == null) return LazyHeaders.DEFAULT
|
||||||
|
|
||||||
return cachedHeaders.getOrPut(manga.source) {
|
return cachedHeaders.getOrPut(manga.source) {
|
||||||
LazyHeaders.Builder().apply {
|
LazyHeaders.Builder().apply {
|
||||||
val nullStr: String? = null
|
val nullStr: String? = null
|
||||||
|
|
|
@ -6,7 +6,7 @@ import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
|
||||||
class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interceptor {
|
class CloudflareInterceptor : Interceptor {
|
||||||
|
|
||||||
//language=RegExp
|
//language=RegExp
|
||||||
private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var (?:\w,)+f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""")
|
private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var (?:\w,)+f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""")
|
||||||
|
@ -17,18 +17,12 @@ class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interc
|
||||||
//language=RegExp
|
//language=RegExp
|
||||||
private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""")
|
private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""")
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val response = chain.proceed(chain.request())
|
val response = chain.proceed(chain.request())
|
||||||
|
|
||||||
// Check if we already solved a challenge
|
|
||||||
if (response.code() != 503 &&
|
|
||||||
cookies.get(response.request().url()).any { it.name() == "cf_clearance" }) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Cloudflare anti-bot is on
|
// Check if Cloudflare anti-bot is on
|
||||||
if ("URL=/cdn-cgi/" in response.header("Refresh", "")
|
if (response.code() == 503 && "cloudflare-nginx" == response.header("Server")) {
|
||||||
&& response.header("Server", "") == "cloudflare-nginx") {
|
|
||||||
return chain.proceed(resolveChallenge(response))
|
return chain.proceed(resolveChallenge(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +30,10 @@ class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interc
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveChallenge(response: Response): Request {
|
private fun resolveChallenge(response: Response): Request {
|
||||||
val duktape = Duktape.create()
|
Duktape.create().use { duktape ->
|
||||||
try {
|
|
||||||
val originalRequest = response.request()
|
val originalRequest = response.request()
|
||||||
val domain = originalRequest.url().host()
|
val url = originalRequest.url()
|
||||||
|
val domain = url.host()
|
||||||
val content = response.body().string()
|
val content = response.body().string()
|
||||||
|
|
||||||
// CloudFlare requires waiting 4 seconds before resolving the challenge
|
// CloudFlare requires waiting 4 seconds before resolving the challenge
|
||||||
|
@ -64,16 +58,19 @@ class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interc
|
||||||
|
|
||||||
val answer = "${result + domain.length}"
|
val answer = "${result + domain.length}"
|
||||||
|
|
||||||
val url = HttpUrl.parse("http://$domain/cdn-cgi/l/chk_jschl").newBuilder()
|
val cloudflareUrl = HttpUrl.parse("${url.scheme()}://$domain/cdn-cgi/l/chk_jschl")
|
||||||
|
.newBuilder()
|
||||||
.addQueryParameter("jschl_vc", challenge)
|
.addQueryParameter("jschl_vc", challenge)
|
||||||
.addQueryParameter("pass", pass)
|
.addQueryParameter("pass", pass)
|
||||||
.addQueryParameter("jschl_answer", answer)
|
.addQueryParameter("jschl_answer", answer)
|
||||||
.toString()
|
.toString()
|
||||||
|
|
||||||
val referer = originalRequest.url().toString()
|
val cloudflareHeaders = originalRequest.headers()
|
||||||
return GET(url, originalRequest.headers().newBuilder().add("Referer", referer).build())
|
.newBuilder()
|
||||||
} finally {
|
.add("Referer", url.toString())
|
||||||
duktape.close()
|
.build()
|
||||||
|
|
||||||
|
return GET(cloudflareUrl, cloudflareHeaders)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ class NetworkHelper(context: Context) {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val cloudflareClient = client.newBuilder()
|
val cloudflareClient = client.newBuilder()
|
||||||
.addInterceptor(CloudflareInterceptor(cookies))
|
.addInterceptor(CloudflareInterceptor())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val cookies: PersistentCookieStore
|
val cookies: PersistentCookieStore
|
||||||
|
|
|
@ -28,38 +28,36 @@ class PersistentCookieStore(context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun addAll(url: HttpUrl, cookies: List<Cookie>) {
|
fun addAll(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
synchronized(this) {
|
val key = url.uri().host
|
||||||
val key = url.uri().host
|
|
||||||
|
|
||||||
// Append or replace the cookies for this domain.
|
// Append or replace the cookies for this domain.
|
||||||
val cookiesForDomain = cookieMap[key].orEmpty().toMutableList()
|
val cookiesForDomain = cookieMap[key].orEmpty().toMutableList()
|
||||||
for (cookie in cookies) {
|
for (cookie in cookies) {
|
||||||
// Find a cookie with the same name. Replace it if found, otherwise add a new one.
|
// Find a cookie with the same name. Replace it if found, otherwise add a new one.
|
||||||
val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() }
|
val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() }
|
||||||
if (pos == -1) {
|
if (pos == -1) {
|
||||||
cookiesForDomain.add(cookie)
|
cookiesForDomain.add(cookie)
|
||||||
} else {
|
} else {
|
||||||
cookiesForDomain[pos] = cookie
|
cookiesForDomain[pos] = cookie
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cookieMap.put(key, cookiesForDomain)
|
|
||||||
|
|
||||||
// Get cookies to be stored in disk
|
|
||||||
val newValues = cookiesForDomain.asSequence()
|
|
||||||
.filter { it.persistent() && !it.hasExpired() }
|
|
||||||
.map { it.toString() }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
prefs.edit().putStringSet(key, newValues).apply()
|
|
||||||
}
|
}
|
||||||
|
cookieMap.put(key, cookiesForDomain)
|
||||||
|
|
||||||
|
// Get cookies to be stored in disk
|
||||||
|
val newValues = cookiesForDomain.asSequence()
|
||||||
|
.filter { it.persistent() && !it.hasExpired() }
|
||||||
|
.map(Cookie::toString)
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
prefs.edit().putStringSet(key, newValues).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun removeAll() {
|
fun removeAll() {
|
||||||
synchronized(this) {
|
prefs.edit().clear().apply()
|
||||||
prefs.edit().clear().apply()
|
cookieMap.clear()
|
||||||
cookieMap.clear()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(url: HttpUrl) = get(url.uri().host)
|
fun get(url: HttpUrl) = get(url.uri().host)
|
||||||
|
|
|
@ -16,7 +16,6 @@ import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.online.LoginSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
import eu.kanade.tachiyomi.ui.manga.MangaActivity
|
||||||
|
@ -151,14 +150,6 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(),
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
// If the source list is empty or it only has unlogged sources, return to main screen.
|
|
||||||
val sources = presenter.sources
|
|
||||||
if (sources.isEmpty() || sources.all { it is LoginSource && !it.isLogged() }) {
|
|
||||||
context.toast(R.string.no_valid_sources)
|
|
||||||
activity.onBackPressed()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize adapter, scroll listener and recycler views
|
// Initialize adapter, scroll listener and recycler views
|
||||||
adapter = FlexibleAdapter(null, this)
|
adapter = FlexibleAdapter(null, this)
|
||||||
setupRecycler()
|
setupRecycler()
|
||||||
|
|
|
@ -24,7 +24,6 @@ import rx.schedulers.Schedulers
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [CatalogueFragment].
|
* Presenter of [CatalogueFragment].
|
||||||
|
@ -118,12 +117,8 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
try {
|
source = getLastUsedSource()
|
||||||
source = getLastUsedSource()
|
sourceFilters = source.getFilterList()
|
||||||
sourceFilters = source.getFilterList()
|
|
||||||
} catch (error: NoSuchElementException) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
query = savedState.getString(CataloguePresenter::query.name, "")
|
query = savedState.getString(CataloguePresenter::query.name, "")
|
||||||
|
@ -291,8 +286,8 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
||||||
fun getLastUsedSource(): CatalogueSource {
|
fun getLastUsedSource(): CatalogueSource {
|
||||||
val id = prefs.lastUsedCatalogueSource().get() ?: -1
|
val id = prefs.lastUsedCatalogueSource().get() ?: -1
|
||||||
val source = sourceManager.get(id)
|
val source = sourceManager.get(id)
|
||||||
if (!isValidSource(source)) {
|
if (!isValidSource(source) || source !in sources) {
|
||||||
return findFirstValidSource()
|
return sources.first { isValidSource(it) }
|
||||||
}
|
}
|
||||||
return source as CatalogueSource
|
return source as CatalogueSource
|
||||||
}
|
}
|
||||||
|
@ -313,15 +308,6 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the first valid source.
|
|
||||||
*
|
|
||||||
* @return the index of the first valid source.
|
|
||||||
*/
|
|
||||||
fun findFirstValidSource(): CatalogueSource {
|
|
||||||
return sources.first { isValidSource(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of enabled sources ordered by language and name.
|
* Returns a list of enabled sources ordered by language and name.
|
||||||
*/
|
*/
|
||||||
|
|
Reference in a new issue