import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;
public class LocalCache {
private static final String TAG = "LocalCache";
private static final String CACHE_ASE_KEY = "CACHE_ASE_KEY";
private Map<String, Map<String, Entry>> mCacheMap;
private String mCacheDirPath;
private static LocalCache INSTANCE;
public static void setup(String cacheDirPath) {
INSTANCE = new LocalCache(cacheDirPath);
}
public static LocalCache getInstance() {
return INSTANCE;
}
public LocalCache(String cacheDirPath) {
mCacheDirPath = cacheDirPath;
mCacheMap = Collections.synchronizedMap(new HashMap<String, Map<String, Entry>>());
init();
}
private void init() {
File rootDir = new File(mCacheDirPath);
String[] list = rootDir.list();
for (String name : list) {
String[] split = name.split("_");
Entry entry = new Entry(split[0], split[1], Integer.parseInt(split[2]));
Map<String, Entry> map = mCacheMap.get(entry.key);
if (map == null) {
map = new HashMap<>();
mCacheMap.put(entry.key, map);
}
map.put(entry.language, entry);
}
}
Observable<String> loadCache(final String key, final String language) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext(getCacheData(key, language));
emitter.onComplete();
}
}).onErrorResumeNext(new Function<Throwable, ObservableSource<String>>() {
@Override
public ObservableSource<String> apply(@NonNull Throwable throwable) throws Exception {
return Observable.empty();
}
});
}
public int getCacheVersion(String key, String language) {
int version = 0;
Map<String, Entry> map = mCacheMap.get(key);
if (map != null) {
Entry entry = map.get(language);
if (entry != null) {
version = entry.version;
}
}
return version;
}
public String getCacheData(String key, String language) throws IOException {
String content = null;
Map<String, Entry> map = mCacheMap.get(key);
if (map != null) {
Entry entry = map.get(language);
if (entry != null) {
byte[] data = readCacheData(entry);
content = decodeData(data);
}
}
return content;
}
public void saveCacheData(String key, String language, int version, String content) throws IOException {
synchronized (key) {
Map<String, Entry> map = mCacheMap.get(key);
if (map == null) {
map = new HashMap<>();
mCacheMap.put(key, map);
}
Entry entry = map.remove(language);
if (entry != null) {
deleteCache(entry);
}
entry = new Entry(key, language, version);
byte[] bytes = encodeData(content);
writeCacheData(entry, bytes);
map.put(language, entry);
}
}
public void deleteCache(String key, String language) {
synchronized (key) {
Map<String, Entry> map = mCacheMap.get(key);
if (map != null) {
Entry entry = map.remove(language);
if (entry != null) {
deleteCache(entry);
}
}
}
}
private byte[] readCacheData(Entry entry) throws IOException {
synchronized (entry) {
File file = new File(mCacheDirPath, String.format("%s_%s_%s", entry.key, entry.language, entry.version));
Log.d(TAG, "readCacheData size=" + file.length() + " path=>" + file.getAbsolutePath());
if (file.exists()) {
FileInputStream ips = new FileInputStream(file);
ByteArrayOutputStream ops = new ByteArrayOutputStream();
byte[] buff = new byte[4096]; int len;
while ((len = ips.read(buff)) != -1) {
ops.write(buff, 0, len);
}
ips.close();
return ops.toByteArray();
}
return null;
}
}
private void writeCacheData(Entry entry, byte[] data) throws IOException {
synchronized (entry) {
File file = new File(mCacheDirPath, String.format("%s_%s_%s", entry.key, entry.language, entry.version));
FileOutputStream ops = new FileOutputStream(file);
ops.write(data);
ops.flush();
ops.close();
Log.d(TAG, "writeCacheData size=" + file.length() + " path=>" + file.getAbsolutePath());
}
}
private void deleteCache(Entry entry) {
synchronized (entry) {
File file = new File(mCacheDirPath, String.format("%s_%s_%s", entry.key, entry.language, entry.version));
file.delete();
}
}
private String decodeData(byte[] data) {
try {
Log.d(TAG, "decodeData=>" + new String(data, "UTF-8"));
byte[] rawKey = MessageDigest.getInstance("MD5").digest(CACHE_ASE_KEY.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] bytes = Base64.decode(data, Base64.NO_WRAP);
return new String(cipher.doFinal(bytes), "UTF-8");
} catch (Exception e) {
Log.e(TAG, "decodeAES", e);
}
return null;
}
private byte[] encodeData(String content) {
try {
byte[] rawKey = MessageDigest.getInstance("MD5").digest(CACHE_ASE_KEY.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] bytes = cipher.doFinal(content.getBytes("UTF-8"));
byte[] encode = Base64.encode(bytes, Base64.NO_WRAP);
Log.d(TAG, "encodeData=>" + new String(encode, "UTF-8"));
return encode;
} catch (Exception e) {
Log.e(TAG, "encodeAES", e);
}
return null;
}
private final class Entry {
private final String key;
private final String language;
private int version;
public Entry(String key, String language, int version) {
this.key = key;
this.language = language;
this.version = version;
}
}
}