diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.java b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.java index 9571c6020..3a4e8c150 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.java @@ -21,22 +21,46 @@ import okio.BufferedSink; import okio.Okio; 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 { + /** Name of cache directory. */ private static final String PARAMETER_CACHE_DIRECTORY = "chapter_disk_cache"; + + /** Application cache version. */ 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; + + /** The maximum number of bytes this cache should use to store. */ private static final int PARAMETER_CACHE_SIZE = 75 * 1024 * 1024; - private Context context; - private Gson gson; + /** Interface to global information about an application environment. */ + 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; + /** + * Constructor of ChapterCache. + * @param context application environment interface. + */ public ChapterCache(Context context) { this.context = context; + + // Initialize Json handler. gson = new Gson(); + // Try to open cache in default cache directory. try { diskCache = DiskLruCache.open( new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY), @@ -45,43 +69,67 @@ public class ChapterCache { PARAMETER_CACHE_SIZE ); } 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.")) return false; try { + // Take dot(.) substring to get filename without the .0 at the end. String key = file.substring(0, file.lastIndexOf(".")); + // Remove file from cache. return diskCache.remove(key); } catch (IOException e) { return false; } } + /** + * Returns directory of cache. + * @return directory of cache. + */ public File getCacheDir() { return diskCache.getDirectory(); } - public long getRealSize() { + /** + * Returns real size of directory. + * @return real size of directory. + */ + private long getRealSize() { return DiskUtils.getDirectorySize(getCacheDir()); } + /** + * Returns real size of directory in human readable format. + * @return real size of directory. + */ public String getReadableSize() { 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> getPageUrlsFromDiskCache(final String chapterUrl) { return Observable.create(subscriber -> { try { + // Get list of pages from chapterUrl. List pages = getPageUrlsFromDiskCacheImpl(chapterUrl); + // Provides the Observer with a new item to observe. subscriber.onNext(pages); + // Notify the Observer that finished sending push-based notifications. subscriber.onCompleted(); } catch (Throwable e) { subscriber.onError(e); @@ -89,18 +137,31 @@ public class ChapterCache { }); } - private List 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 getPageUrlsFromDiskCacheImpl(String chapterUrl) throws IOException /*TODO IOException never thrown*/ { + // Initialize snapshot (a snapshot of the values for an entry). DiskLruCache.Snapshot snapshot = null; + + // Initialize list of pages. List pages = null; try { + // Create md5 key and retrieve snapshot. String key = DiskUtils.hashKeyForDisk(chapterUrl); snapshot = diskCache.get(key); + + // Convert JSON string to list of objects. Type collectionType = new TypeToken>() {}.getType(); pages = gson.fromJson(snapshot.getString(0), collectionType); + } catch (IOException e) { - // Do Nothing. + // Do Nothing. //TODO error handling? } finally { if (snapshot != null) { snapshot.close(); @@ -109,18 +170,30 @@ public class ChapterCache { 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 pages) { + // Convert list of pages to json string. String cachedValue = gson.toJson(pages); + // Initialize the editor (edits the values for an entry). DiskLruCache.Editor editor = null; + + // Initialize OutputStream. OutputStream outputStream = null; + try { + // Get editor from md5 key. String key = DiskUtils.hashKeyForDisk(chapterUrl); editor = diskCache.edit(key); if (editor == null) { return; } + // Write chapter urls to cache. outputStream = new BufferedOutputStream(editor.newOutputStream(0)); outputStream.write(cachedValue.getBytes()); outputStream.flush(); @@ -128,7 +201,7 @@ public class ChapterCache { diskCache.flush(); editor.commit(); } catch (Exception e) { - // Do Nothing. + // Do Nothing. TODO error handling? } finally { if (editor != null) { editor.abortUnlessCommitted(); @@ -137,12 +210,17 @@ public class ChapterCache { try { outputStream.close(); } 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) { try { return diskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null; @@ -152,8 +230,14 @@ public class ChapterCache { return false; } + /** + * Get image path from url. + * @param imageUrl url of image. + * @return path of image. + */ public String getImagePath(final String imageUrl) { try { + // Get file from md5 key. String imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0"; File file = new File(diskCache.getDirectory(), imageName); return file.getCanonicalPath(); @@ -163,17 +247,28 @@ public class ChapterCache { 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 { + // Initialize editor (edits the values for an entry). DiskLruCache.Editor editor = null; + + // Initialize BufferedSink (used for small writes). BufferedSink sink = null; try { + // Get editor from md5 key. String key = DiskUtils.hashKeyForDisk(imageUrl); editor = diskCache.edit(key); if (editor == null) { throw new IOException("Unable to edit key"); } + // Initialize OutputStream and write image. OutputStream outputStream = new BufferedOutputStream(editor.newOutputStream(0)); sink = Okio.buffer(Okio.sink(outputStream)); sink.writeAll(response.body().source()); diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.java b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.java index 7f2697a37..e76bcf34d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.java @@ -20,33 +20,78 @@ import java.io.OutputStream; 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 { + /** + * Name of cache directory. + */ 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) { this.context = context; + + // Get cache directory from parameter. cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY); + + // Create cache directory. createCacheDir(); } + /** + * Check if cache dir exist if not create directory. + * + * @return true if cache dir does exist and is created. + */ private boolean createCacheDir() { 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) { 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 - public void save(String thumbnailUrl, LazyHeaders headers, ImageView imageView) { + /** + * 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. + * @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)) + // Do not try and create the string. Instead... only try to realize the truth. There is no string. return; + // Download the cover with Glide and save the file. GlideUrl url = new GlideUrl(thumbnailUrl, headers); Glide.with(context) .load(url) @@ -54,7 +99,10 @@ public class CoverCache { @Override public void onResourceReady(File resource, GlideAnimation anim) { 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) { 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(); + + // Create destination file. File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)); + + + // Check if file already exists, if true delete it. if (dest.exists()) dest.delete(); + // Write thumbnail image to file. InputStream in = new FileInputStream(source); try { OutputStream out = new FileOutputStream(dest); try { - // Transfer bytes from in to out + // Transfer bytes from in to out. byte[] buf = new byte[1024]; int len; 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)); } - // 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)) return false; + // Remove file. File file = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)); return file.exists() && file.delete(); } - // Save and load the image from cache - public void saveAndLoadFromCache(ImageView imageView, String thumbnailUrl, LazyHeaders headers) { - File localCover = get(thumbnailUrl); + /** + * 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. + */ + public void saveOrLoadFromCache(ImageView imageView, String thumbnailUrl, LazyHeaders headers) { + // If file exist load it otherwise save it. + File localCover = getCoverFromCache(thumbnailUrl); if (localCover.exists()) { loadFromCache(imageView, localCover); } 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) { - File localCover = get(thumbnailUrl); + // If localCover exist load it from cache otherwise load it from network. + File localCover = getCoverFromCache(thumbnailUrl); if (localCover.exists()) { loadFromCache(imageView, localCover); } 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) { Glide.with(context) .load(file) @@ -134,9 +228,19 @@ public class CoverCache { .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) { + // Check if url is empty. + if (TextUtils.isEmpty(thumbnailUrl)) + return; + GlideUrl url = new GlideUrl(thumbnailUrl, headers); Glide.with(context) .load(url) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.java b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.java index c94397bcd..e3613ed56 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverGlideModule.java @@ -7,8 +7,16 @@ import com.bumptech.glide.GlideBuilder; import com.bumptech.glide.load.DecodeFormat; import com.bumptech.glide.module.GlideModule; +/** + * Class used to update Glide module settings + */ 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 public void applyOptions(Context context, GlideBuilder builder) { builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); @@ -16,6 +24,6 @@ public class CoverGlideModule implements GlideModule { @Override public void registerComponents(Context context, Glide glide) { - + // Nothing to see here! } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java index 525c176a3..d115758ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java @@ -44,7 +44,7 @@ public class LibraryHolder extends FlexibleViewHolder { private void loadCover(Manga manga, Source source, CoverCache coverCache) { if (manga.thumbnail_url != null) { - coverCache.saveAndLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders()); + coverCache.saveOrLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders()); } else { thumbnail.setImageResource(android.R.color.transparent); } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java index bee8651f4..033e8e7ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java @@ -88,7 +88,7 @@ public class MangaInfoFragment extends BaseRxFragment { LazyHeaders headers = getPresenter().source.getGlideHeaders(); if (manga.thumbnail_url != null && cover.getDrawable() == null) { if (manga.favorite) { - coverCache.saveAndLoadFromCache(cover, manga.thumbnail_url, headers); + coverCache.saveOrLoadFromCache(cover, manga.thumbnail_url, headers); } else { coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers); } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java index ea69a76fc..bfeb251be 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.java @@ -19,17 +19,18 @@ import rx.schedulers.Schedulers; public class MangaInfoPresenter extends BasePresenter { - @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_CHAPTER_COUNT = 2; 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 protected void onCreate(Bundle savedState) { @@ -111,7 +112,7 @@ public class MangaInfoPresenter extends BasePresenter { if (isFavorite) { coverCache.save(manga.thumbnail_url, source.getGlideHeaders()); } else { - coverCache.delete(manga.thumbnail_url); + coverCache.deleteCoverFromCache(manga.thumbnail_url); } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.java index eb9318820..6feccd55b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.java @@ -71,7 +71,7 @@ public class SettingsAdvancedFragment extends SettingsNestedFragment { subscriptions.add(Observable.defer(() -> Observable.from(files)) .concatMap(file -> { - if (chapterCache.remove(file.getName())) { + if (chapterCache.removeFileFromCache(file.getName())) { deletedFiles.incrementAndGet(); } return Observable.just(file);