Files
EhViewer/app/src/main/java/com/hippo/ehviewer/cache/AnyCache.java
T
2015-03-25 19:44:50 +08:00

371 lines
10 KiB
Java

/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.cache;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.v4.util.LruCache;
import com.hippo.ehviewer.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public abstract class AnyCache<V> {
private static final String TAG = AnyCache.class.getSimpleName();
private @Nullable LruCache<String, V> mMemoryCache;
private @Nullable DiskCache<V> mDiskCache;
/**
* Used to temporarily pause the disk cache while scrolling
*/
public boolean mPauseDiskAccess = false;
private final Object mPauseLock = new Object();
protected abstract int sizeOf(String key, V value);
protected abstract V read(InputStream is);
protected abstract boolean write(OutputStream os, V value);
public void setMemoryCache(int maxSize) {
mMemoryCache = new MemoryCahce<V>(maxSize, this);
}
public void setDiskCache(File cacheDir, int maxSize) {
try {
mDiskCache = new DiskCache<V>(cacheDir, maxSize, this);
} catch (IOException e) {
Log.w(TAG, "Can't create disk cache", e);
}
}
/**
* Check if have memory cache
*
* @return true if have memory cache
*/
public boolean hasMemoryCache() {
return mMemoryCache != null;
}
/**
* Check if have disk cache
*
* @return true if have disk cache
*/
public boolean hasDiskCache() {
return mDiskCache != null;
}
/**
* Get value from memory cache
*
* @param key the key to get value
* @return the value you get, null for miss or no memory cache
*/
public V getFromMemory(String key) {
if (mMemoryCache != null) {
return mMemoryCache.get(key);
} else {
return null;
}
}
/**
* Get value from memory cache
*
* @param key the key to get value
* @return the value you get, null for miss or no memory cache or get error
*/
public V getFromDisk(String key) {
if (mDiskCache != null) {
// Wait for pause
waitUntilUnpaused();
String diskKey = hashKeyForDisk(key);
return mDiskCache.get(diskKey);
} else {
return null;
}
}
/**
* Get value from memory cache and disk cache. If miss in memory cache and
* get in disk cache, it will put value from disk cache to memory cache.
*
* @param key the key to get value
* @return the value you get
*/
public V get(String key) {
V value;
if (mMemoryCache != null) {
value = mMemoryCache.get(key);
if (value != null) {
return value;
}
}
if (mDiskCache != null) {
// Wait for pause
waitUntilUnpaused();
String diskKey = hashKeyForDisk(key);
value = mDiskCache.get(diskKey);
// Put value to memory cach
if (value != null && mMemoryCache != null) {
mMemoryCache.put(key, value);
}
return value;
} else {
return null;
}
}
/**
* Put value to memory cache
*
* @param key the key
* @param value the value
* @return false if no memory cache
*/
public boolean putToMemory(String key, V value) {
if (mMemoryCache != null) {
mMemoryCache.put(key, value);
return true;
} else {
return false;
}
}
/**
* Put value to disk cache
*
* @param key the key
* @param value the value
* @return false if no disk cache or get error
*/
public boolean putToDisk(String key, V value) {
if (mDiskCache != null) {
// Wait for pause
waitUntilUnpaused();
String diskKey = hashKeyForDisk(key);
return mDiskCache.put(diskKey, value);
} else {
return false;
}
}
/**
* Put value to memory cache and disk cache
*
* @param key the key
* @param value the value
*/
public void put(String key, V value) {
if (mMemoryCache != null) {
mMemoryCache.put(key, value);
}
if (mDiskCache != null) {
// Wait for pause
waitUntilUnpaused();
String diskKey = hashKeyForDisk(key);
mDiskCache.put(diskKey, value);
}
}
/**
* Used to temporarily pause the disk cache while the user is scrolling to
* improve scrolling.
*
* @param pause True to temporarily pause the disk cache, false otherwise.
*/
public void setPauseDiskCache(final boolean pause) {
synchronized (mPauseLock) {
if (mPauseDiskAccess != pause) {
mPauseDiskAccess = pause;
if (!pause) {
mPauseLock.notify();
}
}
}
}
private void waitUntilUnpaused() {
synchronized (mPauseLock) {
if (Looper.myLooper() != Looper.getMainLooper()) {
while (mPauseDiskAccess) {
try {
mPauseLock.wait();
} catch (InterruptedException e) {
// ignored, we'll start waiting again
}
}
}
}
}
/**
* Evicts all of the items from the memory cache and lets the system know
* now would be a good time to garbage collect
*/
public void evictAll() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
System.gc();
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable
* for using as a disk filename.
*
* @param key The key used to store the file
*/
private static String hashKeyForDisk(final String key) {
String cacheKey;
try {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(key.getBytes());
cacheKey = bytesToHexString(digest.digest());
} catch (final NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* http://stackoverflow.com/questions/332079
*
* @param bytes The bytes to convert.
* @return A {@link String} converted from the bytes of a hashable key used
* to store a filename on the disk, to hex digits.
*/
private static String bytesToHexString(final byte[] bytes) {
final StringBuilder builder = new StringBuilder();
for (final byte b : bytes) {
final String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
builder.append('0');
}
builder.append(hex);
}
return builder.toString();
}
public class MemoryCahce<E> extends LruCache<String, E> {
public AnyCache<E> mParent;
public MemoryCahce(int maxSize, AnyCache<E> parent) {
super(maxSize);
mParent = parent;
}
@Override
protected int sizeOf(String key, E value) {
return mParent.sizeOf(key, value);
}
}
public static class DiskCache<E> {
private static final int IO_BUFFER_SIZE = 8 * 1024;
public DiskLruCache mDiskLruCache;
public AnyCache<E> mParent;
public DiskCache(File cacheDir, int size, AnyCache<E> parent) throws IOException {
mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, size);
mParent = parent;
}
public E get(String key) {
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(key);
if ( snapshot == null ) {
// Miss
return null;
}
final InputStream in = snapshot.getInputStream(0);
if (in != null) {
final BufferedInputStream buffIn =
new BufferedInputStream(in, IO_BUFFER_SIZE);
return mParent.read(buffIn);
} else {
// Can't get InputStream
return null;
}
} catch (IOException e) {
// e.printStackTrace();
return null;
} finally {
Util.closeQuietly(snapshot);
}
}
public boolean put(String key, E value) {
DiskLruCache.Editor editor = null;
OutputStream os = null;
try {
editor = mDiskLruCache.edit(key);
if (editor == null) {
// The editor is in progress
return false;
}
os = editor.newOutputStream(0);
if (os != null) {
final BufferedOutputStream buffOut =
new BufferedOutputStream(os, IO_BUFFER_SIZE);
boolean result = mParent.write(buffOut, value);
mDiskLruCache.flush();
editor.commit();
return result;
} else {
// Can't get OutputStream
editor.abort();
return false;
}
} catch (IOException e) {
Util.closeQuietly(os);
try {
if (editor != null) {
editor.abort();
}
} catch (IOException ignored) {
}
return false;
}
}
}
}