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 extends Page> 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"/>
+
+