blob: b1844b8549105527928fe8ed35761212c7fa04ee [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package vogar;
import vogar.commands.Command;
import vogar.commands.Mkdir;
import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;
import java.util.logging.Logger;
/**
* Caches content by MD5.
*/
public final class Md5Cache {
private static final Logger logger = Logger.getLogger(Md5Cache.class.getName());
private static final File CACHE_ROOT = new File("/tmp/vogar-md5-cache/");
private final String keyPrefix;
/**
* Creates a new cache accessor. There's only one directory on disk, so 'keyPrefix' is really
* just a convenience for humans inspecting the cache.
*/
public Md5Cache(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/**
* Returns an ASCII hex representation of the MD5 of the content of 'file'.
*/
private static String md5(File file) {
byte[] digest = null;
try {
MessageDigest digester = MessageDigest.getInstance("MD5");
byte[] bytes = new byte[8192];
FileInputStream in = new FileInputStream(file);
try {
int byteCount;
while ((byteCount = in.read(bytes)) > 0) {
digester.update(bytes, 0, byteCount);
}
digest = digester.digest();
} finally {
in.close();
}
} catch (Exception cause) {
throw new RuntimeException("Unable to compute MD5 of \"" + file + "\"", cause);
}
return (digest == null) ? null : byteArrayToHexString(digest);
}
private static String byteArrayToHexString(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(Integer.toHexString((b >> 4) & 0xf));
result.append(Integer.toHexString(b & 0xf));
}
return result.toString();
}
/**
* Returns the appropriate key for a dex file corresponding to the contents of 'classpath'.
* Returns null if we don't think it's possible to cache the given classpath.
*/
public File makeKey(Classpath classpath) {
// Do we have it in cache?
String key = keyPrefix;
for (File element : classpath.getElements()) {
// We only cache dexed .jar files, not directories.
if (!element.toString().endsWith(".jar")) {
return null;
}
key += "-" + md5(element);
}
return new File(CACHE_ROOT, key);
}
/**
* Copy the file 'content' into the cache with the given 'key'.
* This method assumes you're using the appropriate key for the content (and has no way to
* check because the key is a function of the inputs that made the content, not the content
* itself).
* We accept a null so the caller doesn't have to pay attention to whether we think we can
* cache the content or not.
*/
public void insert(File key, File content) {
if (key == null) {
return;
}
logger.fine("inserting " + key);
if (!key.toString().startsWith(CACHE_ROOT.toString())) {
throw new IllegalArgumentException("key '" + key + "' not a valid cache key");
}
// Make sure the cache exists first.
new Mkdir().mkdirs(CACHE_ROOT);
// Copy it onto the same file system first, then atomically move it into place.
// That way, if we fail, we don't leave anything dangerous lying around.
File temporary = new File(key + ".tmp");
new Command.Builder().args("cp", content, temporary).execute();
new Command.Builder().args("mv", temporary, key).execute();
}
}