/**
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.ut.biolab.medsavant.shared.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
*
* @author AndrewBrook
*/
public class RemoteFileCache {
private static final Log LOG = LogFactory.getLog(RemoteFileCache.class);
/** So multiple threads don't try updating the cache index at the same time. */
private static final Object indexLock = new Object();
/**
* Get the cache file corresponding to the given path.
* @param url URL whose hash we use to determine changes
* @param source path to our resource (often the same as url)
* @param bufferSize block size of buffer (0 if not using block-based cacheing)
* @param length file length (written to index but not actually used)
* @return file containing cache data for <code>url</code>
* @throws IOException
*/
public static File getCacheFile(URL url, String source, int bufferSize, long length) throws IOException {
synchronized (indexLock) {
//create index
File cacheDir = DirectorySettings.getCacheDirectory();
File index = new File(cacheDir, "cacheIndex");
index.createNewFile();
// Check for entry
String newETag = NetworkUtils.getHash(url);
boolean entryFound = false;
boolean entryInvalid = false;
BufferedReader bufferedReader = new BufferedReader(new FileReader(index));
String line;
List<String> allLines = new ArrayList<String>();
File cacheFile = null;
while ((line = bufferedReader.readLine()) != null) {
if (!entryFound) {
String[] lineArray = line.split(",");
if (source.equals(lineArray[0])) {
entryFound = true;
// Equivalent entry found
cacheFile = new File(lineArray[4]);
// Compare ETags and buffer sizes. Could also check file lengths (lineArray[2]), but
// we currently don't do that, since the ETag should reflect such a change.
if (!lineArray[1].equals(newETag) || bufferSize != Integer.parseInt(lineArray[3])) {
// ETag changed or new buffer size. Cache file is invalid.
LOG.info("Removed out-of-date cache file " + cacheFile + " for " + url);
entryInvalid = true;
cacheFile.delete();
continue;
}
}
}
allLines.add(line);
}
if (entryInvalid) {
// We've invalidated a cache entry, so rewrite the index file without the entry.
BufferedWriter out = new BufferedWriter(new FileWriter(index, false));
for (String l: allLines) {
out.write(l);
out.newLine();
}
out.close();
}
// Add entry
if (entryInvalid || !entryFound) {
// Special case. If it's a non-blocked file, write -1 for bufferSize so that we know the file has not
// yet been completely written.
if (bufferSize == 0) {
bufferSize = -1;
}
BufferedWriter out = new BufferedWriter(new FileWriter(index, true));
cacheFile = new File(cacheDir, source.replaceAll("[\\:/]", "+"));
out.write(source + "," +
newETag + "," +
length + "," +
bufferSize + "," +
cacheFile); // replace all instances of \/:*?"<>|
out.newLine();
out.close();
}
return cacheFile;
}
}
/**
* Get the cache file corresponding to the given path. In MedSavant, we don't currently use block-level cacheing.
* @param url URL whose hash we use to determine changes
* @return file containing cache data for <code>url</code>
* @throws IOException
*/
public static File getCacheFile(URL url) throws IOException {
File result = findCacheEntry(url.toString());
if (result == null) {
LOG.info(url + " not found in cache, downloading...");
result = getCacheFile(url, url.toString(), 0, 0);
NetworkUtils.downloadFile(url, result.getParentFile(), result.getName());
updateCacheEntry(result);
}
return result;
}
/**
* For un-blocked files we use the bufferSize to indicate whether the file has been
* completely downloaded. It is set to -1 when the download starts and to 0 when it completes.
*
* @param f the the cache file to be updated
* @throws IOException
*/
public static void updateCacheEntry(File f) throws IOException {
synchronized (indexLock) {
File cacheDir = DirectorySettings.getCacheDirectory();
File index = new File(cacheDir, "cacheIndex");
index.createNewFile();
// Check for entry
String path = f.getAbsolutePath();
BufferedReader bufferedReader = new BufferedReader(new FileReader(index));
String line;
List<String> allLines = new ArrayList<String>();
String[] updatedLine = null;
while ((line = bufferedReader.readLine()) != null) {
if (updatedLine == null) {
String[] lineArray = line.split(",");
if (path.equals(lineArray[4])) {
updatedLine = lineArray;
continue;
}
}
allLines.add(line);
}
// We're updating a cache entry, so rewrite the index file without the entry.
BufferedWriter out = new BufferedWriter(new FileWriter(index, false));
for (String l: allLines) {
out.write(l);
out.newLine();
}
if (updatedLine != null && f.exists()) {
out.write(updatedLine[0] + "," + updatedLine[1] + "," + updatedLine[2] + ",0," + updatedLine[4]);
out.newLine();
}
out.close();
}
}
public static void removeCacheEntry(String source) throws IOException {
synchronized (indexLock) {
File oldFile = findCacheEntry(source);
oldFile.delete();
updateCacheEntry(oldFile);
}
}
/**
* Determine whether there is a valid entry for the given source URL.
* @param source URL of the source
* @return true if we successfully found the source in our index
*/
public static File findCacheEntry(String source) {
BufferedReader reader = null;
try {
File cacheDir = DirectorySettings.getCacheDirectory();
File index = new File(cacheDir, "cacheIndex");
index.createNewFile();
// Check for entry
reader = new BufferedReader(new FileReader(index));
String line;
while ((line = reader.readLine()) != null) {
String[] lineArray = line.split(",");
if (source.equals(lineArray[0]) && !lineArray[3].equals("-1")) {
return new File(lineArray[4]);
}
}
} catch (IOException x) {
LOG.error("Error reading cache.", x);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignored) {
}
}
}
return null;
}
}