summaryrefslogtreecommitdiff
path: root/src/de/danoeh/antennapod/asynctask
diff options
context:
space:
mode:
authordaniel oeh <daniel.oeh@gmail.com>2013-08-22 11:55:19 +0200
committerdaniel oeh <daniel.oeh@gmail.com>2013-08-22 11:55:19 +0200
commit1c8dc43a7f4db4141480423b561b0824828779f5 (patch)
tree3bee590c33f9b97306a599830faec5b96377c650 /src/de/danoeh/antennapod/asynctask
parent1563ab4ff8237dde23b97db39132306d248072da (diff)
downloadAntennaPod-1c8dc43a7f4db4141480423b561b0824828779f5.zip
Added ImageDiskCache
Diffstat (limited to 'src/de/danoeh/antennapod/asynctask')
-rw-r--r--src/de/danoeh/antennapod/asynctask/ImageDiskCache.java346
1 files changed, 346 insertions, 0 deletions
diff --git a/src/de/danoeh/antennapod/asynctask/ImageDiskCache.java b/src/de/danoeh/antennapod/asynctask/ImageDiskCache.java
new file mode 100644
index 000000000..938f5d968
--- /dev/null
+++ b/src/de/danoeh/antennapod/asynctask/ImageDiskCache.java
@@ -0,0 +1,346 @@
+package de.danoeh.antennapod.asynctask;
+
+import android.util.Log;
+import android.util.Pair;
+import android.widget.ImageView;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.PodcastApp;
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Provides local cache for storing downloaded image. An image disk cache downloads images and stores them as long
+ * as the cache is not full. Once the cache is full, the image disk cache will delete older images.
+ */
+public class ImageDiskCache {
+ private static final String TAG = "ImageDiskCache";
+
+ private static HashMap<String, ImageDiskCache> cacheSingletons = new HashMap<String, ImageDiskCache>();
+
+ /**
+ * Return a default instance of an ImageDiskCache. This cache will store data in the external cache folder.
+ */
+ public static synchronized ImageDiskCache getDefaultInstance() {
+ final String DEFAULT_PATH = "imagecache";
+ final long DEFAULT_MAX_CACHE_SIZE = 10 * 1024 * 1024;
+
+ File cacheDir = PodcastApp.getInstance().getExternalCacheDir();
+ if (cacheDir == null) {
+ return null;
+ }
+ return getInstance(new File(cacheDir, DEFAULT_PATH).getAbsolutePath(), DEFAULT_MAX_CACHE_SIZE);
+ }
+
+ /**
+ * Return an instance of an ImageDiskCache that stores images in the specified folder.
+ */
+ public static synchronized ImageDiskCache getInstance(String path, long maxCacheSize) {
+ if (path == null) {
+ throw new NullPointerException();
+ }
+ ImageDiskCache cache = cacheSingletons.get(path);
+ if (cache == null) {
+ cache = new ImageDiskCache(path, maxCacheSize);
+ cacheSingletons.put(new File(path).getAbsolutePath(), cache);
+ }
+ return cache;
+ }
+
+ /**
+ * Filename - cache object mapping
+ */
+ private static final int KEY_IMAGE_DISK_CACHE = 2;
+ private static final String CACHE_FILE_NAME = "cachefile";
+ private ExecutorService executor;
+ private ConcurrentHashMap<String, DiskCacheObject> diskCache;
+ private final long maxCacheSize;
+ private int cacheSize;
+ private final File cacheFolder;
+
+ private ImageDiskCache(String path, long maxCacheSize) {
+ this.maxCacheSize = maxCacheSize;
+ this.cacheFolder = new File(path);
+ if (!cacheFolder.exists() && !cacheFolder.mkdir()) {
+ throw new IllegalArgumentException("Image disk cache could not create cache folder in: " + path);
+ }
+
+ executor = Executors.newFixedThreadPool(Runtime.getRuntime()
+ .availableProcessors());
+ }
+
+ private synchronized void initCacheFolder() {
+ if (diskCache != null) {
+ File cacheFile = new File(cacheFolder, CACHE_FILE_NAME);
+ if (cacheFile.exists()) {
+ try {
+ InputStream in = new FileInputStream(cacheFile);
+ BufferedInputStream buffer = new BufferedInputStream(in);
+ ObjectInputStream objectInput = new ObjectInputStream(buffer);
+ diskCache = (ConcurrentHashMap<String, DiskCacheObject>) objectInput.readObject();
+ // calculate cache size
+ for (DiskCacheObject dco : diskCache.values()) {
+ cacheSize += dco.size;
+ }
+ deleteInvalidFiles();
+ } catch (IOException e) {
+ e.printStackTrace();
+ diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
+ } catch (ClassCastException e) {
+ e.printStackTrace();
+ diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
+ }
+ } else {
+ diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
+ }
+ }
+ }
+
+ private List<File> getCacheFileList() {
+ Collection<DiskCacheObject> values = diskCache.values();
+ List<File> files = new ArrayList<File>();
+ for (DiskCacheObject dco : values) {
+ files.add(dco.getFile());
+ }
+ files.add(new File(cacheFolder, CACHE_FILE_NAME));
+ return files;
+ }
+
+ private Pair<String, DiskCacheObject> getOldestCacheObject() {
+ Collection<String> keys = diskCache.keySet();
+ DiskCacheObject oldest = null;
+ String oldestKey = null;
+
+ for (String key : keys) {
+
+ if (oldestKey == null) {
+ oldestKey = key;
+ oldest = diskCache.get(key);
+ } else {
+ DiskCacheObject dco = diskCache.get(key);
+ if (oldest.timestamp > dco.timestamp) {
+ oldestKey = key;
+ oldest = diskCache.get(key);
+ }
+ }
+ }
+ return new Pair<String, DiskCacheObject>(oldestKey, oldest);
+ }
+
+ private synchronized void deleteCacheObject(String key, DiskCacheObject value) {
+ Log.i(TAG, "Deleting cached object: " + key);
+ diskCache.remove(key);
+ boolean result = value.getFile().delete();
+ if (!result) {
+ Log.w(TAG, "Could not delete file " + value.fileUrl);
+ }
+ cacheSize -= value.size;
+ }
+
+ private synchronized void deleteInvalidFiles() {
+ // delete files that are not stored inside the cache
+ File[] files = cacheFolder.listFiles();
+ List<File> cacheFiles = getCacheFileList();
+ for (File file : files) {
+ if (!cacheFiles.contains(file)) {
+ Log.i(TAG, "Deleting unused file: " + file.getAbsolutePath());
+ boolean result = file.delete();
+ if (!result) {
+ Log.w(TAG, "Could not delete file: " + file.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ private synchronized void cleanup() {
+ if (cacheSize > maxCacheSize) {
+ while (cacheSize > maxCacheSize) {
+ Pair<String, DiskCacheObject> oldest = getOldestCacheObject();
+ deleteCacheObject(oldest.first, oldest.second);
+ }
+ }
+ }
+
+ /**
+ * Loads a new image from the disk cache. If the image that the url points to has already been downloaded, the image will
+ * be loaded from the disk. Otherwise, the image will be downloaded first.
+ * The image will be stored in the thumbnail cache.
+ */
+ public synchronized void loadThumbnailBitmap(final String url, final ImageView target) {
+ final ImageLoader il = ImageLoader.getInstance();
+ if (diskCache != null) {
+ DiskCacheObject dco = getFromCacheIfAvailable(url);
+ if (dco != null) {
+ il.loadThumbnailBitmap(dco.loadImage(), target);
+ return;
+ }
+ }
+ target.setTag(KEY_IMAGE_DISK_CACHE, url);
+ executor.submit(new ImageDownloader(url) {
+ @Override
+ protected void onImageLoaded(DiskCacheObject diskCacheObject) {
+ if (target.getTag(KEY_IMAGE_DISK_CACHE) == url) {
+ il.loadThumbnailBitmap(diskCacheObject.loadImage(), target);
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Loads a new image from the disk cache. If the image that the url points to has already been downloaded, the image will
+ * be loaded from the disk. Otherwise, the image will be downloaded first.
+ * The image will be stored in the cover cache.
+ */
+ public synchronized void loadCoverBitmap(final String url, final ImageView target) {
+ final ImageLoader il = ImageLoader.getInstance();
+ if (diskCache != null) {
+ DiskCacheObject dco = getFromCacheIfAvailable(url);
+ if (dco != null) {
+ il.loadThumbnailBitmap(dco.loadImage(), target);
+ return;
+ }
+ }
+ target.setTag(KEY_IMAGE_DISK_CACHE, url);
+ executor.submit(new ImageDownloader(url) {
+ @Override
+ protected void onImageLoaded(DiskCacheObject diskCacheObject) {
+ if (target.getTag(KEY_IMAGE_DISK_CACHE) == url) {
+ il.loadCoverBitmap(diskCacheObject.loadImage(), target);
+ }
+ }
+ });
+ }
+
+ private synchronized void addToDiskCache(String url, DiskCacheObject obj) {
+ if (diskCache == null) {
+ initCacheFolder();
+ }
+ if (AppConfig.DEBUG) Log.d(TAG, "Adding new image to disk cache: " + url);
+ diskCache.put(url, obj);
+ cacheSize += obj.size;
+ if (cacheSize > maxCacheSize) {
+ cleanup();
+ }
+ }
+
+ private synchronized DiskCacheObject getFromCacheIfAvailable(String key) {
+ if (diskCache == null) {
+ initCacheFolder();
+ }
+ DiskCacheObject dco = diskCache.get(key);
+ if (dco != null) {
+ dco.timestamp = System.currentTimeMillis();
+ }
+ return dco;
+ }
+
+ private abstract class ImageDownloader implements Runnable {
+ private String downloadUrl;
+
+ public ImageDownloader(String downloadUrl) {
+ this.downloadUrl = downloadUrl;
+ }
+
+ protected abstract void onImageLoaded(DiskCacheObject diskCacheObject);
+
+ public void run() {
+ DiskCacheObject tmp = getFromCacheIfAvailable(downloadUrl);
+ if (tmp != null) {
+ onImageLoaded(tmp);
+ return;
+ }
+
+ InputStream input = null;
+ OutputStream output = null;
+ try {
+ URL url = new URL(downloadUrl);
+ input = url.openStream();
+
+ File newFile = new File(cacheFolder, Integer.toString(downloadUrl.hashCode()));
+ output = new FileOutputStream(newFile);
+ long size = IOUtils.copy(input, output);
+
+ final DiskCacheObject dco = new DiskCacheObject(newFile.getAbsolutePath(), size);
+ addToDiskCache(downloadUrl, dco);
+ onImageLoaded(dco);
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(input);
+ IOUtils.closeQuietly(output);
+ }
+ }
+ }
+
+ private static class DiskCacheObject {
+ private final String fileUrl;
+
+ /**
+ * Last usage of this image cache object.
+ */
+ private long timestamp;
+ private final long size;
+
+ public DiskCacheObject(String fileUrl, long size) {
+ if (fileUrl == null) {
+ throw new NullPointerException();
+ }
+ this.fileUrl = fileUrl;
+ this.timestamp = System.currentTimeMillis();
+ this.size = size;
+ }
+
+ public File getFile() {
+ return new File(fileUrl);
+ }
+
+ public ImageLoader.ImageWorkerTaskResource loadImage() {
+ return new ImageLoader.ImageWorkerTaskResource() {
+ FileInputStream in = null;
+
+ @Override
+ public InputStream openImageInputStream() {
+ try {
+ return new FileInputStream(getFile());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ if (in != null) {
+ try {
+ in.close();
+ return openImageInputStream();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ return fileUrl;
+ }
+ };
+ }
+ }
+}