Changes in cover cache. Store covers in external cache dir

This commit is contained in:
len 2016-04-11 18:49:45 +02:00
parent b84635ffec
commit b8d1a88623
11 changed files with 115 additions and 124 deletions

View file

@ -1,14 +1,11 @@
package eu.kanade.tachiyomi.data.cache package eu.kanade.tachiyomi.data.cache
import android.content.Context import android.content.Context
import android.widget.ImageView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.animation.GlideAnimation import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.signature.StringSignature
import eu.kanade.tachiyomi.util.DiskUtils import eu.kanade.tachiyomi.util.DiskUtils
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -28,17 +25,15 @@ class CoverCache(private val context: Context) {
/** /**
* Cache directory used for cache management. * Cache directory used for cache management.
*/ */
private val CACHE_DIRNAME = "cover_disk_cache" private val cacheDir: File = File(context.externalCacheDir, "cover_disk_cache")
private val cacheDir: File = File(context.cacheDir, CACHE_DIRNAME)
/** /**
* Download the cover with Glide and save the file. * Download the cover with Glide and save the file.
* @param thumbnailUrl url of thumbnail. * @param thumbnailUrl url of thumbnail.
* @param headers headers included in Glide request. * @param headers headers included in Glide request.
* @param imageView imageView where picture should be displayed. * @param onReady function to call when the image is ready
*/ */
@JvmOverloads fun save(thumbnailUrl: String?, headers: LazyHeaders, onReady: ((File) -> Unit)? = null) {
fun save(thumbnailUrl: String?, headers: LazyHeaders, imageView: ImageView? = null) {
// Check if url is empty. // Check if url is empty.
if (thumbnailUrl.isNullOrEmpty()) if (thumbnailUrl.isNullOrEmpty())
return return
@ -51,12 +46,9 @@ class CoverCache(private val context: Context) {
override fun onResourceReady(resource: File, anim: GlideAnimation<in File>) { override fun onResourceReady(resource: File, anim: GlideAnimation<in File>) {
try { try {
// Copy the cover from Glide's cache to local cache. // Copy the cover from Glide's cache to local cache.
copyToLocalCache(thumbnailUrl!!, resource) copyToCache(thumbnailUrl!!, resource)
// Check if imageView isn't null and show picture in imageView. onReady?.invoke(resource)
if (imageView != null) {
loadFromCache(imageView, resource)
}
} catch (e: IOException) { } catch (e: IOException) {
// Do nothing. // Do nothing.
} }
@ -64,6 +56,35 @@ class CoverCache(private val context: Context) {
}) })
} }
/**
* 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 {
return File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl))
}
/** /**
* Copy the given file to this cache. * Copy the given file to this cache.
* @param thumbnailUrl url of thumbnail. * @param thumbnailUrl url of thumbnail.
@ -71,7 +92,7 @@ class CoverCache(private val context: Context) {
* @throws IOException if there's any error. * @throws IOException if there's any error.
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun copyToLocalCache(thumbnailUrl: String, sourceFile: File) { fun copyToCache(thumbnailUrl: String, sourceFile: File) {
// Get destination file. // Get destination file.
val destFile = getCoverFromCache(thumbnailUrl) val destFile = getCoverFromCache(thumbnailUrl)
@ -85,28 +106,19 @@ class CoverCache(private val context: Context) {
* @throws IOException if there's any error. * @throws IOException if there's any error.
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun copyToLocalCache(thumbnailUrl: String, inputStream: InputStream) { fun copyToCache(thumbnailUrl: String, inputStream: InputStream) {
// Get destination file. // Get destination file.
val destFile = getCoverFromCache(thumbnailUrl) val destFile = getCoverFromCache(thumbnailUrl)
destFile.outputStream().use { inputStream.copyTo(it) } destFile.outputStream().use { inputStream.copyTo(it) }
} }
/**
* Returns the cover from cache.
* @param thumbnailUrl the thumbnail url.
* @return cover image.
*/
private fun getCoverFromCache(thumbnailUrl: String): File {
return File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl))
}
/** /**
* Delete the cover file from the cache. * Delete the cover file from the cache.
* @param thumbnailUrl the thumbnail url. * @param thumbnailUrl the thumbnail url.
* @return status of deletion. * @return status of deletion.
*/ */
fun deleteCoverFromCache(thumbnailUrl: String?): Boolean { fun deleteFromCache(thumbnailUrl: String?): Boolean {
// Check if url is empty. // Check if url is empty.
if (thumbnailUrl.isNullOrEmpty()) if (thumbnailUrl.isNullOrEmpty())
return false return false
@ -116,56 +128,4 @@ class CoverCache(private val context: Context) {
return file.exists() && file.delete() return file.exists() && file.delete()
} }
/**
* Save or load the image from cache
* @param imageView imageView where picture should be displayed.
* @param thumbnailUrl the thumbnail url.
* @param headers headers included in Glide request.
*/
fun saveOrLoadFromCache(imageView: ImageView, thumbnailUrl: String, headers: LazyHeaders) {
// If file exist load it otherwise save it.
val localCover = getCoverFromCache(thumbnailUrl)
if (localCover.exists()) {
loadFromCache(imageView, localCover)
} else {
save(thumbnailUrl, headers, imageView)
}
}
/**
* Helper method to load the cover from the cache directory into the specified image view.
* Glide stores the resized image in its cache to improve performance.
* @param imageView imageView where picture should be displayed.
* @param file file to load. Must exist!.
*/
private fun loadFromCache(imageView: ImageView, file: File) {
Glide.with(context)
.load(file)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.centerCrop()
.signature(StringSignature(file.lastModified().toString()))
.into(imageView)
}
/**
* Helper method to load the cover from network into the specified image view.
* The source image is stored in Glide's cache so that it can be easily copied to this cache
* if the manga is added to the library.
* @param imageView imageView where picture should be displayed.
* @param thumbnailUrl url of thumbnail.
* @param headers headers included in Glide request.
*/
fun loadFromNetwork(imageView: ImageView, thumbnailUrl: String?, headers: LazyHeaders) {
// Check if url is empty.
if (thumbnailUrl.isNullOrEmpty())
return
val url = GlideUrl(thumbnailUrl, headers)
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.centerCrop()
.into(imageView)
}
} }

