Improve the use and caching of the covers' cache. Use restartables in LibraryPresenter

This commit is contained in:
inorichi 2015-12-04 11:50:40 +01:00
parent 976f010d64
commit 54a715640d
7 changed files with 92 additions and 83 deletions

View File

@ -36,16 +36,24 @@ public class CoverCache {
return !cacheDir.exists() && cacheDir.mkdirs(); return !cacheDir.exists() && cacheDir.mkdirs();
} }
public void save(String thumbnailUrl, LazyHeaders headers) {
save(thumbnailUrl, headers, null);
}
// Download the cover with Glide (it can avoid repeating requests) and save the file on this cache // Download the cover with Glide (it can avoid repeating requests) and save the file on this cache
public void save(String cover, LazyHeaders headers) { // Optionally, load the image in the given image view when the resource is ready, if not null
GlideUrl url = new GlideUrl(cover, headers); public void save(String thumbnailUrl, LazyHeaders headers, ImageView imageView) {
GlideUrl url = new GlideUrl(thumbnailUrl, headers);
Glide.with(context) Glide.with(context)
.load(url) .load(url)
.downloadOnly(new SimpleTarget<File>() { .downloadOnly(new SimpleTarget<File>() {
@Override @Override
public void onResourceReady(File resource, GlideAnimation<? super File> anim) { public void onResourceReady(File resource, GlideAnimation<? super File> anim) {
try { try {
add(cover, resource); add(thumbnailUrl, resource);
if (imageView != null) {
loadFromCache(imageView, resource);
}
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -54,8 +62,9 @@ public class CoverCache {
} }
// Copy the cover from Glide's cache to this cache // Copy the cover from Glide's cache to this cache
public void add(String key, File source) throws IOException { public void add(String thumbnailUrl, File source) throws IOException {
File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(key)); createCacheDir();
File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
if (dest.exists()) if (dest.exists())
dest.delete(); dest.delete();
@ -78,37 +87,29 @@ public class CoverCache {
} }
// Get the cover from cache // Get the cover from cache
public File get(String key) { public File get(String thumbnailUrl) {
return new File(cacheDir, DiskUtils.hashKeyForDisk(key)); return new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
} }
// Delete the cover from cache // Delete the cover from cache
public boolean delete(String key) { public boolean delete(String thumbnailUrl) {
File file = new File(cacheDir, DiskUtils.hashKeyForDisk(key)); File file = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
return file.exists() && file.delete(); return file.exists() && file.delete();
} }
// Load the cover from cache or network if it doesn't exist // Save and load the image from cache
public void loadOrFetchInto(ImageView imageView, String cover, LazyHeaders headers) { public void saveAndLoadFromCache(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
File localCover = get(cover); File localCover = get(thumbnailUrl);
if (localCover.exists()) { if (localCover.exists()) {
loadLocalInto(context, imageView, localCover); loadFromCache(imageView, localCover);
} else { } else {
loadRemoteInto(context, imageView, cover, headers); save(thumbnailUrl, headers, imageView);
} }
} }
// Load the cover from cache // Helper method to load the cover from the cache directory into the specified image view
public static void loadLocalInto(Context context, ImageView imageView, String cover) { // The file must exist
File cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY); private void loadFromCache(ImageView imageView, File file) {
File localCover = new File(cacheDir, DiskUtils.hashKeyForDisk(cover));
if (localCover.exists()) {
loadLocalInto(context, imageView, localCover);
}
}
// Load the cover from the cache directory into the specified image view
private static void loadLocalInto(Context context, ImageView imageView, File file) {
Glide.with(context) Glide.with(context)
.load(file) .load(file)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
@ -116,9 +117,10 @@ public class CoverCache {
.into(imageView); .into(imageView);
} }
// Load the cover from network into the specified image view. It does NOT save the image in cache // Helper method to load the cover from network into the specified image view.
private static void loadRemoteInto(Context context, ImageView imageView, String cover, LazyHeaders headers) { // It does NOT save the image in cache
GlideUrl url = new GlideUrl(cover, headers); public void loadFromNetwork(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
GlideUrl url = new GlideUrl(thumbnailUrl, headers);
Glide.with(context) Glide.with(context)
.load(url) .load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE) .diskCacheStrategy(DiskCacheStrategy.SOURCE)

View File

@ -25,7 +25,6 @@ public class DownloadQueue {
public void remove(Download download) { public void remove(Download download) {
queue.remove(download); queue.remove(download);
download.setStatus(Download.NOT_DOWNLOADED);
download.setStatusSubject(null); download.setStatusSubject(null);
} }

View File

@ -1,6 +1,7 @@
package eu.kanade.mangafeed.ui.library; package eu.kanade.mangafeed.ui.library;
import android.content.Context; import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter; import android.widget.Filter;
import android.widget.Filterable; import android.widget.Filterable;
@ -14,10 +15,12 @@ public class LibraryAdapter extends EasyAdapter<Manga> implements Filterable {
List<Manga> mangas; List<Manga> mangas;
Filter filter; Filter filter;
private LibraryPresenter presenter;
public LibraryAdapter(Context context) { public LibraryAdapter(LibraryFragment fragment) {
super(context, LibraryHolder.class); super(fragment.getActivity(), LibraryHolder.class);
filter = new LibraryFilter(); filter = new LibraryFilter();
presenter = fragment.getPresenter();
} }
public void setNewItems(List<Manga> list) { public void setNewItems(List<Manga> list) {
@ -57,9 +60,16 @@ public class LibraryAdapter extends EasyAdapter<Manga> implements Filterable {
@Override @Override
public void publishResults(CharSequence constraint, FilterResults results) { public void publishResults(CharSequence constraint, FilterResults results) {
setItems((List<Manga >) results.values); setItems((List<Manga>) results.values);
} }
} }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
LibraryHolder holder = (LibraryHolder) view.getTag();
Manga manga = getItem(position);
holder.loadCover(manga, presenter.sourceManager.get(manga.source), presenter.coverCache);
return view;
}
} }

View File

@ -3,6 +3,7 @@ package eu.kanade.mangafeed.ui.library;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.util.SparseBooleanArray;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -20,9 +21,10 @@ import butterknife.OnItemClick;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.database.models.Manga; import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.sync.LibraryUpdateService; import eu.kanade.mangafeed.data.sync.LibraryUpdateService;
import eu.kanade.mangafeed.ui.manga.MangaActivity;
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
import eu.kanade.mangafeed.ui.manga.MangaActivity;
import nucleus.factory.RequiresPresenter; import nucleus.factory.RequiresPresenter;
import rx.Observable;
@RequiresPresenter(LibraryPresenter.class) @RequiresPresenter(LibraryPresenter.class)
public class LibraryFragment extends BaseRxFragment<LibraryPresenter> { public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
@ -31,10 +33,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
private LibraryAdapter adapter; private LibraryAdapter adapter;
public static LibraryFragment newInstance() { public static LibraryFragment newInstance() {
LibraryFragment fragment = new LibraryFragment(); return new LibraryFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
} }
@Override @Override
@ -95,7 +94,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
} }
private void createAdapter() { private void createAdapter() {
adapter = new LibraryAdapter(getActivity()); adapter = new LibraryAdapter(this);
grid.setAdapter(adapter); grid.setAdapter(adapter);
} }
@ -136,7 +135,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_delete: case R.id.action_delete:
getPresenter().onDelete(grid.getCheckedItemPositions(), adapter); getPresenter().deleteMangas(getSelectedMangas());
mode.finish(); mode.finish();
return true; return true;
} }
@ -150,4 +149,11 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
}); });
} }
private Observable<Manga> getSelectedMangas() {
SparseBooleanArray checkedItems = grid.getCheckedItemPositions();
return Observable.range(0, checkedItems.size())
.map(checkedItems::keyAt)
.map(adapter::getItem);
}
} }

