diff --git a/app/build.gradle b/app/build.gradle index 2afcc1412..c8c88d5c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -118,7 +118,6 @@ dependencies { // Network client compile "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION" - compile "com.squareup.okhttp3:okhttp-urlconnection:$OKHTTP_VERSION" // REST compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" @@ -131,6 +130,9 @@ dependencies { // JSON compile 'com.google.code.gson:gson:2.6.2' + // JavaScript engine + compile 'com.squareup.duktape:duktape-android:0.9.5' + // Disk cache compile 'com.jakewharton:disklrucache:2.0.2' @@ -154,6 +156,7 @@ dependencies { // Image library compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' // Logging compile 'com.jakewharton.timber:timber:4.1.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 00da616d5..503af4860 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,7 +101,7 @@ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index 3fbce3d06..9e1b1556c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -1,11 +1,6 @@ package eu.kanade.tachiyomi.data.cache import android.content.Context -import com.bumptech.glide.Glide -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.load.model.LazyHeaders -import com.bumptech.glide.request.animation.GlideAnimation -import com.bumptech.glide.request.target.SimpleTarget import eu.kanade.tachiyomi.util.DiskUtils import java.io.File import java.io.IOException @@ -27,80 +22,19 @@ class CoverCache(private val context: Context) { */ private val cacheDir: File = File(context.externalCacheDir, "cover_disk_cache") - /** - * Download the cover with Glide and save the file. - * @param thumbnailUrl url of thumbnail. - * @param headers headers included in Glide request. - * @param onReady function to call when the image is ready - */ - fun save(thumbnailUrl: String?, headers: LazyHeaders?, onReady: ((File) -> Unit)? = null) { - // Check if url is empty. - if (thumbnailUrl.isNullOrEmpty()) - return - - // Download the cover with Glide and save the file. - val url = GlideUrl(thumbnailUrl, headers) - Glide.with(context) - .load(url) - .downloadOnly(object : SimpleTarget() { - override fun onResourceReady(resource: File, anim: GlideAnimation) { - try { - // Copy the cover from Glide's cache to local cache. - copyToCache(thumbnailUrl!!, resource) - - onReady?.invoke(resource) - } catch (e: IOException) { - // Do nothing. - } - } - }) - } - - /** - * Save or load the image from cache - * @param thumbnailUrl the thumbnail url. - * @param headers headers included in Glide request. - * @param onReady function to call when the image is ready - */ - fun saveOrLoadFromCache(thumbnailUrl: String?, headers: LazyHeaders?, onReady: ((File) -> Unit)?) { - // Check if url is empty. - if (thumbnailUrl.isNullOrEmpty()) - return - - // If file exist load it otherwise save it. - val localCover = getCoverFromCache(thumbnailUrl!!) - if (localCover.exists()) { - onReady?.invoke(localCover) - } else { - save(thumbnailUrl, headers, onReady) - } - } - /** * Returns the cover from cache. + * * @param thumbnailUrl the thumbnail url. * @return cover image. */ - private fun getCoverFromCache(thumbnailUrl: String): File { + fun getCoverFile(thumbnailUrl: String): File { return File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)) } - /** - * Copy the given file to this cache. - * @param thumbnailUrl url of thumbnail. - * @param sourceFile the source file of the cover image. - * @throws IOException if there's any error. - */ - @Throws(IOException::class) - fun copyToCache(thumbnailUrl: String, sourceFile: File) { - // Get destination file. - val destFile = getCoverFromCache(thumbnailUrl) - - sourceFile.copyTo(destFile, overwrite = true) - } - /** * Copy the given stream to this cache. + * * @param thumbnailUrl url of the thumbnail. * @param inputStream the stream to copy. * @throws IOException if there's any error. @@ -108,13 +42,14 @@ class CoverCache(private val context: Context) { @Throws(IOException::class) fun copyToCache(thumbnailUrl: String, inputStream: InputStream) { // Get destination file. - val destFile = getCoverFromCache(thumbnailUrl) + val destFile = getCoverFile(thumbnailUrl) destFile.outputStream().use { inputStream.copyTo(it) } } /** * Delete the cover file from the cache. + * * @param thumbnailUrl the thumbnail url. * @return status of deletion. */ @@ -124,7 +59,7 @@ class CoverCache(private val context: Context) { return false // Remove file. - val file = File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)) + val file = getCoverFile(thumbnailUrl!!) return file.exists() && file.delete() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.kt deleted file mode 100644 index 3e7504802..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.kt +++ /dev/null @@ -1,22 +0,0 @@ -package eu.kanade.tachiyomi.data.cache - -import android.content.Context -import com.bumptech.glide.Glide -import com.bumptech.glide.GlideBuilder -import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory -import com.bumptech.glide.module.GlideModule - -/** - * Class used to update Glide module settings - */ -class CoverGlideModule : GlideModule { - - 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) { - // Nothing to see here! - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt new file mode 100644 index 000000000..9ad04315f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt @@ -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()) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaDataFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaDataFetcher.kt new file mode 100644 index 000000000..8e1b0ee56 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaDataFetcher.kt @@ -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, + private val file: File, + private val manga: Manga) +: DataFetcher { + + @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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt new file mode 100644 index 000000000..9402c8fab --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt @@ -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 { + + /** + * 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>(100) + + /** + * Map where request headers are stored for a source. + */ + private val cachedHeaders = hashMapOf() + + init { + App.get(context).component.inject(this) + } + + /** + * Factory class for creating [MangaModelLoader] instances. + */ + class Factory : ModelLoaderFactory { + + 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? { + // 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() + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/services/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/services/MyAnimeList.kt index bded0ade8..b4f9e7aa9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/services/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/mangasync/services/MyAnimeList.kt @@ -102,7 +102,7 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont // MAL doesn't support score with decimals fun getList(): Observable> { - return networkService.requestBody(get(getListUrl(username), headers), true) + return networkService.requestBody(get(getListUrl(username), headers), networkService.forceCacheClient) .map { Jsoup.parse(it) } .flatMap { Observable.from(it.select("manga")) } .map { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareScraper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareScraper.kt new file mode 100644 index 000000000..21cca0f1a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareScraper.kt @@ -0,0 +1,81 @@ +package eu.kanade.tachiyomi.data.network + +import android.net.Uri +import com.squareup.duktape.Duktape +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response + +object CloudflareScraper { + + //language=RegExp + private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var t,r,a,f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""") + + //language=RegExp + private val passPattern = Regex("""name="pass" value="(.+?)"""") + + //language=RegExp + private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""") + + fun request(chain: Interceptor.Chain, cookies: PersistentCookieStore): Response { + val response = chain.proceed(chain.request()) + + // Check if we already solved a challenge + if (response.code() != 502 && + cookies.get(response.request().url()).find { it.name() == "cf_clearance" } != null) { + return response + } + + // Check if Cloudflare anti-bot is on + if ("URL=/cdn-cgi/" in response.header("Refresh", "") + && response.header("Server", "") == "cloudflare-nginx") { + return chain.proceed(resolveChallenge(response)) + } + + return response + } + + private fun resolveChallenge(response: Response): Request { + val duktape = Duktape.create() + try { + val originalRequest = response.request() + val domain = originalRequest.url().host() + val content = response.body().string() + + // CloudFlare requires waiting 5 seconds before resolving the challenge + Thread.sleep(5000) + + val operation = operationPattern.find(content)?.groups?.get(1)?.value + val challenge = challengePattern.find(content)?.groups?.get(1)?.value + val pass = passPattern.find(content)?.groups?.get(1)?.value + + if (operation == null || challenge == null || pass == null) { + throw RuntimeException("Failed resolving Cloudflare challenge") + } + + val js = operation + //language=RegExp + .replace(Regex("""a\.value =(.+?) \+ .+?;"""), "$1") + //language=RegExp + .replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "") + .replace("\n", "") + + // Duktape can only return strings, so the result has to be converted to string first + val result = duktape.evaluate("$js.toString()").toInt() + + val answer = "${result + domain.length}" + + val url = Uri.parse("http://$domain/cdn-cgi/l/chk_jschl").buildUpon() + .appendQueryParameter("jschl_vc", challenge) + .appendQueryParameter("pass", pass) + .appendQueryParameter("jschl_answer", answer) + .toString() + + val referer = originalRequest.url().toString() + return get(url, originalRequest.headers().newBuilder().add("Referer", referer).build()) + } finally { + duktape.close() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt index e37905e1d..33eed5ffa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt @@ -1,12 +1,12 @@ package eu.kanade.tachiyomi.data.network import android.content.Context -import okhttp3.* +import okhttp3.Cache +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import rx.Observable import java.io.File -import java.net.CookieManager -import java.net.CookiePolicy -import java.net.CookieStore class NetworkHelper(context: Context) { @@ -14,43 +14,41 @@ class NetworkHelper(context: Context) { private val cacheSize = 5L * 1024 * 1024 // 5 MiB - private val cookieManager = CookieManager().apply { - setCookiePolicy(CookiePolicy.ACCEPT_ALL) - } + private val cookieManager = PersistentCookieJar(context) - private val forceCacheInterceptor = { chain: Interceptor.Chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .removeHeader("Pragma") - .header("Cache-Control", "max-age=" + 600) - .build() - } - - private val client = OkHttpClient.Builder() - .cookieJar(JavaNetCookieJar(cookieManager)) + val defaultClient = OkHttpClient.Builder() + .cookieJar(cookieManager) .cache(Cache(cacheDir, cacheSize)) .build() - private val forceCacheClient = client.newBuilder() - .addNetworkInterceptor(forceCacheInterceptor) + val forceCacheClient = defaultClient.newBuilder() + .addNetworkInterceptor({ chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .removeHeader("Pragma") + .header("Cache-Control", "max-age=" + 600) + .build() + }) .build() - val cookies: CookieStore - get() = cookieManager.cookieStore + val cloudflareClient = defaultClient.newBuilder() + .addInterceptor { CloudflareScraper.request(it, cookies) } + .build() + + val cookies: PersistentCookieStore + get() = cookieManager.store @JvmOverloads - fun request(request: Request, forceCache: Boolean = false): Observable { + fun request(request: Request, client: OkHttpClient = defaultClient): Observable { return Observable.fromCallable { - val c = if (forceCache) forceCacheClient else client - c.newCall(request).execute().apply { body().close() } + client.newCall(request).execute().apply { body().close() } } } @JvmOverloads - fun requestBody(request: Request, forceCache: Boolean = false): Observable { + fun requestBody(request: Request, client: OkHttpClient = defaultClient): Observable { return Observable.fromCallable { - val c = if (forceCache) forceCacheClient else client - c.newCall(request).execute().body().string() + client.newCall(request).execute().body().string() } } @@ -59,7 +57,7 @@ class NetworkHelper(context: Context) { } fun requestBodyProgressBlocking(request: Request, listener: ProgressListener): Response { - val progressClient = client.newBuilder() + val progressClient = defaultClient.newBuilder() .cache(null) .addNetworkInterceptor { chain -> val originalResponse = chain.proceed(chain.request()) @@ -72,5 +70,4 @@ class NetworkHelper(context: Context) { return progressClient.newCall(request).execute() } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieJar.kt new file mode 100644 index 000000000..a53bc39c0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieJar.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.data.network + +import android.content.Context +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl + +class PersistentCookieJar(context: Context) : CookieJar { + + val store = PersistentCookieStore(context) + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + store.addAll(url, cookies) + } + + override fun loadForRequest(url: HttpUrl): List { + return store.get(url) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieStore.kt new file mode 100644 index 000000000..498e56f96 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieStore.kt @@ -0,0 +1,75 @@ +package eu.kanade.tachiyomi.data.network + +import android.content.Context +import okhttp3.Cookie +import okhttp3.HttpUrl +import java.net.URI +import java.util.concurrent.ConcurrentHashMap + +class PersistentCookieStore(context: Context) { + + private val cookieMap = ConcurrentHashMap>() + private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE) + + init { + for ((key, value) in prefs.all) { + @Suppress("UNCHECKED_CAST") + val cookies = value as? Set + if (cookies != null) { + try { + val url = HttpUrl.parse("http://$key") + val nonExpiredCookies = cookies.map { Cookie.parse(url, it) } + .filter { !it.hasExpired() } + cookieMap.put(key, nonExpiredCookies) + } catch (e: Exception) { + // Ignore + } + } + } + } + + fun addAll(url: HttpUrl, cookies: List) { + synchronized(this) { + val key = url.uri().host + + // Append or replace the cookies for this domain. + val cookiesForDomain = cookieMap[key].orEmpty().toMutableList() + for (cookie in cookies) { + // Find a cookie with the same name. Replace it if found, otherwise add a new one. + val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() } + if (pos == -1) { + cookiesForDomain.add(cookie) + } else { + 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() + } + } + + fun removeAll() { + synchronized(this) { + prefs.edit().clear().apply() + cookieMap.clear() + } + } + + fun get(url: HttpUrl) = get(url.uri().host) + + fun get(uri: URI) = get(uri.host) + + private fun get(url: String): List { + return cookieMap[url].orEmpty().filter { !it.hasExpired() } + } + + fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt() + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt index 23b87aaf9..cdb54a353 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.source.base import android.content.Context -import com.bumptech.glide.load.model.LazyHeaders import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.models.Chapter @@ -11,6 +10,7 @@ import eu.kanade.tachiyomi.data.network.get import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page +import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.Jsoup @@ -27,12 +27,13 @@ abstract class Source(context: Context) : BaseSource() { val requestHeaders by lazy { headersBuilder().build() } - val glideHeaders by lazy { glideHeadersBuilder().build() } - init { App.get(context).component.inject(this) } + open val networkClient: OkHttpClient + get() = networkService.defaultClient + override fun isLoginRequired(): Boolean { return false } @@ -75,7 +76,7 @@ abstract class Source(context: Context) : BaseSource() { // Get the most popular mangas from the source open fun pullPopularMangasFromNetwork(page: MangasPage): Observable { - return networkService.requestBody(popularMangaRequest(page), true) + return networkService.requestBody(popularMangaRequest(page), networkClient) .map { Jsoup.parse(it) } .doOnNext { doc -> page.mangas = parsePopularMangasFromHtml(doc) } .doOnNext { doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page) } @@ -84,7 +85,7 @@ abstract class Source(context: Context) : BaseSource() { // Get mangas from the source with a query open fun searchMangasFromNetwork(page: MangasPage, query: String): Observable { - return networkService.requestBody(searchMangaRequest(page, query), true) + return networkService.requestBody(searchMangaRequest(page, query), networkClient) .map { Jsoup.parse(it) } .doOnNext { doc -> page.mangas = parseSearchFromHtml(doc) } .doOnNext { doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query) } @@ -93,13 +94,13 @@ abstract class Source(context: Context) : BaseSource() { // Get manga details from the source open fun pullMangaFromNetwork(mangaUrl: String): Observable { - return networkService.requestBody(mangaDetailsRequest(mangaUrl)) + return networkService.requestBody(mangaDetailsRequest(mangaUrl), networkClient) .flatMap { Observable.just(parseHtmlToManga(mangaUrl, it)) } } // Get chapter list of a manga from the source open fun pullChaptersFromNetwork(mangaUrl: String): Observable> { - return networkService.requestBody(chapterListRequest(mangaUrl)) + return networkService.requestBody(chapterListRequest(mangaUrl), networkClient) .flatMap { unparsedHtml -> val chapters = parseHtmlToChapters(unparsedHtml) if (!chapters.isEmpty()) @@ -116,7 +117,7 @@ abstract class Source(context: Context) : BaseSource() { } open fun pullPageListFromNetwork(chapterUrl: String): Observable> { - return networkService.requestBody(pageListRequest(chapterUrl)) + return networkService.requestBody(pageListRequest(chapterUrl), networkClient) .flatMap { unparsedHtml -> val pages = convertToPages(parseHtmlToPageUrls(unparsedHtml)) if (!pages.isEmpty()) @@ -141,7 +142,7 @@ abstract class Source(context: Context) : BaseSource() { open fun getImageUrlFromPage(page: Page): Observable { page.status = Page.LOAD_PAGE - return networkService.requestBody(imageUrlRequest(page)) + return networkService.requestBody(imageUrlRequest(page), networkClient) .flatMap { unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)) } .onErrorResumeNext { e -> page.status = Page.ERROR @@ -224,13 +225,4 @@ abstract class Source(context: Context) : BaseSource() { } - protected open fun glideHeadersBuilder(): LazyHeaders.Builder { - val builder = LazyHeaders.Builder() - for ((key, value) in requestHeaders.toMultimap()) { - builder.addHeader(key, value[0]) - } - - return builder - } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java index 66986103e..ae094dacf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java @@ -10,7 +10,6 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import java.net.HttpCookie; import java.net.URI; import java.net.URISyntaxException; import java.text.ParseException; @@ -34,6 +33,7 @@ import eu.kanade.tachiyomi.data.source.base.LoginSource; import eu.kanade.tachiyomi.data.source.model.MangasPage; import eu.kanade.tachiyomi.data.source.model.Page; import eu.kanade.tachiyomi.util.Parser; +import okhttp3.Cookie; import okhttp3.FormBody; import okhttp3.Headers; import okhttp3.Request; @@ -358,8 +358,8 @@ public class Batoto extends LoginSource { @Override public boolean isLogged() { try { - for ( HttpCookie cookie : getNetworkService().getCookies().get(new URI(BASE_URL)) ) { - if (cookie.getName().equals("pass_hash")) + for (Cookie cookie : getNetworkService().getCookies().get(new URI(BASE_URL))) { + if (cookie.name().equals("pass_hash")) return true; } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java deleted file mode 100644 index 0da058bad..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java +++ /dev/null @@ -1,234 +0,0 @@ -package eu.kanade.tachiyomi.data.source.online.english; - -import android.content.Context; -import android.net.Uri; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import eu.kanade.tachiyomi.data.database.models.Chapter; -import eu.kanade.tachiyomi.data.database.models.Manga; -import eu.kanade.tachiyomi.data.network.ReqKt; -import eu.kanade.tachiyomi.data.source.Language; -import eu.kanade.tachiyomi.data.source.LanguageKt; -import eu.kanade.tachiyomi.data.source.base.Source; -import eu.kanade.tachiyomi.data.source.model.MangasPage; -import eu.kanade.tachiyomi.data.source.model.Page; -import eu.kanade.tachiyomi.util.Parser; -import okhttp3.FormBody; -import okhttp3.Headers; -import okhttp3.Request; - -public class Kissmanga extends Source { - - public static final String NAME = "Kissmanga"; - public static final String HOST = "kissmanga.com"; - public static final String IP = "93.174.95.110"; - public static final String BASE_URL = "http://" + IP; - public static final String POPULAR_MANGAS_URL = BASE_URL + "/MangaList/MostPopular?page=%s"; - public static final String SEARCH_URL = BASE_URL + "/AdvanceSearch"; - - public Kissmanga(Context context) { - super(context); - } - - @Override - protected Headers.Builder headersBuilder() { - Headers.Builder builder = super.headersBuilder(); - builder.add("Host", HOST); - return builder; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getBaseUrl() { - return BASE_URL; - } - - public Language getLang() { - return LanguageKt.getEN(); - } - - @Override - protected String getInitialPopularMangasUrl() { - return String.format(POPULAR_MANGAS_URL, 1); - } - - @Override - protected String getInitialSearchUrl(String query) { - return SEARCH_URL; - } - - @Override - protected Request searchMangaRequest(MangasPage page, String query) { - if (page.page == 1) { - page.url = getInitialSearchUrl(query); - } - - FormBody.Builder form = new FormBody.Builder(); - form.add("authorArtist", ""); - form.add("mangaName", query); - form.add("status", ""); - form.add("genres", ""); - - return ReqKt.post(page.url, getRequestHeaders(), form.build()); - } - - @Override - protected Request pageListRequest(String chapterUrl) { - return ReqKt.post(getBaseUrl() + chapterUrl, getRequestHeaders()); - } - - @Override - protected Request imageRequest(Page page) { - return ReqKt.get(page.getImageUrl()); - } - - @Override - protected List parsePopularMangasFromHtml(Document parsedHtml) { - List mangaList = new ArrayList<>(); - - for (Element currentHtmlBlock : parsedHtml.select("table.listing tr:gt(1)")) { - Manga manga = constructPopularMangaFromHtml(currentHtmlBlock); - mangaList.add(manga); - } - - return mangaList; - } - - private Manga constructPopularMangaFromHtml(Element htmlBlock) { - Manga manga = new Manga(); - manga.source = getId(); - - Element urlElement = Parser.element(htmlBlock, "td a:eq(0)"); - - if (urlElement != null) { - manga.setUrl(urlElement.attr("href")); - manga.title = urlElement.text(); - } - - return manga; - } - - @Override - protected String parseNextPopularMangasUrl(Document parsedHtml, MangasPage page) { - String path = Parser.href(parsedHtml, "li > a:contains(› Next)"); - return path != null ? BASE_URL + path : null; - } - - @Override - protected List parseSearchFromHtml(Document parsedHtml) { - return parsePopularMangasFromHtml(parsedHtml); - } - - @Override - protected String parseNextSearchUrl(Document parsedHtml, MangasPage page, String query) { - return null; - } - - @Override - protected Manga parseHtmlToManga(String mangaUrl, String unparsedHtml) { - Document parsedDocument = Jsoup.parse(unparsedHtml); - Element infoElement = parsedDocument.select("div.barContent").first(); - - Manga manga = Manga.create(mangaUrl); - manga.title = Parser.text(infoElement, "a.bigChar"); - manga.author = Parser.text(infoElement, "p:has(span:contains(Author:)) > a"); - manga.genre = Parser.allText(infoElement, "p:has(span:contains(Genres:)) > *:gt(0)"); - manga.description = Parser.allText(infoElement, "p:has(span:contains(Summary:)) ~ p"); - manga.status = parseStatus(Parser.text(infoElement, "p:has(span:contains(Status:))")); - - String thumbnail = Parser.src(parsedDocument, ".rightBox:eq(0) img"); - if (thumbnail != null) { - manga.thumbnail_url = Uri.parse(thumbnail).buildUpon().authority(IP).toString(); - } - - manga.initialized = true; - return manga; - } - - private int parseStatus(String status) { - if (status.contains("Ongoing")) { - return Manga.ONGOING; - } - if (status.contains("Completed")) { - return Manga.COMPLETED; - } - return Manga.UNKNOWN; - } - - @Override - protected List parseHtmlToChapters(String unparsedHtml) { - Document parsedDocument = Jsoup.parse(unparsedHtml); - List chapterList = new ArrayList<>(); - - for (Element chapterElement : parsedDocument.select("table.listing tr:gt(1)")) { - Chapter chapter = constructChapterFromHtmlBlock(chapterElement); - chapterList.add(chapter); - } - - return chapterList; - } - - private Chapter constructChapterFromHtmlBlock(Element chapterElement) { - Chapter chapter = Chapter.create(); - - Element urlElement = Parser.element(chapterElement, "a"); - String date = Parser.text(chapterElement, "td:eq(1)"); - - if (urlElement != null) { - chapter.setUrl(urlElement.attr("href")); - chapter.name = urlElement.text(); - } - if (date != null) { - try { - chapter.date_upload = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH).parse(date).getTime(); - } catch (ParseException e) { /* Ignore */ } - } - return chapter; - } - - @Override - protected List parseHtmlToPageUrls(String unparsedHtml) { - Document parsedDocument = Jsoup.parse(unparsedHtml); - List pageUrlList = new ArrayList<>(); - - int numImages = parsedDocument.select("#divImage img").size(); - - for (int i = 0; i < numImages; i++) { - pageUrlList.add(""); - } - return pageUrlList; - } - - @Override - protected List parseFirstPage(List pages, String unparsedHtml) { - Pattern p = Pattern.compile("lstImages.push\\(\"(.+?)\""); - Matcher m = p.matcher(unparsedHtml); - - int i = 0; - while (m.find()) { - pages.get(i++).setImageUrl(m.group(1)); - } - return (List) pages; - } - - @Override - protected String parseHtmlToImageUrl(String unparsedHtml) { - return null; - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt new file mode 100644 index 000000000..fd89f6c01 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt @@ -0,0 +1,200 @@ +package eu.kanade.tachiyomi.data.source.online.english + +import android.content.Context +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.network.get +import eu.kanade.tachiyomi.data.network.post +import eu.kanade.tachiyomi.data.source.EN +import eu.kanade.tachiyomi.data.source.base.Source +import eu.kanade.tachiyomi.data.source.model.MangasPage +import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.util.Parser +import okhttp3.FormBody +import okhttp3.OkHttpClient +import okhttp3.Request +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +class Kissmanga(context: Context) : Source(context) { + + override fun getName() = NAME + + override fun getBaseUrl() = BASE_URL + + override fun getLang() = EN + + override val networkClient: OkHttpClient + get() = networkService.cloudflareClient + + override fun getInitialPopularMangasUrl(): String { + return String.format(POPULAR_MANGAS_URL, 1) + } + + override fun getInitialSearchUrl(query: String): String { + return SEARCH_URL + } + + override fun searchMangaRequest(page: MangasPage, query: String): Request { + if (page.page == 1) { + page.url = getInitialSearchUrl(query) + } + + val form = FormBody.Builder() + form.add("authorArtist", "") + form.add("mangaName", query) + form.add("status", "") + form.add("genres", "") + + return post(page.url, requestHeaders, form.build()) + } + + override fun pageListRequest(chapterUrl: String): Request { + return post(baseUrl + chapterUrl, requestHeaders) + } + + override fun imageRequest(page: Page): Request { + return get(page.imageUrl) + } + + override fun parsePopularMangasFromHtml(parsedHtml: Document): List { + val mangaList = ArrayList() + + for (currentHtmlBlock in parsedHtml.select("table.listing tr:gt(1)")) { + val manga = constructPopularMangaFromHtml(currentHtmlBlock) + mangaList.add(manga) + } + + return mangaList + } + + private fun constructPopularMangaFromHtml(htmlBlock: Element): Manga { + val manga = Manga() + manga.source = id + + val urlElement = Parser.element(htmlBlock, "td a:eq(0)") + + if (urlElement != null) { + manga.setUrl(urlElement.attr("href")) + manga.title = urlElement.text() + } + + return manga + } + + override fun parseNextPopularMangasUrl(parsedHtml: Document, page: MangasPage): String? { + val path = Parser.href(parsedHtml, "li > a:contains(› Next)") + return if (path != null) BASE_URL + path else null + } + + override fun parseSearchFromHtml(parsedHtml: Document): List { + return parsePopularMangasFromHtml(parsedHtml) + } + + override fun parseNextSearchUrl(parsedHtml: Document, page: MangasPage, query: String): String? { + return null + } + + override fun parseHtmlToManga(mangaUrl: String, unparsedHtml: String): Manga { + val parsedDocument = Jsoup.parse(unparsedHtml) + val infoElement = parsedDocument.select("div.barContent").first() + + val manga = Manga.create(mangaUrl) + manga.title = Parser.text(infoElement, "a.bigChar") + manga.author = Parser.text(infoElement, "p:has(span:contains(Author:)) > a") + manga.genre = Parser.allText(infoElement, "p:has(span:contains(Genres:)) > *:gt(0)") + manga.description = Parser.allText(infoElement, "p:has(span:contains(Summary:)) ~ p") + manga.status = parseStatus(Parser.text(infoElement, "p:has(span:contains(Status:))")!!) + + val thumbnail = Parser.src(parsedDocument, ".rightBox:eq(0) img") + if (thumbnail != null) { + manga.thumbnail_url = thumbnail + } + + manga.initialized = true + return manga + } + + private fun parseStatus(status: String): Int { + if (status.contains("Ongoing")) { + return Manga.ONGOING + } + if (status.contains("Completed")) { + return Manga.COMPLETED + } + return Manga.UNKNOWN + } + + override fun parseHtmlToChapters(unparsedHtml: String): List { + val parsedDocument = Jsoup.parse(unparsedHtml) + val chapterList = ArrayList() + + for (chapterElement in parsedDocument.select("table.listing tr:gt(1)")) { + val chapter = constructChapterFromHtmlBlock(chapterElement) + chapterList.add(chapter) + } + + return chapterList + } + + private fun constructChapterFromHtmlBlock(chapterElement: Element): Chapter { + val chapter = Chapter.create() + + val urlElement = Parser.element(chapterElement, "a") + val date = Parser.text(chapterElement, "td:eq(1)") + + if (urlElement != null) { + chapter.setUrl(urlElement.attr("href")) + chapter.name = urlElement.text() + } + if (date != null) { + try { + chapter.date_upload = SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH).parse(date).time + } catch (e: ParseException) { /* Ignore */ + } + + } + return chapter + } + + override fun parseHtmlToPageUrls(unparsedHtml: String): List { + val parsedDocument = Jsoup.parse(unparsedHtml) + val pageUrlList = ArrayList() + + val numImages = parsedDocument.select("#divImage img").size + + for (i in 0..numImages - 1) { + pageUrlList.add("") + } + return pageUrlList + } + + override fun parseFirstPage(pages: List, unparsedHtml: String): List { + val p = Pattern.compile("lstImages.push\\(\"(.+?)\"") + val m = p.matcher(unparsedHtml) + + var i = 0 + while (m.find()) { + pages[i++].imageUrl = m.group(1) + } + return pages + } + + override fun parseHtmlToImageUrl(unparsedHtml: String): String? { + return null + } + + companion object { + + val NAME = "Kissmanga" + val BASE_URL = "http://kissmanga.com" + val POPULAR_MANGAS_URL = BASE_URL + "/MangaList/MostPopular?page=%s" + val SEARCH_URL = BASE_URL + "/AdvanceSearch" + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/ReadMangaToday.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/ReadMangaToday.java index 2dfbbfdbb..afac46c72 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/ReadMangaToday.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/ReadMangaToday.java @@ -100,7 +100,7 @@ public class ReadMangaToday extends Source { @Override public Observable searchMangasFromNetwork(final MangasPage page, String query) { return networkService - .requestBody(searchMangaRequest(page, query), true) + .requestBody(searchMangaRequest(page, query), networkService.getDefaultClient()) .doOnNext(new Action1() { @Override public void call(String doc) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt index 84c23ae5f..cdf51a8f9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt @@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.injection.component import android.app.Application import dagger.Component +import eu.kanade.tachiyomi.data.glide.AppGlideModule import eu.kanade.tachiyomi.data.download.DownloadService +import eu.kanade.tachiyomi.data.glide.MangaModelLoader import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService @@ -51,6 +53,9 @@ interface AppComponent { fun inject(downloadService: DownloadService) fun inject(updateMangaSyncService: UpdateMangaSyncService) + fun inject(mangaModelLoader: MangaModelLoader) + fun inject(appGlideModule: AppGlideModule) + fun inject(updateDownloader: UpdateDownloader) fun application(): Application diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt index 6b44f0387..0dcd4e182 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.catalogue import android.view.View import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.model.GlideUrl import eu.kanade.tachiyomi.data.database.models.Manga import kotlinx.android.synthetic.main.item_catalogue_grid.view.* @@ -42,20 +41,16 @@ class CatalogueGridHolder(private val view: View, private val adapter: Catalogue * @param manga the manga to bind. */ fun setImage(manga: Manga) { + Glide.clear(view.thumbnail) if (!manga.thumbnail_url.isNullOrEmpty()) { - val url = manga.thumbnail_url!! - val headers = adapter.fragment.presenter.source.glideHeaders - Glide.with(view.context) - .load(if (headers != null) GlideUrl(url, headers) else url) + .load(manga) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .centerCrop() .skipMemoryCache(true) .placeholder(android.R.color.transparent) .into(view.thumbnail) - } else { - Glide.clear(view.thumbnail) } } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index cbf67fa51..601ce8d92 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.catalogue import android.os.Bundle +import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -38,6 +39,11 @@ class CataloguePresenter : BasePresenter() { */ @Inject lateinit var prefs: PreferencesHelper + /** + * Cover cache. + */ + @Inject lateinit var coverCache: CoverCache + /** * Enabled sources. */ @@ -335,6 +341,9 @@ class CataloguePresenter : BasePresenter() { */ fun changeMangaFavorite(manga: Manga) { manga.favorite = !manga.favorite + if (!manga.favorite) { + coverCache.deleteFromCache(manga.thumbnail_url) + } db.insertManga(manga).executeAsBlocking() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 7d6a4d1ba..58124602d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -98,10 +98,9 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : * @param position the position to bind. */ override fun onBindViewHolder(holder: LibraryHolder, position: Int) { - val presenter = (fragment.parentFragment as LibraryFragment).presenter val manga = getItem(position) - holder.onSetValues(manga, presenter) + holder.onSetValues(manga) //When user scrolls this bind the correct selection status holder.itemView.isActivated = isSelected(position) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 598f4d946..f46e27f44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -12,7 +12,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService -import eu.kanade.tachiyomi.event.LibraryMangaEvent +import eu.kanade.tachiyomi.ui.library.LibraryMangaEvent import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 4e60d1682..cbd0a3aed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.event.LibraryMangaEvent import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.category.CategoryActivity import eu.kanade.tachiyomi.ui.main.MainActivity @@ -388,7 +387,10 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback * @param mangas the manga list to move. */ private fun moveMangasToCategories(mangas: List) { - val categories = presenter.categories + // Hide the default category because it has a different behavior than the ones from db. + val categories = presenter.categories.filter { it.id != 0 } + + // Get indexes of the common categories to preselect. val commonCategoriesIndexes = presenter.getCommonCategories(mangas) .map { categories.indexOf(it) } .toTypedArray() @@ -397,7 +399,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback .title(R.string.action_move_category) .items(categories.map { it.name }) .itemsCallbackMultiChoice(commonCategoriesIndexes) { dialog, positions, text -> - presenter.moveMangasToCategories(positions, mangas) + val selectedCategories = positions.map { categories[it] } + presenter.moveMangasToCategories(selectedCategories, mangas) destroyActionModeIfNeeded() true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 163d20f27..fa3e6bd80 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -3,10 +3,7 @@ package eu.kanade.tachiyomi.ui.library import android.view.View import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.signature.StringSignature -import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.base.Source import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import kotlinx.android.synthetic.main.item_catalogue_grid.view.* @@ -19,8 +16,10 @@ import kotlinx.android.synthetic.main.item_catalogue_grid.view.* * @param listener a listener to react to single tap and long tap events. * @constructor creates a new library holder. */ -class LibraryHolder(private val view: View, private val adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener) : - FlexibleViewHolder(view, adapter, listener) { +class LibraryHolder(private val view: View, + private val adapter: LibraryCategoryAdapter, + listener: FlexibleViewHolder.OnListItemClickListener) +: FlexibleViewHolder(view, adapter, listener) { private var manga: Manga? = null @@ -29,9 +28,8 @@ class LibraryHolder(private val view: View, private val adapter: LibraryCategory * holder with the given manga. * * @param manga the manga to bind. - * @param presenter the library presenter. */ - fun onSetValues(manga: Manga, presenter: LibraryPresenter) { + fun onSetValues(manga: Manga) { this.manga = manga // Update the title of the manga. @@ -44,31 +42,13 @@ class LibraryHolder(private val view: View, private val adapter: LibraryCategory } // Update the cover. - loadCover(manga, presenter.sourceManager.get(manga.source)!!, presenter.coverCache) - } - - /** - * Load the cover of a manga in a image view. - * - * @param manga the manga to bind. - * @param source the source of the manga. - * @param coverCache the cache that stores the cover in the filesystem. - */ - private fun loadCover(manga: Manga, source: Source, coverCache: CoverCache) { Glide.clear(view.thumbnail) - if (!manga.thumbnail_url.isNullOrEmpty()) { - coverCache.saveOrLoadFromCache(manga.thumbnail_url, source.glideHeaders) { - if (adapter.fragment.isResumed && this.manga == manga) { - Glide.with(view.context) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .signature(StringSignature(it.lastModified().toString())) - .placeholder(android.R.color.transparent) - .into(itemView.thumbnail) - } - } - } + Glide.with(view.context) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .placeholder(android.R.color.transparent) + .into(view.thumbnail) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangaEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt similarity index 87% rename from app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangaEvent.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt index ce4a21219..2b1be71e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangaEvent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.event +package eu.kanade.tachiyomi.ui.library import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index b5bb1475e..25b7e7e0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -11,10 +11,10 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.event.LibraryMangaEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import rx.Observable import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers import rx.subjects.BehaviorSubject import java.io.IOException import java.io.InputStream @@ -236,26 +236,18 @@ class LibraryPresenter : BasePresenter() { * Remove the selected manga from the library. */ fun deleteMangas() { - for (manga in selectedMangas) { - manga.favorite = false - } + // Create a set of the list + val mangaToDelete = selectedMangas.toSet() - db.insertMangas(selectedMangas).executeAsBlocking() - } - - /** - * Move the given list of manga to categories. - * - * @param positions the indexes of the selected categories. - * @param mangas the list of manga to move. - */ - fun moveMangasToCategories(positions: Array, mangas: List) { - val categoriesToAdd = ArrayList() - for (index in positions) { - categoriesToAdd.add(categories[index]) - } - - moveMangasToCategories(categoriesToAdd, mangas) + Observable.from(mangaToDelete) + .subscribeOn(Schedulers.io()) + .doOnNext { + it.favorite = false + coverCache.deleteFromCache(it.thumbnail_url) + } + .toList() + .flatMap { db.insertMangas(it).asRxObservable() } + .subscribe() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt index ae325be9b..331fbda80 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.kt @@ -8,7 +8,7 @@ import android.support.v4.app.FragmentManager import android.support.v4.app.FragmentPagerAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.event.MangaEvent +import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersFragment import eu.kanade.tachiyomi.ui.manga.info.MangaInfoFragment diff --git a/app/src/main/java/eu/kanade/tachiyomi/event/MangaEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaEvent.kt similarity index 71% rename from app/src/main/java/eu/kanade/tachiyomi/event/MangaEvent.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaEvent.kt index e1168e1b4..ff86676da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/event/MangaEvent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaEvent.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.event +package eu.kanade.tachiyomi.ui.manga import eu.kanade.tachiyomi.data.database.models.Manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 5cc19fe07..c6ea2cdb9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -4,8 +4,8 @@ import android.os.Bundle import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager -import eu.kanade.tachiyomi.event.ChapterCountEvent -import eu.kanade.tachiyomi.event.MangaEvent +import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent +import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.SharedData import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 5ddbf5a5b..ee4ee66df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -11,8 +11,8 @@ import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.base.Source -import eu.kanade.tachiyomi.event.ChapterCountEvent -import eu.kanade.tachiyomi.event.MangaEvent +import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent +import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.SharedData import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/event/ChapterCountEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/ChapterCountEvent.kt similarity index 86% rename from app/src/main/java/eu/kanade/tachiyomi/event/ChapterCountEvent.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/ChapterCountEvent.kt index 767c6c852..14a025df6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/event/ChapterCountEvent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/ChapterCountEvent.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.event +package eu.kanade.tachiyomi.ui.manga.info import rx.Observable import rx.subjects.BehaviorSubject diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index e50f812c2..6ff56d8b6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -6,8 +6,6 @@ import android.os.Bundle import android.view.* import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.signature.StringSignature import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.base.Source @@ -112,45 +110,19 @@ class MangaInfoFragment : BaseRxFragment() { // Set the favorite drawable to the correct one. setFavoriteDrawable(manga.favorite) - // Initialize CoverCache and Glide headers to retrieve cover information. - val coverCache = presenter.coverCache - val headers = presenter.source.glideHeaders - // Set cover if it wasn't already. - if (manga_cover.drawable == null) { - manga.thumbnail_url?.let { url -> - if (manga.favorite) { - coverCache.saveOrLoadFromCache(url, headers) { - if (isResumed) { - Glide.with(context) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .signature(StringSignature(it.lastModified().toString())) - .into(manga_cover) + if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) { + Glide.with(context) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .into(manga_cover) - Glide.with(context) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .signature(StringSignature(it.lastModified().toString())) - .into(backdrop) - } - } - } else { - Glide.with(context) - .load(if (headers != null) GlideUrl(url, headers) else url) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .centerCrop() - .into(manga_cover) - - Glide.with(context) - .load(if (headers != null) GlideUrl(url, headers) else url) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .centerCrop() - .into(backdrop) - } - } + Glide.with(context) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .into(backdrop) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index dc8360406..73ffe02e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -6,9 +6,8 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.base.Source -import eu.kanade.tachiyomi.event.ChapterCountEvent -import eu.kanade.tachiyomi.event.MangaEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.util.SharedData import rx.Observable import rx.android.schedulers.AndroidSchedulers @@ -116,22 +115,11 @@ class MangaInfoPresenter : BasePresenter() { */ fun toggleFavorite() { manga.favorite = !manga.favorite - onMangaFavoriteChange(manga.favorite) - db.insertManga(manga).executeAsBlocking() - refreshManga() - } - - /** - * (Removes / Saves) cover depending on favorite status. - * - * @param isFavorite determines if manga is favorite or not. - */ - private fun onMangaFavoriteChange(isFavorite: Boolean) { - if (isFavorite) { - coverCache.save(manga.thumbnail_url, source.glideHeaders) - } else { + if (!manga.favorite) { coverCache.deleteFromCache(manga.thumbnail_url) } + db.insertManga(manga).executeAsBlocking() + refreshManga() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt index 6eedc4b90..2de2aa9c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/myanimelist/MyAnimeListPresenter.kt @@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaSync import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager -import eu.kanade.tachiyomi.event.MangaEvent +import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.SharedData import eu.kanade.tachiyomi.util.toast diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index ca8571d70..27d3d4930 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -21,7 +21,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.event.ReaderEvent +import eu.kanade.tachiyomi.ui.reader.ReaderEvent import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.base.listener.SimpleAnimationListener import eu.kanade.tachiyomi.ui.base.listener.SimpleSeekBarListener diff --git a/app/src/main/java/eu/kanade/tachiyomi/event/ReaderEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderEvent.kt similarity index 81% rename from app/src/main/java/eu/kanade/tachiyomi/event/ReaderEvent.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderEvent.kt index e70e1c77b..04a1a403f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/event/ReaderEvent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderEvent.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.event +package eu.kanade.tachiyomi.ui.reader import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index e233d8234..de71dd6e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.base.Source import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.event.ReaderEvent +import eu.kanade.tachiyomi.ui.reader.ReaderEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.SharedData import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt index 73ae0d0b1..f98955cea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager +import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.ui.base.activity.BaseActivity @@ -20,6 +21,7 @@ class SettingsActivity : BaseActivity() { @Inject lateinit var db: DatabaseHelper @Inject lateinit var sourceManager: SourceManager @Inject lateinit var syncManager: MangaSyncManager + @Inject lateinit var networkHelper: NetworkHelper override fun onCreate(savedState: Bundle?) { setAppTheme() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt index c67f1ed16..afad12aa7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.setting import android.os.Bundle -import android.support.v7.preference.Preference import android.view.View import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R @@ -16,8 +15,6 @@ import java.util.concurrent.atomic.AtomicInteger class SettingsAdvancedFragment : SettingsNestedFragment() { - private var clearCacheSubscription: Subscription? = null - companion object { fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment { @@ -27,17 +24,28 @@ class SettingsAdvancedFragment : SettingsNestedFragment() { } } - override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { - val clearCache = findPreference(getString(R.string.pref_clear_chapter_cache_key)) - val clearDatabase = findPreference(getString(R.string.pref_clear_database_key)) + private val clearCache by lazy { findPreference(getString(R.string.pref_clear_chapter_cache_key)) } - clearCache.setOnPreferenceClickListener { preference -> - clearChapterCache(preference) + private val clearDatabase by lazy { findPreference(getString(R.string.pref_clear_database_key)) } + + private val clearCookies by lazy { findPreference(getString(R.string.pref_clear_cookies_key)) } + + private var clearCacheSubscription: Subscription? = null + + override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { + clearCache.setOnPreferenceClickListener { + clearChapterCache() true } clearCache.summary = getString(R.string.used_cache, chapterCache.readableSize) - clearDatabase.setOnPreferenceClickListener { preference -> + clearCookies.setOnPreferenceClickListener { + settingsActivity.networkHelper.cookies.removeAll() + activity.toast(R.string.cookies_cleared) + true + } + + clearDatabase.setOnPreferenceClickListener { clearDatabase() true } @@ -48,7 +56,7 @@ class SettingsAdvancedFragment : SettingsNestedFragment() { super.onDestroyView() } - private fun clearChapterCache(preference: Preference) { + private fun clearChapterCache() { val deletedFiles = AtomicInteger() val files = chapterCache.cacheDir.listFiles() @@ -78,7 +86,7 @@ class SettingsAdvancedFragment : SettingsNestedFragment() { }, { dialog.dismiss() activity.toast(getString(R.string.cache_deleted, deletedFiles.get())) - preference.summary = getString(R.string.used_cache, chapterCache.readableSize) + clearCache.summary = getString(R.string.used_cache, chapterCache.readableSize) }) } @@ -87,7 +95,10 @@ class SettingsAdvancedFragment : SettingsNestedFragment() { .content(R.string.clear_database_confirmation) .positiveText(android.R.string.yes) .negativeText(android.R.string.no) - .onPositive { dialog, which -> db.deleteMangasNotInLibrary().executeAsBlocking() } + .onPositive { dialog, which -> + db.deleteMangasNotInLibrary().executeAsBlocking() + activity.toast(R.string.clear_database_completed) + } .show() } diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index e66fe62d3..1d66c4250 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -52,6 +52,7 @@ pref_clear_chapter_cache_key pref_clear_database_key + pref_clear_cookies_key pref_version pref_build_time diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2fe654d22..2e38b2554 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,9 +158,12 @@ Used: %1$s Cache cleared. %1$d files have been deleted An error occurred while clearing cache + Clear cookies + Cookies cleared Clear database Delete manga and chapters that are not in your library Are you sure? Read chapters and progress of non-library manga will be lost + Entries deleted Show warnings Show warning messages during library sync Reencode images diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index ae20a4843..c3b0fee02 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -6,6 +6,10 @@ android:key="@string/pref_clear_chapter_cache_key" android:title="@string/pref_clear_chapter_cache"/> + +