View file

@ -12,7 +12,7 @@ import java.util.*
* *
* @param fragment the fragment containing this adapter. * @param fragment the fragment containing this adapter.
*/ */
class CatalogueAdapter(private val fragment: CatalogueFragment) : FlexibleAdapter<CatalogueHolder, Manga>() { class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter<CatalogueHolder, Manga>() {
/** /**
* Property to get the list of manga in the adapter. * Property to get the list of manga in the adapter.
@ -83,7 +83,7 @@ class CatalogueAdapter(private val fragment: CatalogueFragment) : FlexibleAdapte
*/ */
override fun onBindViewHolder(holder: CatalogueHolder, position: Int) { override fun onBindViewHolder(holder: CatalogueHolder, position: Int) {
val manga = getItem(position) val manga = getItem(position)
holder.onSetValues(manga, fragment.presenter) holder.onSetValues(manga)
} }
} }

View file

@ -370,7 +370,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
* @param manga the manga initialized * @param manga the manga initialized
*/ */
fun onMangaInitialized(manga: Manga) { fun onMangaInitialized(manga: Manga) {
getHolder(manga)?.setImage(manga, presenter) getHolder(manga)?.setImage(manga)
} }
/** /**

View file

@ -1,6 +1,9 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.view.View 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 eu.kanade.tachiyomi.data.database.models.Manga
import kotlinx.android.synthetic.main.item_catalogue_grid.view.* import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
@ -13,7 +16,7 @@ import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
* @param listener a listener to react to single tap and long tap events. * @param listener a listener to react to single tap and long tap events.
* @constructor creates a new catalogue holder. * @constructor creates a new catalogue holder.
*/ */
class CatalogueGridHolder(private val view: View, adapter: CatalogueAdapter, listener: OnListItemClickListener) : class CatalogueGridHolder(private val view: View, private val adapter: CatalogueAdapter, listener: OnListItemClickListener) :
CatalogueHolder(view, adapter, listener) { CatalogueHolder(view, adapter, listener) {
/** /**
@ -21,16 +24,15 @@ class CatalogueGridHolder(private val view: View, adapter: CatalogueAdapter, lis
* holder with the given manga. * holder with the given manga.
* *
* @param manga the manga to bind. * @param manga the manga to bind.
* @param presenter the catalogue presenter.
*/ */
override fun onSetValues(manga: Manga, presenter: CataloguePresenter) { override fun onSetValues(manga: Manga) {
// Set manga title // Set manga title
view.title.text = manga.title view.title.text = manga.title
// Set alpha of thumbnail. // Set alpha of thumbnail.
view.thumbnail.alpha = if (manga.favorite) 0.3f else 1.0f view.thumbnail.alpha = if (manga.favorite) 0.3f else 1.0f
setImage(manga, presenter) setImage(manga)
} }
/** /**
@ -38,12 +40,18 @@ class CatalogueGridHolder(private val view: View, adapter: CatalogueAdapter, lis
* and the url is now known. * and the url is now known.
* *
* @param manga the manga to bind. * @param manga the manga to bind.
* @param presenter the catalogue presenter.
*/ */
fun setImage(manga: Manga, presenter: CataloguePresenter) { fun setImage(manga: Manga) {
if (manga.thumbnail_url != null) { if (manga.thumbnail_url != null) {
presenter.coverCache.loadFromNetwork(view.thumbnail, manga.thumbnail_url, val url = manga.thumbnail_url!!
presenter.source.glideHeaders) val headers = adapter.fragment.presenter.source.glideHeaders
Glide.with(view.context)
.load(if (headers != null) GlideUrl(url, headers) else url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.centerCrop()
.into(view.thumbnail)
} else { } else {
view.thumbnail.setImageResource(android.R.color.transparent) view.thumbnail.setImageResource(android.R.color.transparent)
} }

View file

@ -19,7 +19,6 @@ abstract class CatalogueHolder(view: View, adapter: CatalogueAdapter, listener:
* holder with the given manga. * holder with the given manga.
* *
* @param manga the manga to bind. * @param manga the manga to bind.
* @param presenter the catalogue presenter.
*/ */
abstract fun onSetValues(manga: Manga, presenter: CataloguePresenter) abstract fun onSetValues(manga: Manga)
} }

View file

@ -25,9 +25,8 @@ class CatalogueListHolder(private val view: View, adapter: CatalogueAdapter, lis
* holder with the given manga. * holder with the given manga.
* *
* @param manga the manga to bind. * @param manga the manga to bind.
* @param presenter the catalogue presenter.
*/ */
override fun onSetValues(manga: Manga, presenter: CataloguePresenter) { override fun onSetValues(manga: Manga) {
view.title.text = manga.title view.title.text = manga.title
view.title.setTextColor(if (manga.favorite) favoriteColor else unfavoriteColor) view.title.setTextColor(if (manga.favorite) favoriteColor else unfavoriteColor)
} }

View file

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import android.os.Bundle import android.os.Bundle
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -33,11 +32,6 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
*/ */
@Inject lateinit var db: DatabaseHelper @Inject lateinit var db: DatabaseHelper
/**
* Cover cache.
*/
@Inject lateinit var coverCache: CoverCache
/** /**
* Preferences. * Preferences.
*/ */

View file

@ -1,6 +1,9 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.view.View 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.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.base.Source import eu.kanade.tachiyomi.data.source.base.Source
@ -16,9 +19,11 @@ import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
* @param listener a listener to react to single tap and long tap events. * @param listener a listener to react to single tap and long tap events.
* @constructor creates a new library holder. * @constructor creates a new library holder.
*/ */
class LibraryHolder(view: View, adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener) : class LibraryHolder(private val view: View, adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener) :
FlexibleViewHolder(view, adapter, listener) { FlexibleViewHolder(view, adapter, listener) {
private var manga: Manga? = null
/** /**
* Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga. * holder with the given manga.
@ -27,11 +32,13 @@ class LibraryHolder(view: View, adapter: LibraryCategoryAdapter, listener: Flexi
* @param presenter the library presenter. * @param presenter the library presenter.
*/ */
fun onSetValues(manga: Manga, presenter: LibraryPresenter) { fun onSetValues(manga: Manga, presenter: LibraryPresenter) {
this.manga = manga
// Update the title of the manga. // Update the title of the manga.
itemView.title.text = manga.title view.title.text = manga.title
// Update the unread count and its visibility. // Update the unread count and its visibility.
with(itemView.unreadText) { with(view.unreadText) {
visibility = if (manga.unread > 0) View.VISIBLE else View.GONE visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
text = manga.unread.toString() text = manga.unread.toString()
} }
@ -49,9 +56,18 @@ class LibraryHolder(view: View, adapter: LibraryCategoryAdapter, listener: Flexi
*/ */
private fun loadCover(manga: Manga, source: Source, coverCache: CoverCache) { private fun loadCover(manga: Manga, source: Source, coverCache: CoverCache) {
if (manga.thumbnail_url != null) { if (manga.thumbnail_url != null) {
coverCache.saveOrLoadFromCache(itemView.thumbnail, manga.thumbnail_url, source.glideHeaders) coverCache.saveOrLoadFromCache(manga.thumbnail_url, source.glideHeaders) {
if (this.manga == manga) {
Glide.with(view.context)
.load(it)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
.centerCrop()
.signature(StringSignature(it.lastModified().toString()))
.into(itemView.thumbnail)
}
}
} else { } else {
itemView.thumbnail.setImageResource(android.R.color.transparent) view.thumbnail.setImageResource(android.R.color.transparent)
} }
} }

View file

@ -280,7 +280,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
@Throws(IOException::class) @Throws(IOException::class)
fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean { fun editCoverWithStream(inputStream: InputStream, manga: Manga): Boolean {
if (manga.thumbnail_url != null && manga.favorite) { if (manga.thumbnail_url != null && manga.favorite) {
coverCache.copyToLocalCache(manga.thumbnail_url, inputStream) coverCache.copyToCache(manga.thumbnail_url, inputStream)
return true return true
} }
return false return false

View file

@ -4,6 +4,10 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
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.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.base.Source import eu.kanade.tachiyomi.data.source.base.Source
@ -95,26 +99,37 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
val headers = presenter.source.glideHeaders val headers = presenter.source.glideHeaders
// Check if thumbnail_url is given. // Check if thumbnail_url is given.
if (manga.thumbnail_url != null) { manga.thumbnail_url?.let { url ->
// Check if cover is already drawn. if (manga.favorite) {
if (manga_cover.drawable == null) { coverCache.saveOrLoadFromCache(url, headers) {
// If manga is in library then (download / save) (from / to) local cache if available, if (isResumed) {
// else download from network. Glide.with(context)
if (manga.favorite) { .load(it)
coverCache.saveOrLoadFromCache(manga_cover, manga.thumbnail_url, headers) .diskCacheStrategy(DiskCacheStrategy.RESULT)
} else { .centerCrop()
coverCache.loadFromNetwork(manga_cover, manga.thumbnail_url, headers) .signature(StringSignature(it.lastModified().toString()))
} .into(manga_cover)
}
// Check if backdrop is already drawn. Glide.with(context)
if (backdrop.drawable == null) { .load(it)
// If manga is in library then (download / save) (from / to) local cache if available, .diskCacheStrategy(DiskCacheStrategy.RESULT)
// else download from network. .centerCrop()
if (manga.favorite) { .signature(StringSignature(it.lastModified().toString()))
coverCache.saveOrLoadFromCache(backdrop, manga.thumbnail_url, headers) .into(backdrop)
} else { }
coverCache.loadFromNetwork(backdrop, manga.thumbnail_url, headers)
} }
} 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)
} }
} }
} }

View file

@ -130,7 +130,7 @@ class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
if (isFavorite) { if (isFavorite) {
coverCache.save(manga.thumbnail_url, source.glideHeaders) coverCache.save(manga.thumbnail_url, source.glideHeaders)
} else { } else {
coverCache.deleteCoverFromCache(manga.thumbnail_url) coverCache.deleteFromCache(manga.thumbnail_url)
} }
} }