View File

@ -7,6 +7,7 @@ import android.widget.TextView;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.cache.CoverCache; import eu.kanade.mangafeed.data.cache.CoverCache;
import eu.kanade.mangafeed.data.database.models.Manga; import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.source.base.Source;
import uk.co.ribot.easyadapter.ItemViewHolder; import uk.co.ribot.easyadapter.ItemViewHolder;
import uk.co.ribot.easyadapter.PositionInfo; import uk.co.ribot.easyadapter.PositionInfo;
import uk.co.ribot.easyadapter.annotations.LayoutId; import uk.co.ribot.easyadapter.annotations.LayoutId;
@ -17,11 +18,8 @@ import uk.co.ribot.easyadapter.annotations.ViewId;
public class LibraryHolder extends ItemViewHolder<Manga> { public class LibraryHolder extends ItemViewHolder<Manga> {
@ViewId(R.id.thumbnail) ImageView thumbnail; @ViewId(R.id.thumbnail) ImageView thumbnail;
@ViewId(R.id.title) TextView title; @ViewId(R.id.title) TextView title;
@ViewId(R.id.author) TextView author; @ViewId(R.id.author) TextView author;
@ViewId(R.id.unreadText) TextView unreadText; @ViewId(R.id.unreadText) TextView unreadText;
public LibraryHolder(View view) { public LibraryHolder(View view) {
@ -38,12 +36,11 @@ public class LibraryHolder extends ItemViewHolder<Manga> {
} else { } else {
unreadText.setVisibility(View.GONE); unreadText.setVisibility(View.GONE);
} }
}
if (manga.thumbnail_url != null) { public void loadCover(Manga manga, Source source, CoverCache coverCache) {
CoverCache.loadLocalInto(getContext(), thumbnail, manga.thumbnail_url); if (manga.thumbnail_url != null)
} else { coverCache.saveAndLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders());
thumbnail.setImageResource(android.R.color.transparent);
}
} }
} }

