/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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
 *
 *      https://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 org.apache.tools.ant.taskdefs;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.MagicNames;
import org.apache.tools.ant.Main;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.email.Header;
import org.apache.tools.ant.types.Mapper;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.Resources;
import org.apache.tools.ant.types.resources.URLProvider;
import org.apache.tools.ant.types.resources.URLResource;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.StringUtils;

Gets a particular file from a URL source. Options include verbose reporting, timestamp based fetches and controlling actions on failures. NB: access through a firewall only works if the whole Java runtime is correctly configured.
Since:Ant 1.1
@ant.taskcategory="network"
/** * Gets a particular file from a URL source. * Options include verbose reporting, timestamp based fetches and controlling * actions on failures. NB: access through a firewall only works if the whole * Java runtime is correctly configured. * * @since Ant 1.1 * * @ant.task category="network" */
public class Get extends Task { private static final int NUMBER_RETRIES = 3; private static final int DOTS_PER_LINE = 50; private static final int BIG_BUFFER_SIZE = 100 * 1024; private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); private static final int REDIRECT_LIMIT = 25; // HttpURLConnection doesn't have a constant for this in Java5 and // what it calls HTTP_MOVED_TEMP would better be FOUND private static final int HTTP_MOVED_TEMP = 307; private static final String HTTP = "http"; private static final String HTTPS = "https"; private static final String DEFAULT_AGENT_PREFIX = "Apache Ant"; private static final String GZIP_CONTENT_ENCODING = "gzip"; private final Resources sources = new Resources(); private File destination; // required private boolean verbose = false; private boolean quiet = false; private boolean useTimestamp = false; //off by default private boolean ignoreErrors = false; private String uname = null; private String pword = null; private long maxTime = 0; private int numberRetries = NUMBER_RETRIES; private boolean skipExisting = false; private boolean httpUseCaches = true; // on by default private boolean tryGzipEncoding = false; private Mapper mapperElement = null; private String userAgent = System.getProperty(MagicNames.HTTP_AGENT_PROPERTY, DEFAULT_AGENT_PREFIX + "/" + Main.getShortAntVersion()); // Store headers as key/value pair without duplicate in keys private Map<String, String> headers = new LinkedHashMap<>();
Does the work.
Throws:
  • BuildException – Thrown in unrecoverable error.
