package eu.nextap.cache;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.webkit.WebResourceResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static eu.nextap.cache.CacheEntryBehaviour.CACHE_ALL;
import static eu.nextap.cache.CacheEntryProcess.*;
import static eu.nextap.cache.CacheStorage.USE_INTERNAL_STORAGE;
import static eu.nextap.cache.FileUtils.copyAndSaveStreams;
import static eu.nextap.cache.FileUtils.getMimeType;
/**
* All processes of caching like saving, loading or editing,
*
* @author Tomáš Vítek
*/
public class UrlCache {
/**
* Define time constants how long will be cache saved.
*/
public static final long ONE_SECOND = 1000L;
public static final long ONE_MINUTE = 60L * ONE_SECOND;
public static final long ONE_HOUR = 60L * ONE_MINUTE;
public static final long ONE_DAY = 24 * ONE_HOUR;
public static final long ONE_WEEK = 7 * ONE_DAY;
public static final long ONE_MONTH = 4 * ONE_WEEK;
public static final long ONE_YEAR = 12 * ONE_MONTH;
public static final long INFINITY = 100000000 * ONE_YEAR;
public static final String ENCODING = "UTF-8";
/**
* Class name.
*/
private static String TAG = "UrlCache";
/**
* Define other variables.
*/
private CacheEntryBehaviour cacheEntryBehaviour = CACHE_ALL;
private CacheStorage cacheStorage = USE_INTERNAL_STORAGE;
private long maxAgeMillis;
/**
* Define objects.
*/
private Map<String, CacheEntry> cacheEntries = new HashMap<String, CacheEntry>();
private Context context;
private File rootDir;
private PathGenerator pathGenerator;
/**
* Set thread to load web and in background download files.
*/
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(NUMBER_OF_CORES * 2, NUMBER_OF_CORES * 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
/**
* UrlCache constructor which will detect if we want to save files on internal or external directory.
*/
public UrlCache(Context context) {
this.context = context;
executor.allowsCoreThreadTimeOut();
setFilesDirectory(getCacheStorage());
setPathGenerator(new UrlPathGenerator());
}
/**
* Register new file in cache so create new path and generate cacheEntry.
*/
public void registerCacheEntry(String url, String mimeType, String encoding) {
//Create unique path.
String filePath = this.rootDir.getPath() + File.separator + pathGenerator.createPath(url);
//Create new CacheEntry.
CacheEntry entry = new CacheEntry(url, filePath, mimeType, encoding);
//Put url and CacheEntry into CacheEntries Map.
this.cacheEntries.put(url, entry);
}
/**
* Load saved cache file or if cache file isnt available download and save it again.
*/
public WebResourceResponse load(final String url) {
//Get current CacheEntry.
CacheEntry cacheEntry = this.cacheEntries.get(url);
//Set when will be cache removed.
setMaxAgeMillis(ONE_HOUR);
//Check if cacheEntry isn't null.
if (cacheEntry == null) {
switch (getCacheEntryBehaviour()) {
case CACHE_ALL:
//Create new cacheEntry.
registerCacheEntry(url, getMimeType(url), ENCODING);
cacheEntry = this.cacheEntries.get(url);
break;
case USE_CACHE_ENTRY:
return null;
default:
throw new IllegalArgumentException("Unknown CacheEntryBehaviour");
}
}
//Find file in definite path.
File cachedFile = new File(cacheEntry.filePath);
//Check if cachedFile doesn't exists.
if (cachedFile.exists()) {
//Define new cacheEntry age.
long cacheEntryAge = System.currentTimeMillis() - cachedFile.lastModified();
//Check if new cacheEntry age isn't bigger than cacheEntry's max age.
if (cacheEntryAge > maxAgeMillis) {
//CacheEntry is not downloaded because time expires
cacheEntry.setCacheEntryProcess(ERROR);
//Cached file deleted.
cachedFile.delete();
//Call load() again.
return load(url);
}
//Cached file exists and is not too old so try to return File.
try {
cacheEntry.setCacheEntryProcess(DOWNLOADED);
return new WebResourceResponse(cacheEntry.mimeType, cacheEntry.encoding, new FileInputStream(cachedFile));
} catch (FileNotFoundException e) {
cacheEntry.setCacheEntryProcess(ERROR);
Log.e(TAG, e.getMessage(), e);
}
} else {
cacheEntry.setCacheEntryProcess(ERROR);
try {
cacheEntry.setCacheEntryProcess(SCHEDULED);
executor.execute(new DownloadInBackground(url, cacheEntry));
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
return null;
}
/**
* Download current file and save it into cache.
*/
private void downloadAndStore(String url, CacheEntry cacheEntry) throws IOException {
Log.d(TAG, "Downloading " + url);
cacheEntry.setCacheEntryProcess(DOWNLOADING);
//Define objects.
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
//Create new file from cacheEntry path.
File file = new File(cacheEntry.filePath);
//Configure path.
if (!file.exists()) {
file.getParentFile().mkdirs();
}
//Open cached file.
FileOutputStream fileOutputStream = new FileOutputStream(file);
InputStream urlInput = urlConnection.getInputStream();
copyAndSaveStreams(fileOutputStream, urlInput);
//Close input and output streams.
urlInput.close();
fileOutputStream.close();
cacheEntry.setCacheEntryProcess(DOWNLOADED);
Log.d(TAG, "Saved " + url);
}
/**
* Will set where will be files saved.
*/
private void setFilesDirectory(CacheStorage cacheStorage) {
switch (cacheStorage) {
case USE_EXTERNAL_STORAGE:
this.rootDir = Environment.getExternalStorageDirectory();
break;
case USE_INTERNAL_STORAGE:
this.rootDir = this.context.getFilesDir();
break;
}
}
public void removeRootDirectory(String url) {
this.pathGenerator.removeRootDirectory(url);
}
public void setCacheEntryBehaviour(CacheEntryBehaviour cacheEntryBehaviour) {
this.cacheEntryBehaviour = cacheEntryBehaviour;
}
/**
* Getters and setters.
*/
public CacheEntryBehaviour getCacheEntryBehaviour() {
return cacheEntryBehaviour;
}
public CacheStorage getCacheStorage() {
return cacheStorage;
}
public void setCacheStorage(CacheStorage cacheStorage) {
this.cacheStorage = cacheStorage;
}
public PathGenerator getPathGenerator() {
return pathGenerator;
}
public void setPathGenerator(PathGenerator pathGenerator) {
this.pathGenerator = pathGenerator;
}
public long getMaxAgeMillis() {
return maxAgeMillis;
}
public void setMaxAgeMillis(long maxAgeMillis) {
this.maxAgeMillis = maxAgeMillis;
}
/**
* Will download files in background during web will be showed.
*/
private class DownloadInBackground implements Runnable {
private String url;
private CacheEntry finalCacheEntry;
DownloadInBackground(String url, CacheEntry cacheEntry) {
this.url = url;
this.finalCacheEntry = cacheEntry;
}
@Override
public void run() {
//Download and save into cache.
try {
downloadAndStore(url, finalCacheEntry);
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
}
}
/**
* CacheEntry is used to save one file from web, so we will use probably many cacheEntries.
*/
private static class CacheEntry {
/**
* Declare variables.
*/
private String url;
private String filePath;
private String mimeType;
private String encoding;
private CacheEntryProcess cacheEntryProcess;
/**
* CacheEntry constructor.
*/
private CacheEntry(String url, String filePath, String mimeType, String encoding) {
//Define variables.
this.url = url;
this.filePath = filePath;
this.mimeType = mimeType;
this.encoding = encoding;
}
public CacheEntryProcess getCacheEntryProcess() {
return cacheEntryProcess;
}
public void setCacheEntryProcess(CacheEntryProcess cacheEntryProcess) {
this.cacheEntryProcess = cacheEntryProcess;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
}
}
Be the first to comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.