View File

@ -1,15 +1,16 @@
package eu.kanade.mangafeed.ui.library; package eu.kanade.mangafeed.ui.library;
import android.os.Bundle; import android.os.Bundle;
import android.util.SparseBooleanArray;
import javax.inject.Inject; import javax.inject.Inject;
import eu.kanade.mangafeed.data.cache.CoverCache;
import eu.kanade.mangafeed.data.database.DatabaseHelper; import eu.kanade.mangafeed.data.database.DatabaseHelper;
import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.preference.PreferencesHelper; import eu.kanade.mangafeed.data.preference.PreferencesHelper;
import eu.kanade.mangafeed.data.source.SourceManager;
import eu.kanade.mangafeed.ui.base.presenter.BasePresenter; import eu.kanade.mangafeed.ui.base.presenter.BasePresenter;
import rx.Observable; import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers; import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers; import rx.schedulers.Schedulers;
@ -17,44 +18,28 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
@Inject DatabaseHelper db; @Inject DatabaseHelper db;
@Inject PreferencesHelper prefs; @Inject PreferencesHelper prefs;
@Inject CoverCache coverCache;
@Inject SourceManager sourceManager;
private Subscription mFavoriteMangasSubscription; private static final int GET_MANGAS = 1;
private Subscription mDeleteMangaSubscription;
@Override @Override
protected void onCreate(Bundle savedState) { protected void onCreate(Bundle savedState) {
super.onCreate(savedState); super.onCreate(savedState);
restartableLatestCache(GET_MANGAS,
() -> db.getMangasWithUnread().createObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
LibraryFragment::onNextMangas);
start(GET_MANGAS);
} }
@Override public void deleteMangas(Observable<Manga> selectedMangas) {
protected void onTakeView(LibraryFragment view) { add(selectedMangas
super.onTakeView(view);
getFavoriteMangas();
}
public void getFavoriteMangas() {
if (mFavoriteMangasSubscription != null)
return;
add(mFavoriteMangasSubscription = db.getMangasWithUnread().createObservable()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .doOnNext(manga -> manga.favorite = false)
.compose(deliverLatestCache())
.subscribe(this.split(LibraryFragment::onNextMangas)));
}
public void onDelete(SparseBooleanArray checkedItems, LibraryAdapter adapter) {
if (mDeleteMangaSubscription != null)
remove(mDeleteMangaSubscription);
add(mDeleteMangaSubscription = Observable.range(0, checkedItems.size())
.observeOn(Schedulers.io())
.map(checkedItems::keyAt)
.map(adapter::getItem)
.map(manga -> {
manga.favorite = false;
return manga;
})
.toList() .toList()
.flatMap(mangas -> db.insertMangas(mangas).createObservable()) .flatMap(mangas -> db.insertMangas(mangas).createObservable())
.subscribe()); .subscribe());

View File

@ -8,9 +8,12 @@ import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.load.model.LazyHeaders;
import butterknife.Bind; import butterknife.Bind;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.cache.CoverCache;
import eu.kanade.mangafeed.data.database.models.Manga; import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
import nucleus.factory.RequiresPresenter; import nucleus.factory.RequiresPresenter;
@ -62,8 +65,15 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
setFavoriteText(manga.favorite); setFavoriteText(manga.favorite);
getPresenter().coverCache.loadOrFetchInto(mCover, if (mCover.getDrawable() == null) {
manga.thumbnail_url, getPresenter().source.getGlideHeaders()); CoverCache coverCache = getPresenter().coverCache;
LazyHeaders headers = getPresenter().source.getGlideHeaders();
if (manga.favorite) {
coverCache.saveAndLoadFromCache(mCover, manga.thumbnail_url, headers);
} else {
coverCache.loadFromNetwork(mCover, manga.thumbnail_url, headers);
}
}
} }