/** * Does the work. * * @exception BuildException Thrown in unrecoverable error. */
@Override public void execute() throws BuildException { checkAttributes(); for (final Resource r : sources) { final URLProvider up = r.as(URLProvider.class); final URL source = up.getURL(); File dest = destination; if (destination.isDirectory()) { if (mapperElement == null) { String path = source.getPath(); if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } final int slash = path.lastIndexOf('/'); if (slash > -1) { path = path.substring(slash + 1); } dest = new File(destination, path); } else { final FileNameMapper mapper = mapperElement.getImplementation(); final String[] d = mapper.mapFileName(source.toString()); if (d == null) { log("skipping " + r + " - mapper can't handle it", Project.MSG_WARN); continue; } if (d.length == 0) { log("skipping " + r + " - mapper returns no file name", Project.MSG_WARN); continue; } if (d.length > 1) { log("skipping " + r + " - mapper returns multiple file" + " names", Project.MSG_WARN); continue; } dest = new File(destination, d[0]); } } //set up logging final int logLevel = Project.MSG_INFO; DownloadProgress progress = null; if (verbose) { progress = new VerboseProgress(System.out); } //execute the get try { doGet(source, dest, logLevel, progress); } catch (final IOException ioe) { log("Error getting " + source + " to " + dest); if (!ignoreErrors) { throw new BuildException(ioe, getLocation()); } } } }
make a get request, with the supplied progress and logging info. All the other config parameters are set at the task level, source, dest, ignoreErrors, etc.
Params:
Throws:
Returns:true for a successful download, false otherwise. The return value is only relevant when ignoreErrors is true, as when false all failures raise BuildExceptions.
Deprecated:only gets the first configured resource
/** * make a get request, with the supplied progress and logging info. * All the other config parameters are set at the task level, * source, dest, ignoreErrors, etc. * @param logLevel level to log at, see {@link Project#log(String, int)} * @param progress progress callback; null for no-callbacks * @return true for a successful download, false otherwise. * The return value is only relevant when {@link #ignoreErrors} is true, as * when false all failures raise BuildExceptions. * @throws IOException for network trouble * @throws BuildException for argument errors, or other trouble when ignoreErrors * is false. * @deprecated only gets the first configured resource */
@Deprecated public boolean doGet(final int logLevel, final DownloadProgress progress) throws IOException { checkAttributes(); return doGet(sources.iterator().next().as(URLProvider.class).getURL(), destination, logLevel, progress); }
make a get request, with the supplied progress and logging info. All the other config parameters like ignoreErrors are set at the task level.
Params:
  • source – the URL to get
  • dest – the target file
  • logLevel – level to log at, see Project.log(String, int)
  • progress – progress callback; null for no-callbacks
Throws:
Returns:true for a successful download, false otherwise. The return value is only relevant when ignoreErrors is true, as when false all failures raise BuildExceptions.
Since:Ant 1.8.0
/** * make a get request, with the supplied progress and logging info. * * All the other config parameters like ignoreErrors are set at * the task level. * @param source the URL to get * @param dest the target file * @param logLevel level to log at, see {@link Project#log(String, int)} * @param progress progress callback; null for no-callbacks * @return true for a successful download, false otherwise. * The return value is only relevant when {@link #ignoreErrors} is true, as * when false all failures raise BuildExceptions. * @throws IOException for network trouble * @throws BuildException for argument errors, or other trouble when ignoreErrors * is false. * @since Ant 1.8.0 */
public boolean doGet(final URL source, final File dest, final int logLevel, DownloadProgress progress) throws IOException { if (dest.exists() && skipExisting) { log("Destination already exists (skipping): " + dest.getAbsolutePath(), logLevel); return true; } // don't do any progress, unless asked if (progress == null) { progress = new NullProgress(); } log("Getting: " + source, logLevel); log("To: " + dest.getAbsolutePath(), logLevel); //set the timestamp to the file date. long timestamp = 0; boolean hasTimestamp = false; if (useTimestamp && dest.exists()) { timestamp = dest.lastModified(); if (verbose) { final Date t = new Date(timestamp); log("local file date : " + t.toString(), logLevel); } hasTimestamp = true; } final GetThread getThread = new GetThread(source, dest, hasTimestamp, timestamp, progress, logLevel, userAgent); getThread.setDaemon(true); getProject().registerThreadTask(getThread, this); getThread.start(); try { getThread.join(maxTime * 1000); } catch (final InterruptedException ie) { log("interrupted waiting for GET to finish", Project.MSG_VERBOSE); } if (getThread.isAlive()) { final String msg = "The GET operation took longer than " + maxTime + " seconds, stopping it."; if (ignoreErrors) { log(msg); } getThread.closeStreams(); if (!ignoreErrors) { throw new BuildException(msg); } return false; } return getThread.wasSuccessful(); } @Override public void log(final String msg, final int msgLevel) { if (!quiet || msgLevel <= Project.MSG_ERR) { super.log(msg, msgLevel); } }
Check the attributes.
/** * Check the attributes. */
private void checkAttributes() { if (userAgent == null || userAgent.trim().isEmpty()) { throw new BuildException("userAgent may not be null or empty"); } if (sources.size() == 0) { throw new BuildException("at least one source is required", getLocation()); } for (final Resource r : sources) { final URLProvider up = r.as(URLProvider.class); if (up == null) { throw new BuildException( "Only URLProvider resources are supported", getLocation()); } } if (destination == null) { throw new BuildException("dest attribute is required", getLocation()); } if (destination.exists() && sources.size() > 1 && !destination.isDirectory()) { throw new BuildException( "The specified destination is not a directory", getLocation()); } if (destination.exists() && !destination.canWrite()) { throw new BuildException("Can't write to " + destination.getAbsolutePath(), getLocation()); } if (sources.size() > 1 && !destination.exists()) { destination.mkdirs(); } }
Set an URL to get.
Params:
  • u – URL for the file.
/** * Set an URL to get. * * @param u URL for the file. */
public void setSrc(final URL u) { add(new URLResource(u)); }
Adds URLs to get.
Params:
  • rc – ResourceCollection
Since:Ant 1.8.0
/** * Adds URLs to get. * @param rc ResourceCollection * @since Ant 1.8.0 */
public void add(final ResourceCollection rc) { sources.add(rc); }
Where to copy the source file.
Params:
  • dest – Path to file.
/** * Where to copy the source file. * * @param dest Path to file. */
public void setDest(final File dest) { this.destination = dest; }
If true, show verbose progress information.
Params:
  • v – if "true" then be verbose
/** * If true, show verbose progress information. * * @param v if "true" then be verbose */
public void setVerbose(final boolean v) { verbose = v; }
If true, set default log level to Project.MSG_ERR.
Params:
  • v – if "true" then be quiet
Since:Ant 1.9.4
/** * If true, set default log level to Project.MSG_ERR. * * @param v if "true" then be quiet * @since Ant 1.9.4 */
public void setQuiet(final boolean v) { this.quiet = v; }
If true, log errors but do not treat as fatal.
Params:
  • v – if "true" then don't report download errors up to ant
/** * If true, log errors but do not treat as fatal. * * @param v if "true" then don't report download errors up to ant */
public void setIgnoreErrors(final boolean v) { ignoreErrors = v; }
If true, conditionally download a file based on the timestamp of the local copy.

In this situation, the if-modified-since header is set so that the file is only fetched if it is newer than the local file (or there is no local file) This flag is only valid on HTTP connections, it is ignored in other cases. When the flag is set, the local copy of the downloaded file will also have its timestamp set to the remote file time.

Note that remote files of date 1/1/1970 (GMT) are treated as 'no timestamp', and web servers often serve files with a timestamp in the future by replacing their timestamp with that of the current time. Also, inter-computer clock differences can cause no end of grief.

Params:
  • v – "true" to enable file time fetching
/** * If true, conditionally download a file based on the timestamp * of the local copy. * * <p>In this situation, the if-modified-since header is set so * that the file is only fetched if it is newer than the local * file (or there is no local file) This flag is only valid on * HTTP connections, it is ignored in other cases. When the flag * is set, the local copy of the downloaded file will also have * its timestamp set to the remote file time.</p> * * <p>Note that remote files of date 1/1/1970 (GMT) are treated as * 'no timestamp', and web servers often serve files with a * timestamp in the future by replacing their timestamp with that * of the current time. Also, inter-computer clock differences can * cause no end of grief.</p> * @param v "true" to enable file time fetching */
public void setUseTimestamp(final boolean v) { useTimestamp = v; }
Username for basic auth.
Params:
  • u – username for authentication
/** * Username for basic auth. * * @param u username for authentication */
public void setUsername(final String u) { this.uname = u; }
password for the basic authentication.
Params:
  • p – password for authentication
/** * password for the basic authentication. * * @param p password for authentication */
public void setPassword(final String p) { this.pword = p; }
The time in seconds the download is allowed to take before being terminated.
Params:
  • maxTime – long
Since:Ant 1.8.0
/** * The time in seconds the download is allowed to take before * being terminated. * * @param maxTime long * @since Ant 1.8.0 */
public void setMaxTime(final long maxTime) { this.maxTime = maxTime; }
The number of attempts to make for opening the URI, defaults to 3.

The name of the method is misleading as a value of 1 means "don't retry on error" and a value of 0 meant don't even try to reach the URI at all.

Params:
  • r – number of attempts to make
Since:Ant 1.8.0
/** * The number of attempts to make for opening the URI, defaults to 3. * * <p>The name of the method is misleading as a value of 1 means * "don't retry on error" and a value of 0 meant don't even try to * reach the URI at all.</p> * * @param r number of attempts to make * @since Ant 1.8.0 */
public void setRetries(final int r) { if (r <= 0) { log("Setting retries to " + r + " will make the task not even try to reach the URI at all", Project.MSG_WARN); } this.numberRetries = r; }
Skip files that already exist locally.
Params:
  • s – "true" to skip existing destination files
Since:Ant 1.8.0
/** * Skip files that already exist locally. * * @param s "true" to skip existing destination files * @since Ant 1.8.0 */
public void setSkipExisting(final boolean s) { this.skipExisting = s; }
HTTP connections only - set the user-agent to be used when communicating with remote server. if null, then the value is considered unset and the behaviour falls back to the default of the http API.
Params:
  • userAgent – String
Since:Ant 1.9.3
/** * HTTP connections only - set the user-agent to be used * when communicating with remote server. if null, then * the value is considered unset and the behaviour falls * back to the default of the http API. * * @param userAgent String * @since Ant 1.9.3 */
public void setUserAgent(final String userAgent) { this.userAgent = userAgent; }
HTTP connections only - control caching on the HttpUrlConnection: httpConnection.setUseCaches(); if false, do not allow caching on the HttpUrlConnection.

Defaults to true (allow caching, which is also the HttpUrlConnection default value.

Params:
  • httpUseCache – boolean
Since:Ant 1.8.0
/** * HTTP connections only - control caching on the * HttpUrlConnection: httpConnection.setUseCaches(); if false, do * not allow caching on the HttpUrlConnection. * * <p>Defaults to true (allow caching, which is also the * HttpUrlConnection default value.</p> * * @param httpUseCache boolean * @since Ant 1.8.0 */
public void setHttpUseCaches(final boolean httpUseCache) { this.httpUseCaches = httpUseCache; }
Whether to transparently try to reduce bandwidth by telling the server ant would support gzip encoding.

Setting this to true also means Ant will uncompress .tar.gz and similar files automatically.

Params:
  • b – boolean
Since:Ant 1.9.5
/** * Whether to transparently try to reduce bandwidth by telling the * server ant would support gzip encoding. * * <p>Setting this to true also means Ant will uncompress * <code>.tar.gz</code> and similar files automatically.</p> * * @param b boolean * @since Ant 1.9.5 */
public void setTryGzipEncoding(boolean b) { tryGzipEncoding = b; }
Add a nested header
Params:
  • header – to be added
/** * Add a nested header * @param header to be added * */
public void addConfiguredHeader(Header header) { if (header != null) { String key = StringUtils.trimToNull(header.getName()); String value = StringUtils.trimToNull(header.getValue()); if (key != null && value != null) { this.headers.put(key, value); } } }
Define the mapper to map source to destination files.
Throws:
Returns:a mapper to be configured.
Since:Ant 1.8.0
/** * Define the mapper to map source to destination files. * @return a mapper to be configured. * @exception BuildException if more than one mapper is defined. * @since Ant 1.8.0 */
public Mapper createMapper() throws BuildException { if (mapperElement != null) { throw new BuildException("Cannot define more than one mapper", getLocation()); } mapperElement = new Mapper(getProject()); return mapperElement; }
Add a nested filenamemapper.
Params:
  • fileNameMapper – the mapper to add.
Since:Ant 1.8.0
/** * Add a nested filenamemapper. * @param fileNameMapper the mapper to add. * @since Ant 1.8.0 */
public void add(final FileNameMapper fileNameMapper) { createMapper().add(fileNameMapper); }
Provide this for Backward Compatibility.
/** * Provide this for Backward Compatibility. */
protected static class Base64Converter extends org.apache.tools.ant.util.Base64Converter { }
Interface implemented for reporting progress of downloading.
/** * Interface implemented for reporting * progress of downloading. */
public interface DownloadProgress {
begin a download
/** * begin a download */
void beginDownload();
tick handler
/** * tick handler * */
void onTick();
end a download
/** * end a download */
void endDownload(); }
do nothing with progress info
/** * do nothing with progress info */
public static class NullProgress implements DownloadProgress {
begin a download
/** * begin a download */
@Override public void beginDownload() { }
tick handler
/** * tick handler * */
@Override public void onTick() { }
end a download
/** * end a download */
@Override public void endDownload() { } }
verbose progress system prints to some output stream
/** * verbose progress system prints to some output stream */
public static class VerboseProgress implements DownloadProgress { private int dots = 0; // CheckStyle:VisibilityModifier OFF - bc PrintStream out; // CheckStyle:VisibilityModifier ON
Construct a verbose progress reporter.
Params:
  • out – the output stream.
/** * Construct a verbose progress reporter. * @param out the output stream. */
public VerboseProgress(final PrintStream out) { this.out = out; }
begin a download
/** * begin a download */
@Override public void beginDownload() { dots = 0; }
tick handler
/** * tick handler * */
@Override public void onTick() { out.print("."); if (dots++ > DOTS_PER_LINE) { out.flush(); dots = 0; } }
end a download
/** * end a download */
@Override public void endDownload() { out.println(); out.flush(); } } private class GetThread extends Thread { private final URL source; private final File dest; private final boolean hasTimestamp; private final long timestamp; private final DownloadProgress progress; private final int logLevel; private boolean success = false; private IOException ioexception = null; private BuildException exception = null; private InputStream is = null; private OutputStream os = null; private URLConnection connection; private int redirections = 0; private String userAgent = null; GetThread(final URL source, final File dest, final boolean h, final long t, final DownloadProgress p, final int l, final String userAgent) { this.source = source; this.dest = dest; hasTimestamp = h; timestamp = t; progress = p; logLevel = l; this.userAgent = userAgent; } @Override public void run() { try { success = get(); } catch (final IOException ioex) { ioexception = ioex; } catch (final BuildException bex) { exception = bex; } } private boolean get() throws IOException, BuildException { connection = openConnection(source); if (connection == null) { return false; } final boolean downloadSucceeded = downloadFile(); //if (and only if) the use file time option is set, then //the saved file now has its timestamp set to that of the //downloaded file if (downloadSucceeded && useTimestamp) { updateTimeStamp(); } return downloadSucceeded; } private boolean redirectionAllowed(final URL aSource, final URL aDest) { if (!(aSource.getProtocol().equals(aDest.getProtocol()) || (HTTP .equals(aSource.getProtocol()) && HTTPS.equals(aDest .getProtocol())))) { final String message = "Redirection detected from " + aSource.getProtocol() + " to " + aDest.getProtocol() + ". Protocol switch unsafe, not allowed."; if (ignoreErrors) { log(message, logLevel); return false; } else { throw new BuildException(message); } } redirections++; if (redirections > REDIRECT_LIMIT) { final String message = "More than " + REDIRECT_LIMIT + " times redirected, giving up"; if (ignoreErrors) { log(message, logLevel); return false; } else { throw new BuildException(message); } } return true; } private URLConnection openConnection(final URL aSource) throws IOException { // set up the URL connection final URLConnection connection = aSource.openConnection(); // modify the headers // NB: things like user authentication could go in here too. if (hasTimestamp) { connection.setIfModifiedSince(timestamp); } // Set the user agent connection.addRequestProperty("User-Agent", this.userAgent); // prepare Java 1.1 style credentials if (uname != null || pword != null) { final String up = uname + ":" + pword; String encoding; // we do not use the sun impl for portability, // and always use our own implementation for consistent // testing final Base64Converter encoder = new Base64Converter(); encoding = encoder.encode(up.getBytes()); connection.setRequestProperty("Authorization", "Basic " + encoding); } if (tryGzipEncoding) { connection.setRequestProperty("Accept-Encoding", GZIP_CONTENT_ENCODING); } for (final Map.Entry<String, String> header : headers.entrySet()) { //we do not log the header value as it may contain sensitive data like passwords log(String.format("Adding header '%s' ", header.getKey())); connection.setRequestProperty(header.getKey(), header.getValue()); } if (connection instanceof HttpURLConnection) { ((HttpURLConnection) connection).setInstanceFollowRedirects(false); connection.setUseCaches(httpUseCaches); } // connect to the remote site (may take some time) try { connection.connect(); } catch (final NullPointerException e) { //bad URLs can trigger NPEs in some JVMs throw new BuildException("Failed to parse " + source.toString(), e); } // First check on a 301 / 302 (moved) response (HTTP only) if (connection instanceof HttpURLConnection) { final HttpURLConnection httpConnection = (HttpURLConnection) connection; final int responseCode = httpConnection.getResponseCode(); if (isMoved(responseCode)) { final String newLocation = httpConnection.getHeaderField("Location"); final String message = aSource + (responseCode == HttpURLConnection.HTTP_MOVED_PERM ? " permanently" : "") + " moved to " + newLocation; log(message, logLevel); final URL newURL = new URL(aSource, newLocation); if (!redirectionAllowed(aSource, newURL)) { return null; } return openConnection(newURL); } // next test for a 304 result (HTTP only) final long lastModified = httpConnection.getLastModified(); if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED || (lastModified != 0 && hasTimestamp && timestamp >= lastModified)) { // not modified so no file download. just return // instead and trace out something so the user // doesn't think that the download happened when it // didn't log("Not modified - so not downloaded", logLevel); return null; } // test for 401 result (HTTP only) if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { final String message = "HTTP Authorization failure"; if (ignoreErrors) { log(message, logLevel); return null; } throw new BuildException(message); } } //REVISIT: at this point even non HTTP connections may //support the if-modified-since behaviour -we just check //the date of the content and skip the write if it is not //newer. Some protocols (FTP) don't include dates, of //course. return connection; } private boolean isMoved(final int responseCode) { return responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_SEE_OTHER || responseCode == HTTP_MOVED_TEMP; } private boolean downloadFile() throws IOException { for (int i = 0; i < numberRetries; i++) { // this three attempt trick is to get round quirks in different // Java implementations. Some of them take a few goes to bind // properly; we ignore the first couple of such failures. try { is = connection.getInputStream(); break; } catch (final IOException ex) { log("Error opening connection " + ex, logLevel); } } if (is == null) { log("Can't get " + source + " to " + dest, logLevel); if (ignoreErrors) { return false; } throw new BuildException("Can't get " + source + " to " + dest, getLocation()); } if (tryGzipEncoding && GZIP_CONTENT_ENCODING.equals(connection.getContentEncoding())) { is = new GZIPInputStream(is); } os = Files.newOutputStream(dest.toPath()); progress.beginDownload(); boolean finished = false; try { final byte[] buffer = new byte[BIG_BUFFER_SIZE]; int length; while (!isInterrupted() && (length = is.read(buffer)) >= 0) { os.write(buffer, 0, length); progress.onTick(); } finished = !isInterrupted(); } finally { FileUtils.close(os); FileUtils.close(is); // we have started to (over)write dest, but failed. // Try to delete the garbage we'd otherwise leave // behind. if (!finished) { dest.delete(); } } progress.endDownload(); return true; } private void updateTimeStamp() { final long remoteTimestamp = connection.getLastModified(); if (verbose) { final Date t = new Date(remoteTimestamp); log("last modified = " + t.toString() + ((remoteTimestamp == 0) ? " - using current time instead" : ""), logLevel); } if (remoteTimestamp != 0) { FILE_UTILS.setFileLastModified(dest, remoteTimestamp); } }
Has the download completed successfully?

Re-throws any exception caught during execution.

/** * Has the download completed successfully? * * <p>Re-throws any exception caught during execution.</p> */
boolean wasSuccessful() throws IOException, BuildException { if (ioexception != null) { throw ioexception; } if (exception != null) { throw exception; } return success; }
Closes streams, interrupts the download, may delete the output file.
/** * Closes streams, interrupts the download, may delete the * output file. */
void closeStreams() { interrupt(); FileUtils.close(os); FileUtils.close(is); if (!success && dest.exists()) { dest.delete(); } } } }