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 { + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(manifest), "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()); + } + File f = new File(manifest.getParentFile(), hashAndFile[1]); + if (!clean) { + if (!f.exists()) { + log("Creating " + f); + String cacheName = hashAndFile[0] + "-" + f.getName(); + if (cache != null) { + cache.mkdirs(); + File cacheFile = new File(cache, cacheName); + if (!cacheFile.exists()) { + download(cacheName, cacheFile); + } + 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); + } + } + String actualHash = hash(f); + if (!actualHash.equals(hashAndFile[0])) { + throw new BuildException("File " + f + " requested by " + manifest + " to have hash " + + hashAndFile[0] + " 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(hashAndFile[0])) { + throw new BuildException("File " + f + " requested by " + manifest + " to have hash " + + hashAndFile[0] + " actually had hash " + actualHash, getLocation()); + } + log("Deleting " + f); + f.delete(); + } + } + } + } catch (IOException x) { + throw new BuildException("Could not open " + manifest + ": " + x, x, getLocation()); + } + } + } + } + + private void download(String cacheName, File destination) { + if (server == null) { + throw new BuildException("Must specify a server to download files from", getLocation()); + } + 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); + } + log("Downloading: " + url); + InputStream is = conn.getInputStream(); + try { + OutputStream os = new FileOutputStream(destination); + try { + byte[] buf = new byte[4096]; + int read; + while ((read = is.read(buf)) != -1) { + os.write(buf, 0, read); + } + os.close(); + } catch (IOException x) { + destination.delete(); + throw x; + } + } finally { + is.close(); + } + return; + } catch (IOException x) { + log("Could not download " + url + " to " + destination + ": " + x, Project.MSG_WARN); + } + } + throw new BuildException("Could not download " + cacheName + " from " + server, getLocation()); + } + + private String hash(File f) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException x) { + throw new BuildException(x, getLocation()); + } + try { + FileInputStream is = new FileInputStream(f); + try { + digest.update(is.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f.length())); + } finally { + is.close(); + } + } catch (IOException x) { + throw new BuildException("Could not get hash for " + f + ": " + x, x, getLocation()); + } + 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}+ +