package com.koushikdutta.async.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
/**
* Created by koush on 4/12/14.
*/
public class FileCache {
class CacheEntry {
final long size;
public CacheEntry(File file) {
size = file.length();
}
}
public static class Snapshot {
FileInputStream[] fins;
long[] lens;
Snapshot(FileInputStream[] fins, long[] lens) {
this.fins = fins;
this.lens = lens;
}
public long getLength(int index) {
return lens[index];
}
public void close() {
StreamUtility.closeQuietly(fins);
}
}
private static String hashAlgorithm = "MD5";
private static MessageDigest findAlternativeMessageDigest() {
if ("MD5".equals(hashAlgorithm)) {
for (Provider provider : Security.getProviders()) {
for (Provider.Service service : provider.getServices()) {
hashAlgorithm = service.getAlgorithm();
try {
MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm);
if (messageDigest != null)
return messageDigest;
} catch (NoSuchAlgorithmException ignored) {
}
}
}
}
return null;
}
static MessageDigest messageDigest;
static {
try {
messageDigest = MessageDigest.getInstance(hashAlgorithm);
} catch (NoSuchAlgorithmException e) {
messageDigest = findAlternativeMessageDigest();
if (null == messageDigest)
throw new RuntimeException(e);
}
try {
messageDigest = (MessageDigest)messageDigest.clone();
}
catch (CloneNotSupportedException e) {
}
}
public static synchronized String toKeyString(Object... parts) {
messageDigest.reset();
for (Object part : parts) {
messageDigest.update(part.toString().getBytes());
}
byte[] md5bytes = messageDigest.digest();
return new BigInteger(1, md5bytes).toString(16);
}
boolean loadAsync;
Random random = new Random();
public File getTempFile() {
File f;
while ((f = new File(directory, new BigInteger(128, random).toString(16))).exists());
return f;
}
public File[] getTempFiles(int count) {
File[] ret = new File[count];
for (int i = 0; i < count; i++) {
ret[i] = getTempFile();
}
return ret;
}
public static void removeFiles(File... files) {
if (files == null)
return;
for (File file: files) {
file.delete();
}
}
public void remove(String key) {
int i = 0;
while (cache.remove(getPartName(key, i)) != null) {
i++;
}
removePartFiles(key);
}
public boolean exists(String key, int part) {
return getPartFile(key, part).exists();
}
public boolean exists(String key) {
return getPartFile(key, 0).exists();
}
public File touch(File file) {
cache.get(file.getName());
file.setLastModified(System.currentTimeMillis());
return file;
}
public FileInputStream get(String key) throws IOException {
return new FileInputStream(touch(getPartFile(key, 0)));
}
public File getFile(String key) {
return touch(getPartFile(key, 0));
}
public FileInputStream[] get(String key, int count) throws IOException {
FileInputStream[] ret = new FileInputStream[count];
try {
for (int i = 0; i < count; i++) {
ret[i] = new FileInputStream(touch(getPartFile(key, i)));
}
}
catch (IOException e) {
// if we can't get all the parts, delete everything
for (FileInputStream fin: ret) {
StreamUtility.closeQuietly(fin);
}
remove(key);
throw e;
}
return ret;
}
String getPartName(String key, int part) {
return key + "." + part;
}
public void commitTempFiles(String key, File... tempFiles) {
removePartFiles(key);
// try to rename everything
for (int i = 0; i < tempFiles.length; i++) {
File tmp = tempFiles[i];
File partFile = getPartFile(key, i);
if (!tmp.renameTo(partFile)) {
// if any rename fails, delete everything
removeFiles(tempFiles);
remove(key);
return;
}
remove(tmp.getName());
cache.put(getPartName(key, i), new CacheEntry(partFile));
}
}
void removePartFiles(String key) {
int i = 0;
File f;
while ((f = getPartFile(key, i)).exists()) {
f.delete();
i++;
}
}
File getPartFile(String key, int part) {
return new File(directory, getPartName(key, part));
}
long blockSize = 4096;
public void setBlockSize(long blockSize) {
this.blockSize = blockSize;
}
class InternalCache extends LruCache<String, CacheEntry> {
public InternalCache() {
super(size);
}
@Override
protected long sizeOf(String key, CacheEntry value) {
return Math.max(blockSize, value.size);
}
@Override
protected void entryRemoved(boolean evicted, String key, CacheEntry oldValue, CacheEntry newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
if (newValue != null)
return;
if (loading)
return;
new File(directory, key).delete();
}
}
InternalCache cache;
File directory;
long size;
Comparator<File> dateCompare = new Comparator<File>() {
@Override
public int compare(File lhs, File rhs) {
long l = lhs.lastModified();
long r = rhs.lastModified();
if (l < r)
return -1;
if (r > l)
return 1;
return 0;
}
};
boolean loading;
void load() {
loading = true;
try {
File[] files = directory.listFiles();
if (files == null)
return;
ArrayList<File> list = new ArrayList<File>();
Collections.addAll(list, files);
Collections.sort(list, dateCompare);
for (File file: list) {
String name = file.getName();
CacheEntry entry = new CacheEntry(file);
cache.put(name, entry);
cache.get(name);
}
}
finally {
loading = false;
}
}
private void doLoad() {
if (loadAsync) {
new Thread() {
@Override
public void run() {
load();
}
}.start();
}
else {
load();
}
}
public FileCache(File directory, long size, boolean loadAsync) {
this.directory = directory;
this.size = size;
this.loadAsync = loadAsync;
cache = new InternalCache();
directory.mkdirs();
doLoad();
}
public long size() {
return cache.size();
}
public void clear() {
removeFiles(directory.listFiles());
cache.evictAll();
}
public Set<String> keySet() {
HashSet<String> ret = new HashSet<String>();
File[] files = directory.listFiles();
if (files == null)
return ret;
for (File file: files) {
String name = file.getName();
int last = name.lastIndexOf('.');
if (last != -1)
ret.add(name.substring(0, last));
}
return ret;
}
public void setMaxSize(long maxSize) {
cache.setMaxSize(maxSize);
doLoad();
}
}