diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2013-08-22 11:55:19 +0200 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2013-08-22 11:55:19 +0200 |
commit | 1c8dc43a7f4db4141480423b561b0824828779f5 (patch) | |
tree | 3bee590c33f9b97306a599830faec5b96377c650 /src/de | |
parent | 1563ab4ff8237dde23b97db39132306d248072da (diff) | |
download | AntennaPod-1c8dc43a7f4db4141480423b561b0824828779f5.zip |
Added ImageDiskCache
Diffstat (limited to 'src/de')
-rw-r--r-- | src/de/danoeh/antennapod/asynctask/ImageDiskCache.java | 346 |
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; + } + }; + } + } +} |