file:/repo/ as the URL.
*/
public void setServer(String server) {
this.server = server;
}
private final List
* 0123456789ABCDEF something-1.0.jar
*
* consisting of an SHA-1 hash followed by a filename.
* The filename is relative to the manifest, usually a simple basename.
* If the file exists and has the specified hash, nothing is done.
* If it has the wrong hash, the task aborts with an error message.
* If it is missing, it is downloaded from the server (or copied from cache)
* using a filename derived from the basename of the file in the manifest and its hash.
* For example, the above line with a server of http://nowhere.net/repo/
* would try to download
*
* http://nowhere.net/repo/0123456789ABCDEF-something-1.0.jar
*
* Any version number etc. in the filename is purely informational;
* the "up to date" check is entirely based on the hash.
*/
public void addManifest(FileSet manifest) {
manifests.add(manifest);
}
private boolean clean;
/**
* If true, rather than creating binary files, will delete them.
* Any cache is ignored in this case.
* If a binary does not match its hash, the build is aborted:
* the file might be a precious customized version and should not be blindly deleted.
*/
public void setClean(boolean clean) {
this.clean = clean;
}
@Override
public void execute() throws BuildException {
for (FileSet fs : manifests) {
DirectoryScanner scanner = fs.getDirectoryScanner(getProject());
File basedir = scanner.getBasedir();
for (String include : scanner.getIncludedFiles()) {
File manifest = new File(basedir, include);
log("Scanning: " + manifest, Project.MSG_VERBOSE);
try {
InputStream is = new FileInputStream(manifest);
try {
BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("#")) {
continue;
}
if (line.trim().length() == 0) {
continue;
}
String[] hashAndFile = line.split(" ", 2);
if (hashAndFile.length < 2) {
throw new BuildException("Bad line '" + line + "' in " + manifest, getLocation());
}
String expectedHash = hashAndFile[0];
String baseName = hashAndFile[1];
File f = new File(manifest.getParentFile(), baseName);
if (!clean) {
if (!f.exists() || !hash(f).equals(expectedHash)) {
log("Creating " + f);
String cacheName = expectedHash + "-" + baseName;
if (cache != null) {
cache.mkdirs();
File cacheFile = new File(cache, cacheName);
if (!cacheFile.exists()) {
download(cacheName, cacheFile, expectedHash);
}
if (f.isFile() && !f.delete()) {
throw new BuildException("Could not delete " + f);
}
try {
FileUtils.getFileUtils().copyFile(cacheFile, f);
} catch (IOException x) {
throw new BuildException("Could not copy " + cacheFile + " to " + f + ": " + x, x, getLocation());
}
} else {
download(cacheName, f, expectedHash);
}
}
String actualHash = hash(f);
if (!actualHash.equals(expectedHash)) {
throw new BuildException("File " + f + " requested by " + manifest + " to have hash " +
expectedHash + " actually had hash " + actualHash, getLocation());
}
log("Have " + f + " with expected hash", Project.MSG_VERBOSE);
} else {
if (f.exists()) {
String actualHash = hash(f);
if (!actualHash.equals(expectedHash)) {
throw new BuildException("File " + f + " requested by " + manifest + " to have hash " +
expectedHash + " actually had hash " + actualHash, getLocation());
}
log("Deleting " + f);
f.delete();
}
}
}
} finally {
is.close();
}
} catch (IOException x) {
throw new BuildException("Could not open " + manifest + ": " + x, x, getLocation());
}
}
}
}
private void download(String cacheName, File destination, String expectedHash) {
if (server == null) {
throw new BuildException("Must specify a server to download files from", getLocation());
}
Throwable firstProblem = null;
for (String prefix : server.split(" ")) {
URL url;
try {
url = new URL(prefix + cacheName);
} catch (MalformedURLException x) {
throw new BuildException(x, getLocation());
}
try {
URLConnection conn = url.openConnection();
conn.connect();
int code = HttpURLConnection.HTTP_OK;
if (conn instanceof HttpURLConnection) {
code = ((HttpURLConnection) conn).getResponseCode();
}
if (code != HttpURLConnection.HTTP_OK) {
log("Skipping download from " + url + " due to response code " + code, Project.MSG_VERBOSE);
continue;
}
log("Downloading: " + url);
InputStream is = conn.getInputStream();
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] buf = new byte[4096];
int read;
while ((read = is.read(buf)) != -1) {
baos.write(buf, 0, read);
}
} catch (IOException x) {
throw new BuildException(x); // should not happen
}
byte[] data = baos.toByteArray();
String actualHash = hash(new ByteArrayInputStream(data));
if (!expectedHash.equals(actualHash)) {
throw new BuildException("Download of " + url + " produced content with hash " +
actualHash + " when " + expectedHash + " was expected", getLocation());
}
OutputStream os = new FileOutputStream(destination);
try {
os.write(data);
} catch (IOException x) {
os.close();
destination.delete();
throw x;
}
os.close();
} finally {
is.close();
}
return;
} catch (IOException x) {
String msg = "Could not download " + url + " to " + destination + ": " + x;
log(msg, Project.MSG_WARN);
if (firstProblem == null) {
firstProblem = new IOException(msg).initCause(x);
}
}
}
throw new BuildException("Could not download " + cacheName + " from " + server + ": " + firstProblem, firstProblem, getLocation());
}
private String hash(File f) {
try {
FileInputStream is = new FileInputStream(f);
try {
return hash(is);
} finally {
is.close();
}
} catch (IOException x) {
throw new BuildException("Could not get hash for " + f + ": " + x, x, getLocation());
}
}
private String hash(InputStream is) throws IOException {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException x) {
throw new BuildException(x, getLocation());
}
byte[] buf = new byte[4096];
int r;
while ((r = is.read(buf)) != -1) {
digest.update(buf, 0, r);
}
return String.format("%040X", new BigInteger(1, digest.digest()));
}
}
/*
Sample upload script (edit repository location as needed):
#!/usr/bin/env ruby
repository = '/tmp/repository'
require 'cgi'
require 'digest/sha1'
require 'date'
cgi = CGI.new
begin
if cgi.request_method == 'POST'
value = cgi['file']
content = value.read
name = value.original_filename.gsub(/\.\.|[^a-zA-Z0-9._+-]/, '_')
sha1 = Digest::SHA1.hexdigest(content).upcase
open("#{repository}/#{sha1}-#{name}", "w") do |f|
f.write content
end
open("#{repository}/log", "a") do |f|
f << "#{DateTime.now.to_s} #{sha1}-#{name} #{cgi.remote_user}\n"
end
cgi.out do <Uploaded. Add to your manifest:
#{sha1} #{name}