Kissmanga loading through Cloudflare. A lot of refactoring was needed
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package eu.kanade.tachiyomi.data.glide
|
||||
|
||||
import android.content.Context
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.module.GlideModule
|
||||
import eu.kanade.tachiyomi.App
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Class used to update Glide module settings
|
||||
*/
|
||||
class AppGlideModule : GlideModule {
|
||||
|
||||
@Inject lateinit var networkHelper: NetworkHelper
|
||||
|
||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||
// Set the cache size of Glide to 15 MiB
|
||||
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024))
|
||||
}
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide) {
|
||||
App.get(context).component.inject(this)
|
||||
glide.register(GlideUrl::class.java, InputStream::class.java,
|
||||
OkHttpUrlLoader.Factory(networkHelper.defaultClient))
|
||||
glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package eu.kanade.tachiyomi.data.glide
|
||||
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* A [DataFetcher] for loading a cover of a manga depending on its favorite status.
|
||||
* If the manga is favorite, it tries to load the cover from our cache, and if it's not found, it
|
||||
* fallbacks to network and copies it to the cache.
|
||||
* If the manga is not favorite, it tries to delete the cover from our cache and always fallback
|
||||
* to network for fetching.
|
||||
*
|
||||
* @param networkFetcher the network fetcher for this cover.
|
||||
* @param file the file where this cover should be. It may exists or not.
|
||||
* @param manga the manga of the cover to load.
|
||||
*/
|
||||
class MangaDataFetcher(private val networkFetcher: DataFetcher<InputStream>,
|
||||
private val file: File,
|
||||
private val manga: Manga)
|
||||
: DataFetcher<InputStream> {
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun loadData(priority: Priority): InputStream? {
|
||||
if (manga.favorite) {
|
||||
if (!file.exists()) {
|
||||
file.parentFile.mkdirs()
|
||||
networkFetcher.loadData(priority)?.let {
|
||||
it.use { input ->
|
||||
file.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FileInputStream(file)
|
||||
} else {
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
return networkFetcher.loadData(priority)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id for this manga's cover.
|
||||
*
|
||||
* Appending the file's modified date to the url, we can force Glide to skip its memory and disk
|
||||
* lookup step and fetch from our custom cache. This allows us to invalidate Glide's cache when
|
||||
* the file has changed. If the file doesn't exist it will append a 0.
|
||||
*/
|
||||
override fun getId(): String {
|
||||
return manga.thumbnail_url + file.lastModified()
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
networkFetcher.cancel()
|
||||
}
|
||||
|
||||
override fun cleanup() {
|
||||
networkFetcher.cleanup()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package eu.kanade.tachiyomi.data.glide
|
||||
|
||||
import android.content.Context
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.model.*
|
||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
||||
import eu.kanade.tachiyomi.App
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
||||
* Coupled with [MangaDataFetcher], this class allows to implement the following flow:
|
||||
*
|
||||
* - Check in RAM LRU.
|
||||
* - Check in disk LRU.
|
||||
* - Check in this module.
|
||||
* - Fetch from the network connection.
|
||||
*
|
||||
* @param context the application context.
|
||||
*/
|
||||
class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||
|
||||
/**
|
||||
* Cover cache where persistent covers are stored.
|
||||
*/
|
||||
@Inject lateinit var coverCache: CoverCache
|
||||
|
||||
/**
|
||||
* Source manager.
|
||||
*/
|
||||
@Inject lateinit var sourceManager: SourceManager
|
||||
|
||||
/**
|
||||
* Base network loader.
|
||||
*/
|
||||
private val baseLoader = Glide.buildModelLoader(GlideUrl::class.java,
|
||||
InputStream::class.java, context)
|
||||
|
||||
/**
|
||||
* LRU cache whose key is the thumbnail url of the manga, and the value contains the request url
|
||||
* and the file where it should be stored in case the manga is a favorite.
|
||||
*/
|
||||
private val modelCache = ModelCache<String, Pair<GlideUrl, File>>(100)
|
||||
|
||||
/**
|
||||
* Map where request headers are stored for a source.
|
||||
*/
|
||||
private val cachedHeaders = hashMapOf<Int, LazyHeaders>()
|
||||
|
||||
init {
|
||||
App.get(context).component.inject(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory class for creating [MangaModelLoader] instances.
|
||||
*/
|
||||
class Factory : ModelLoaderFactory<Manga, InputStream> {
|
||||
|
||||
override fun build(context: Context, factories: GenericLoaderFactory)
|
||||
= MangaModelLoader(context)
|
||||
|
||||
override fun teardown() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [MangaDataFetcher] for the given manga or null if the url is empty.
|
||||
*
|
||||
* @param manga the model.
|
||||
* @param width the width of the view where the resource will be loaded.
|
||||
* @param height the height of the view where the resource will be loaded.
|
||||
*/
|
||||
override fun getResourceFetcher(manga: Manga,
|
||||
width: Int,
|
||||
height: Int): DataFetcher<InputStream>? {
|
||||
// Check thumbnail is not null or empty
|
||||
val url = manga.thumbnail_url
|
||||
if (url.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Obtain the request url and the file for this url from the LRU cache, or calculate it
|
||||
// and add them to the cache.
|
||||
val (glideUrl, file) = modelCache.get(url, width, height) ?:
|
||||
Pair(GlideUrl(url, getHeaders(manga)), coverCache.getCoverFile(url)).apply {
|
||||
modelCache.put(url, width, height, this)
|
||||
}
|
||||
|
||||
// Get the network fetcher for this request url.
|
||||
val networkFetcher = baseLoader.getResourceFetcher(glideUrl, width, height)
|
||||
|
||||
// Return an instance of our fetcher providing the needed elements.
|
||||
return MangaDataFetcher(networkFetcher, file, manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request headers for a source copying its OkHttp headers and caching them.
|
||||
*
|
||||
* @param manga the model.
|
||||
*/
|
||||
fun getHeaders(manga: Manga): LazyHeaders {
|
||||
return cachedHeaders.getOrPut(manga.source) {
|
||||
val source = sourceManager.get(manga.source)!!
|
||||
|
||||
LazyHeaders.Builder().apply {
|
||||
for ((key, value) in source.requestHeaders.toMultimap()) {
|
||||
addHeader(key, value[0])
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user