Merge pull request #78 from NoodleMage/upstream

Code optimization. Added javadoc. Removed setSize for it is not used
This commit is contained in:
inorichi 2016-01-27 17:41:53 +01:00
commit ba5d13936c
7 changed files with 256 additions and 48 deletions

View File

@ -21,22 +21,46 @@ import okio.BufferedSink;
import okio.Okio; import okio.Okio;
import rx.Observable; import rx.Observable;
/**
* Class used to create chapter cache
* For each image in a chapter a file is created
* For each chapter a Json list is created and converted to a file.
* The files are in format *md5key*.0
*/
public class ChapterCache { public class ChapterCache {
/** Name of cache directory. */
private static final String PARAMETER_CACHE_DIRECTORY = "chapter_disk_cache"; private static final String PARAMETER_CACHE_DIRECTORY = "chapter_disk_cache";
/** Application cache version. */
private static final int PARAMETER_APP_VERSION = 1; private static final int PARAMETER_APP_VERSION = 1;
/** The number of values per cache entry. Must be positive. */
private static final int PARAMETER_VALUE_COUNT = 1; private static final int PARAMETER_VALUE_COUNT = 1;
/** The maximum number of bytes this cache should use to store. */
private static final int PARAMETER_CACHE_SIZE = 75 * 1024 * 1024; private static final int PARAMETER_CACHE_SIZE = 75 * 1024 * 1024;
private Context context; /** Interface to global information about an application environment. */
private Gson gson; private final Context context;
/** Google Json class used for parsing json files. */
private final Gson gson;
/** Cache class used for cache management. */
private DiskLruCache diskCache; private DiskLruCache diskCache;
/**
* Constructor of ChapterCache.
* @param context application environment interface.
*/
public ChapterCache(Context context) { public ChapterCache(Context context) {
this.context = context; this.context = context;
// Initialize Json handler.
gson = new Gson(); gson = new Gson();
// Try to open cache in default cache directory.
try { try {
diskCache = DiskLruCache.open( diskCache = DiskLruCache.open(
new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY), new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY),
@ -45,43 +69,67 @@ public class ChapterCache {
PARAMETER_CACHE_SIZE PARAMETER_CACHE_SIZE
); );
} catch (IOException e) { } catch (IOException e) {
// Do Nothing. // Do Nothing. TODO error handling.
} }
} }
public boolean remove(String file) { /**
* Remove file from cache.
* @param file name of chapter file md5.0.
* @return false if file is journal or error else returns status of deletion.
*/
public boolean removeFileFromCache(String file) {
// Make sure we don't delete the journal file (keeps track of cache).
if (file.equals("journal") || file.startsWith("journal.")) if (file.equals("journal") || file.startsWith("journal."))
return false; return false;
try { try {
// Take dot(.) substring to get filename without the .0 at the end.
String key = file.substring(0, file.lastIndexOf(".")); String key = file.substring(0, file.lastIndexOf("."));
// Remove file from cache.
return diskCache.remove(key); return diskCache.remove(key);
} catch (IOException e) { } catch (IOException e) {
return false; return false;
} }
} }
/**
* Returns directory of cache.
* @return directory of cache.
*/
public File getCacheDir() { public File getCacheDir() {
return diskCache.getDirectory(); return diskCache.getDirectory();
} }
public long getRealSize() { /**
* Returns real size of directory.
* @return real size of directory.
*/
private long getRealSize() {
return DiskUtils.getDirectorySize(getCacheDir()); return DiskUtils.getDirectorySize(getCacheDir());
} }
/**
* Returns real size of directory in human readable format.
* @return real size of directory.
*/
public String getReadableSize() { public String getReadableSize() {
return Formatter.formatFileSize(context, getRealSize()); return Formatter.formatFileSize(context, getRealSize());
} }
public void setSize(int value) { /**
diskCache.setMaxSize(value * 1024 * 1024); * Get page objects from cache.
} * @param chapterUrl the url of the chapter.
* @return list of chapter pages.
*/
public Observable<List<Page>> getPageUrlsFromDiskCache(final String chapterUrl) { public Observable<List<Page>> getPageUrlsFromDiskCache(final String chapterUrl) {
return Observable.create(subscriber -> { return Observable.create(subscriber -> {
try { try {
// Get list of pages from chapterUrl.
List<Page> pages = getPageUrlsFromDiskCacheImpl(chapterUrl); List<Page> pages = getPageUrlsFromDiskCacheImpl(chapterUrl);
// Provides the Observer with a new item to observe.
subscriber.onNext(pages); subscriber.onNext(pages);
// Notify the Observer that finished sending push-based notifications.
subscriber.onCompleted(); subscriber.onCompleted();
} catch (Throwable e) { } catch (Throwable e) {
subscriber.onError(e); subscriber.onError(e);
@ -89,18 +137,31 @@ public class ChapterCache {
}); });
} }
private List<Page> getPageUrlsFromDiskCacheImpl(String chapterUrl) throws IOException { /**
* Implementation of the getPageUrlsFromDiskCache() function
* @param chapterUrl the url of the chapter
* @return returns list of chapter pages
* @throws IOException does nothing atm
*/
private List<Page> getPageUrlsFromDiskCacheImpl(String chapterUrl) throws IOException /*TODO IOException never thrown*/ {
// Initialize snapshot (a snapshot of the values for an entry).
DiskLruCache.Snapshot snapshot = null; DiskLruCache.Snapshot snapshot = null;
// Initialize list of pages.
List<Page> pages = null; List<Page> pages = null;
try { try {
// Create md5 key and retrieve snapshot.
String key = DiskUtils.hashKeyForDisk(chapterUrl); String key = DiskUtils.hashKeyForDisk(chapterUrl);
snapshot = diskCache.get(key); snapshot = diskCache.get(key);
// Convert JSON string to list of objects.
Type collectionType = new TypeToken<List<Page>>() {}.getType(); Type collectionType = new TypeToken<List<Page>>() {}.getType();
pages = gson.fromJson(snapshot.getString(0), collectionType); pages = gson.fromJson(snapshot.getString(0), collectionType);
} catch (IOException e) { } catch (IOException e) {
// Do Nothing. // Do Nothing. //TODO error handling?
} finally { } finally {
if (snapshot != null) { if (snapshot != null) {
snapshot.close(); snapshot.close();
@ -109,18 +170,30 @@ public class ChapterCache {
return pages; return pages;
} }
/**
* Add page urls to disk cache.
* @param chapterUrl the url of the chapter.
* @param pages list of chapter pages.
*/
public void putPageUrlsToDiskCache(final String chapterUrl, final List<Page> pages) { public void putPageUrlsToDiskCache(final String chapterUrl, final List<Page> pages) {
// Convert list of pages to json string.
String cachedValue = gson.toJson(pages); String cachedValue = gson.toJson(pages);
// Initialize the editor (edits the values for an entry).
DiskLruCache.Editor editor = null; DiskLruCache.Editor editor = null;
// Initialize OutputStream.
OutputStream outputStream = null; OutputStream outputStream = null;
try { try {
// Get editor from md5 key.
String key = DiskUtils.hashKeyForDisk(chapterUrl); String key = DiskUtils.hashKeyForDisk(chapterUrl);
editor = diskCache.edit(key); editor = diskCache.edit(key);
if (editor == null) { if (editor == null) {
return; return;
} }
// Write chapter urls to cache.
outputStream = new BufferedOutputStream(editor.newOutputStream(0)); outputStream = new BufferedOutputStream(editor.newOutputStream(0));
outputStream.write(cachedValue.getBytes()); outputStream.write(cachedValue.getBytes());
outputStream.flush(); outputStream.flush();
@ -128,7 +201,7 @@ public class ChapterCache {
diskCache.flush(); diskCache.flush();
editor.commit(); editor.commit();
} catch (Exception e) { } catch (Exception e) {
// Do Nothing. // Do Nothing. TODO error handling?
} finally { } finally {
if (editor != null) { if (editor != null) {
editor.abortUnlessCommitted(); editor.abortUnlessCommitted();
@ -137,12 +210,17 @@ public class ChapterCache {
try { try {
outputStream.close(); outputStream.close();
} catch (IOException ignore) { } catch (IOException ignore) {
// Do Nothing. // Do Nothing. TODO error handling?
} }
} }
} }
} }
/**
* Check if image is in cache.
* @param imageUrl url of image.
* @return true if in cache otherwise false.
*/
public boolean isImageInCache(final String imageUrl) { public boolean isImageInCache(final String imageUrl) {
try { try {
return diskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null; return diskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null;
@ -152,8 +230,14 @@ public class ChapterCache {
return false; return false;
} }
/**
* Get image path from url.
* @param imageUrl url of image.
* @return path of image.
*/
public String getImagePath(final String imageUrl) { public String getImagePath(final String imageUrl) {
try { try {
// Get file from md5 key.
String imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0"; String imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0";
File file = new File(diskCache.getDirectory(), imageName); File file = new File(diskCache.getDirectory(), imageName);
return file.getCanonicalPath(); return file.getCanonicalPath();
@ -163,17 +247,28 @@ public class ChapterCache {
return null; return null;
} }
/**
* Add image to cache
* @param imageUrl url of image.
* @param response http response from page.
* @throws IOException image error.
*/
public void putImageToDiskCache(final String imageUrl, final Response response) throws IOException { public void putImageToDiskCache(final String imageUrl, final Response response) throws IOException {
// Initialize editor (edits the values for an entry).
DiskLruCache.Editor editor = null; DiskLruCache.Editor editor = null;
// Initialize BufferedSink (used for small writes).
BufferedSink sink = null; BufferedSink sink = null;
try { try {
// Get editor from md5 key.
String key = DiskUtils.hashKeyForDisk(imageUrl); String key = DiskUtils.hashKeyForDisk(imageUrl);
editor = diskCache.edit(key); editor = diskCache.edit(key);
if (editor == null) { if (editor == null) {
throw new IOException("Unable to edit key"); throw new IOException("Unable to edit key");
} }
// Initialize OutputStream and write image.
OutputStream outputStream = new BufferedOutputStream(editor.newOutputStream(0)); OutputStream outputStream = new BufferedOutputStream(editor.newOutputStream(0));
sink = Okio.buffer(Okio.sink(outputStream)); sink = Okio.buffer(Okio.sink(outputStream));
sink.writeAll(response.body().source()); sink.writeAll(response.body().source());

View File

@ -20,33 +20,78 @@ import java.io.OutputStream;
import eu.kanade.tachiyomi.util.DiskUtils; import eu.kanade.tachiyomi.util.DiskUtils;
/**
* Class used to create cover cache
* Makes use of Glide(which can avoid repeating requests) for saving the file.
* It is not necessary to load the images to the cache.
* Names of files are created with the md5 of the thumbnailURL
*/
public class CoverCache { public class CoverCache {
/**
* Name of cache directory.
*/
private static final String PARAMETER_CACHE_DIRECTORY = "cover_disk_cache"; private static final String PARAMETER_CACHE_DIRECTORY = "cover_disk_cache";
private Context context; /**
private File cacheDir; * Interface to global information about an application environment.
*/
private final Context context;
/**
* Cache class used for cache management.
*/
private final File cacheDir;
/**
* Constructor of CoverCache.
*
* @param context application environment interface.
*/
public CoverCache(Context context) { public CoverCache(Context context) {
this.context = context; this.context = context;
// Get cache directory from parameter.
cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY); cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY);
// Create cache directory.
createCacheDir(); createCacheDir();
} }
/**
* Check if cache dir exist if not create directory.
*
* @return true if cache dir does exist and is created.
*/
private boolean createCacheDir() { private boolean createCacheDir() {
return !cacheDir.exists() && cacheDir.mkdirs(); return !cacheDir.exists() && cacheDir.mkdirs();
} }
/**
* Download the cover with Glide (it can avoid repeating requests) and save the file.
*
* @param thumbnailUrl url of thumbnail.
* @param headers headers included in Glide request.
*/
public void save(String thumbnailUrl, LazyHeaders headers) { public void save(String thumbnailUrl, LazyHeaders headers) {
save(thumbnailUrl, headers, null); save(thumbnailUrl, headers, null);
} }
// Download the cover with Glide (it can avoid repeating requests) and save the file on this cache /**
// Optionally, load the image in the given image view when the resource is ready, if not null * Download the cover with Glide (it can avoid repeating requests) and save the file.
public void save(String thumbnailUrl, LazyHeaders headers, ImageView imageView) { *
* @param thumbnailUrl url of thumbnail.
* @param headers headers included in Glide request.
* @param imageView imageView where picture should be displayed.
*/
private void save(String thumbnailUrl, LazyHeaders headers, ImageView imageView) {
// Check if url is empty.
if (TextUtils.isEmpty(thumbnailUrl)) if (TextUtils.isEmpty(thumbnailUrl))
// Do not try and create the string. Instead... only try to realize the truth. There is no string.
return; return;
// Download the cover with Glide and save the file.
GlideUrl url = new GlideUrl(thumbnailUrl, headers); GlideUrl url = new GlideUrl(thumbnailUrl, headers);
Glide.with(context) Glide.with(context)
.load(url) .load(url)
@ -54,7 +99,10 @@ public class CoverCache {
@Override @Override
public void onResourceReady(File resource, GlideAnimation<? super File> anim) { public void onResourceReady(File resource, GlideAnimation<? super File> anim) {
try { try {
add(thumbnailUrl, resource); // Copy the cover from Glide's cache to local cache.
copyToLocalCache(thumbnailUrl, resource);
// Check if imageView isn't null and show picture in imageView.
if (imageView != null) { if (imageView != null) {
loadFromCache(imageView, resource); loadFromCache(imageView, resource);
} }
@ -65,18 +113,32 @@ public class CoverCache {
}); });
} }
// Copy the cover from Glide's cache to this cache
public void add(String thumbnailUrl, File source) throws IOException { /**
* Copy the cover from Glide's cache to local cache.
*
* @param thumbnailUrl url of thumbnail.
* @param source the cover image.
* @throws IOException TODO not returned atm?
*/
private void copyToLocalCache(String thumbnailUrl, File source) throws IOException {
// Create cache directory and check if directory exist
createCacheDir(); createCacheDir();
// Create destination file.
File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)); File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
// Check if file already exists, if true delete it.
if (dest.exists()) if (dest.exists())
dest.delete(); dest.delete();
// Write thumbnail image to file.
InputStream in = new FileInputStream(source); InputStream in = new FileInputStream(source);
try { try {
OutputStream out = new FileOutputStream(dest); OutputStream out = new FileOutputStream(dest);
try { try {
// Transfer bytes from in to out // Transfer bytes from in to out.
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
int len; int len;
while ((len = in.read(buf)) > 0) { while ((len = in.read(buf)) > 0) {
@ -90,23 +152,43 @@ public class CoverCache {
} }
} }
// Get the cover from cache
public File get(String thumbnailUrl) { /**
* Returns the cover from cache.
*
* @param thumbnailUrl the thumbnail url.
* @return cover image.
*/
private File getCoverFromCache(String thumbnailUrl) {
return new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)); return new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
} }
// Delete the cover from cache /**
public boolean delete(String thumbnailUrl) { * Delete the cover file from the cache.
*
* @param thumbnailUrl the thumbnail url.
* @return status of deletion.
*/
public boolean deleteCoverFromCache(String thumbnailUrl) {
// Check if url is empty.
if (TextUtils.isEmpty(thumbnailUrl)) if (TextUtils.isEmpty(thumbnailUrl))
return false; return false;
// Remove file.
File file = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)); File file = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
return file.exists() && file.delete(); return file.exists() && file.delete();
} }
// Save and load the image from cache /**
public void saveAndLoadFromCache(ImageView imageView, String thumbnailUrl, LazyHeaders headers) { * Save or load the image from cache
File localCover = get(thumbnailUrl); *
* @param imageView imageView where picture should be displayed.
* @param thumbnailUrl the thumbnail url.
* @param headers headers included in Glide request.
*/
public void saveOrLoadFromCache(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
// If file exist load it otherwise save it.
File localCover = getCoverFromCache(thumbnailUrl);
if (localCover.exists()) { if (localCover.exists()) {
loadFromCache(imageView, localCover); loadFromCache(imageView, localCover);
} else { } else {
@ -114,9 +196,17 @@ public class CoverCache {
} }
} }
// If the image is already in our cache, use it. If not, load it with glide /**
* If the image is already in our cache, use it. If not, load it with glide.
* TODO not used atm.
*
* @param imageView imageView where picture should be displayed.
* @param thumbnailUrl url of thumbnail.
* @param headers headers included in Glide request.
*/
public void loadFromCacheOrNetwork(ImageView imageView, String thumbnailUrl, LazyHeaders headers) { public void loadFromCacheOrNetwork(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
File localCover = get(thumbnailUrl); // If localCover exist load it from cache otherwise load it from network.
File localCover = getCoverFromCache(thumbnailUrl);
if (localCover.exists()) { if (localCover.exists()) {
loadFromCache(imageView, localCover); loadFromCache(imageView, localCover);
} else { } else {
@ -124,8 +214,12 @@ public class CoverCache {
} }
} }
// Helper method to load the cover from the cache directory into the specified image view /**
// The file must exist * Helper method to load the cover from the cache directory into the specified image view.
*
* @param imageView imageView where picture should be displayed.
* @param file file to load. Must exist!.
*/
private void loadFromCache(ImageView imageView, File file) { private void loadFromCache(ImageView imageView, File file) {
Glide.with(context) Glide.with(context)
.load(file) .load(file)
@ -134,9 +228,19 @@ public class CoverCache {
.into(imageView); .into(imageView);
} }
// Helper method to 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.
* It does NOT save the image in cache!
*
* @param imageView imageView where picture should be displayed.
* @param thumbnailUrl url of thumbnail.
* @param headers headers included in Glide request.
*/
public void loadFromNetwork(ImageView imageView, String thumbnailUrl, LazyHeaders headers) { public void loadFromNetwork(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
// Check if url is empty.
if (TextUtils.isEmpty(thumbnailUrl))
return;
GlideUrl url = new GlideUrl(thumbnailUrl, headers); GlideUrl url = new GlideUrl(thumbnailUrl, headers);
Glide.with(context) Glide.with(context)
.load(url) .load(url)

View File

@ -7,8 +7,16 @@ import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.load.DecodeFormat; import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.module.GlideModule; import com.bumptech.glide.module.GlideModule;
/**
* Class used to update Glide module settings
*/
public class CoverGlideModule implements GlideModule { public class CoverGlideModule implements GlideModule {
/**
* Bitmaps decoded from most image formats (other than GIFs with hidden configs), will be decoded with the
* ARGB_8888 config.
*/
@Override @Override
public void applyOptions(Context context, GlideBuilder builder) { public void applyOptions(Context context, GlideBuilder builder) {
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
@ -16,6 +24,6 @@ public class CoverGlideModule implements GlideModule {
@Override @Override
public void registerComponents(Context context, Glide glide) { public void registerComponents(Context context, Glide glide) {
// Nothing to see here!
} }
} }

View File

@ -44,7 +44,7 @@ public class LibraryHolder extends FlexibleViewHolder {
private void loadCover(Manga manga, Source source, CoverCache coverCache) { private void loadCover(Manga manga, Source source, CoverCache coverCache) {
if (manga.thumbnail_url != null) { if (manga.thumbnail_url != null) {
coverCache.saveAndLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders()); coverCache.saveOrLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders());
} else { } else {
thumbnail.setImageResource(android.R.color.transparent); thumbnail.setImageResource(android.R.color.transparent);
} }

View File

@ -88,7 +88,7 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
LazyHeaders headers = getPresenter().source.getGlideHeaders(); LazyHeaders headers = getPresenter().source.getGlideHeaders();
if (manga.thumbnail_url != null && cover.getDrawable() == null) { if (manga.thumbnail_url != null && cover.getDrawable() == null) {
if (manga.favorite) { if (manga.favorite) {
coverCache.saveAndLoadFromCache(cover, manga.thumbnail_url, headers); coverCache.saveOrLoadFromCache(cover, manga.thumbnail_url, headers);
} else { } else {
coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers); coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers);
} }

View File

@ -19,17 +19,18 @@ import rx.schedulers.Schedulers;
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> { public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
@Inject DatabaseHelper db;
@Inject SourceManager sourceManager;
@Inject CoverCache coverCache;
private Manga manga;
protected Source source;
private int count = -1;
private static final int GET_MANGA = 1; private static final int GET_MANGA = 1;
private static final int GET_CHAPTER_COUNT = 2; private static final int GET_CHAPTER_COUNT = 2;
private static final int FETCH_MANGA_INFO = 3; private static final int FETCH_MANGA_INFO = 3;
protected Source source;
@Inject
DatabaseHelper db;
@Inject
SourceManager sourceManager;
@Inject
CoverCache coverCache;
private Manga manga;
private int count = -1;
@Override @Override
protected void onCreate(Bundle savedState) { protected void onCreate(Bundle savedState) {
@ -111,7 +112,7 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
if (isFavorite) { if (isFavorite) {
coverCache.save(manga.thumbnail_url, source.getGlideHeaders()); coverCache.save(manga.thumbnail_url, source.getGlideHeaders());
} else { } else {
coverCache.delete(manga.thumbnail_url); coverCache.deleteCoverFromCache(manga.thumbnail_url);
} }
} }

View File

@ -71,7 +71,7 @@ public class SettingsAdvancedFragment extends SettingsNestedFragment {
subscriptions.add(Observable.defer(() -> Observable.from(files)) subscriptions.add(Observable.defer(() -> Observable.from(files))
.concatMap(file -> { .concatMap(file -> {
if (chapterCache.remove(file.getName())) { if (chapterCache.removeFileFromCache(file.getName())) {
deletedFiles.incrementAndGet(); deletedFiles.incrementAndGet();
} }
return Observable.just(file); return Observable.just(file);