package in.srain.cube.cache;
import in.srain.cube.concurrent.SimpleExecutor;
import in.srain.cube.concurrent.SimpleTask;
import in.srain.cube.file.FileUtil;
import in.srain.cube.file.LruFileCache;
import in.srain.cube.request.JsonData;
import in.srain.cube.util.CLog;
import in.srain.cube.util.Debug;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import android.util.LruCache;
/**
* @author http://www.liaohuqiu.net
*/
public class CacheManager
{
private static final boolean DEBUG = Debug.DEBUG_CACHE;
private static final String LOG_TAG = "cube_cache";
private LruCache<String, CacheInfo> mMemoryCache;
private LruFileCache mFileCache;
private static final byte AFTER_READ_FROM_FILE = 0x01;
private static final byte AFTER_READ_FROM_ASSERT = 0x02;
private static final byte AFTER_CONVERT = 0x04;
private static final byte DO_READ_FROM_FILE = 0x01;
private static final byte DO_READ_FROM_ASSERT = 0x02;
private static final byte DO_CONVERT = 0x04;
private static final byte CONVERT_FOR_MEMORY = 0x03;
private static final byte CONVERT_FOR_FILE = 0x01;
private static final byte CONVERT_FOR_ASSERT = 0x02;
private static final byte CONVERT_FOR_CREATE = 0x04;
private Context mContext;
@SuppressLint("NewApi")
public CacheManager(Context content, String cacheDir,
int memoryCacheSizeInKB, int fileCacheSizeInKB)
{
mContext = content;
mMemoryCache = new LruCache<String, CacheInfo>(
memoryCacheSizeInKB * 1024)
{
@Override
protected int sizeOf(String key, CacheInfo value)
{
return (value.getSize() + key.getBytes().length);
}
};
mFileCache = new LruFileCache(content, cacheDir,
fileCacheSizeInKB * 1024);
mFileCache.initDiskCacheAsync();
if (DEBUG)
{
CLog.d(LOG_TAG,
"init file cache. dir: %s => %s, size: %s, used: %s",
cacheDir, mFileCache.getCachePath(),
mFileCache.getMaxSize(), mFileCache.getUsedSpace());
}
}
public <T> void requestCache(ICacheAble<T> cacheAble)
{
InnerCacheTask<T> task = new InnerCacheTask<T>(cacheAble);
task.beginQuery();
}
public <T> void continueAfterCreateData(ICacheAble<T> cacheAble,
final String data)
{
setCacheData(cacheAble.getCacheKey(), data);
InnerCacheTask<T> task = new InnerCacheTask<T>(cacheAble);
task.beginConvertDataAsync(CONVERT_FOR_CREATE);
}
public void setCacheData(final String cacheKey, final String data)
{
if (TextUtils.isEmpty(cacheKey) || TextUtils.isEmpty(data))
{
return;
}
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, setCacheData", cacheKey);
}
SimpleExecutor.getInstance().execute(
new Runnable()
{
@Override
public void run()
{
CacheInfo cacheInfo = CacheInfo.createForNow(data);
putDataToMemoryCache(cacheKey, cacheInfo);
mFileCache.write(cacheKey, cacheInfo.getCacheData());
mFileCache.flushDiskCacheAsyncWithDelay(1000);
}
});
}
private class InnerCacheTask<T1> extends SimpleTask
{
private ICacheAble<T1> mCacheAble;
private CacheInfo mRawData;
private T1 mResult;
private byte mWorkType = 0;
private byte mConvertFor = 0;
private byte mCurrentStatus = 0;
public InnerCacheTask(ICacheAble<T1> cacheAble)
{
mCacheAble = cacheAble;
}
@SuppressLint("NewApi")
void beginQuery()
{
if (mCacheAble.cacheIsDisabled())
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, Cache is disabled, query from server",
mCacheAble.getCacheKey());
}
mCacheAble.createDataForCache(CacheManager.this);
return;
}
String cacheKey = mCacheAble.getCacheKey();
// try to find in runtime cache
mRawData = mMemoryCache.get(cacheKey);
if (mRawData != null)
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, exist in list",
mCacheAble.getCacheKey());
}
beginConvertDataAsync(CONVERT_FOR_MEMORY);
return;
}
// try read from cache data
boolean hasFileCache = mFileCache.has(mCacheAble.getCacheKey());
if (hasFileCache)
{
beginQueryFromCacheFileAsync();
return;
}
// try to read from assert cache file
String assertInitDataPath = mCacheAble.getAssertInitDataPath();
if (assertInitDataPath != null && assertInitDataPath.length() > 0)
{
beginQueryFromAssertCacheFileAsync();
return;
}
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, cache file not exist",
mCacheAble.getCacheKey());
}
mCacheAble.createDataForCache(CacheManager.this);
}
@Override
public void doInBackground()
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, doInBackground: mWorkType: %s",
mCacheAble.getCacheKey(), mWorkType);
}
switch (mWorkType)
{
case DO_READ_FROM_FILE:
doQueryFromCacheFileInBackground();
setCurrentStatus(AFTER_READ_FROM_FILE);
break;
case DO_READ_FROM_ASSERT:
doQueryFromAssertCacheFileInBackground();
setCurrentStatus(AFTER_READ_FROM_ASSERT);
break;
case DO_CONVERT:
doConvertDataInBackground();
setCurrentStatus(AFTER_CONVERT);
break;
default:
break;
}
}
@Override
public void onFinish()
{
switch (mCurrentStatus)
{
case AFTER_READ_FROM_FILE:
beginConvertDataAsync(CONVERT_FOR_FILE);
break;
case AFTER_READ_FROM_ASSERT:
beginConvertDataAsync(CONVERT_FOR_ASSERT);
break;
case AFTER_CONVERT:
done();
break;
default:
break;
}
}
private void beginQueryFromCacheFileAsync()
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, beginQueryFromCacheFileAsync",
mCacheAble.getCacheKey());
}
mWorkType = DO_READ_FROM_FILE;
restart();
SimpleExecutor.getInstance().execute(this);
}
private void beginQueryFromAssertCacheFileAsync()
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, beginQueryFromAssertCacheFileAsync",
mCacheAble.getCacheKey());
}
mWorkType = DO_READ_FROM_ASSERT;
restart();
SimpleExecutor.getInstance().execute(this);
}
private void beginConvertDataAsync(byte convertFor)
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, beginConvertDataAsync",
mCacheAble.getCacheKey());
}
mConvertFor = convertFor;
mWorkType = DO_CONVERT;
restart();
SimpleExecutor.getInstance().execute(this);
}
private void doQueryFromCacheFileInBackground()
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, try read cache data from file",
mCacheAble.getCacheKey());
}
String cacheContent = mFileCache.read(mCacheAble.getCacheKey());
JsonData jsonData = JsonData.create(cacheContent);
mRawData = CacheInfo.createFromJson(jsonData);
}
private void doQueryFromAssertCacheFileInBackground()
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, try read cache data from assert file",
mCacheAble.getCacheKey());
}
String cacheContent = FileUtil.readAssert(mContext,
mCacheAble.getAssertInitDataPath());
mRawData = CacheInfo.createInvalidated(cacheContent);
putDataToMemoryCache(mCacheAble.getCacheKey(), mRawData);
}
private void doConvertDataInBackground()
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, doConvertDataInBackground",
mCacheAble.getCacheKey());
}
JsonData data = JsonData.create(mRawData.getData());
mResult = mCacheAble.processRawDataFromCache(data);
}
private void setCurrentStatus(byte status)
{
mCurrentStatus = status;
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, setCurrentStatus: %s",
mCacheAble.getCacheKey(), status);
}
}
private void done()
{
long lastTime = mRawData.getTime();
long timeInterval = System.currentTimeMillis() / 1000 - lastTime;
boolean outOfDate = timeInterval > mCacheAble.getCacheTime()
|| timeInterval < 0;
switch (mConvertFor)
{
case CONVERT_FOR_ASSERT:
mCacheAble.onCacheData(CacheResultType.FROM_INIT_FILE, mResult,
outOfDate);
break;
case CONVERT_FOR_CREATE:
mCacheAble.onCacheData(CacheResultType.FROM_CREATED, mResult,
outOfDate);
break;
case CONVERT_FOR_FILE:
mCacheAble.onCacheData(CacheResultType.FROM_INIT_FILE, mResult,
outOfDate);
break;
case CONVERT_FOR_MEMORY:
mCacheAble.onCacheData(CacheResultType.FROM_CACHE_FILE,
mResult, outOfDate);
break;
}
if (outOfDate)
{
mCacheAble.createDataForCache(CacheManager.this);
}
}
}
@SuppressLint("NewApi")
private void putDataToMemoryCache(String key, CacheInfo data)
{
if (TextUtils.isEmpty(key))
{
return;
}
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, set cache to runtime cache list", key);
}
mMemoryCache.put(key, data);
}
/**
* delete cache by key
*
* @param key
*/
@SuppressLint("NewApi")
public void invalidateCache(String key)
{
if (DEBUG)
{
CLog.d(LOG_TAG, "%s, invalidateCache", key);
}
mFileCache.delete(key);
mMemoryCache.remove(key);
}
/**
* clear the memory cache
*/
@SuppressLint("NewApi")
public void clearMemoryCache()
{
if (mMemoryCache != null)
{
mMemoryCache.evictAll();
}
}
/**
* get the spaced has been used
*
* @return
*/
@SuppressLint("NewApi")
public int getMemoryCacheUsedSpace()
{
return mMemoryCache.size();
}
/**
* get the spaced max space in config
*
* @return
*/
@SuppressLint("NewApi")
public int getMemoryCacheMaxSpace()
{
return mMemoryCache.maxSize();
}
/**
* clear the disk cache
*/
public void clearDiskCache()
{
if (null != mFileCache)
{
mFileCache.clearCache();
}
}
/**
* return the file cache path
*
* @return
*/
public String getFileCachePath()
{
if (null != mFileCache)
{
return mFileCache.getCachePath();
}
return null;
}
/**
* get the used space in file cache
*
* @return
*/
public long getFileCacheUsedSpace()
{
return null != mFileCache ? mFileCache.getUsedSpace() : 0;
}
/**
* get the max space for file cache
*
* @return
*/
public long getFileCacheMaxSpace()
{
if (null != mFileCache)
{
return mFileCache.getMaxSize();
}
return 0;
}
}