@@ -21,6 +21,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
@@ -51,12 +52,47 @@ public class Get extends Task {
private String pword = null;
/**
* Does the work.
*
* @exception BuildException Thrown in unrecoverable error.
*/
public void execute() throws BuildException {
//set up logging
int logLevel = Project.MSG_INFO;
DownloadProgress progress=null;
if (verbose) {
progress = new VerboseProgress(System.out);
}
//execute the get
try {
doGet(logLevel, progress);
} catch (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.
* @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.
*/
public boolean doGet(int logLevel, DownloadProgress progress)
throws IOException {
if (source == null) {
throw new BuildException("src attribute is required", getLocation());
}
@@ -67,172 +103,155 @@ public class Get extends Task {
if (dest.exists() && dest.isDirectory()) {
throw new BuildException("The specified destination is a directory",
getLocation());
getLocation());
}
if (dest.exists() && !dest.canWrite()) {
throw new BuildException("Can't write to " + dest.getAbsolutePath(),
getLocation());
getLocation());
}
//dont do any progress, unless asked
if(progress==null) {
progress = new NullProgress();
}
log("Getting: " + source, logLevel);
try {
log("Getting: " + source);
//set the timestamp to the file date.
long timestamp = 0;
boolean hasTimestamp = false;
if (useTimestamp && dest.exists()) {
timestamp = dest.lastModified();
if (verbose) {
Date t = new Date(timestamp);
log("local file date : " + t.toString());
}
//set the timestamp to the file date.
long timestamp = 0;
hasTimestamp = true;
boolean hasTimestamp = false;
if (useTimestamp && dest.exists()) {
timestamp = dest.lastModified();
if (verbose) {
Date t = new Date(timestamp);
log("local file date : " + t.toString(), logLevel);
}
hasTimestamp = true;
}
//set up the URL connection
URLConnection connection = source.openConnection();
//modify the headers
//NB: things like user authentication could go in here too.
if (useTimestamp && hasTimestamp) {
connection.setIfModifiedSince(timestamp);
}
// prepare Java 1.1 style credentials
if (uname != null || pword != null) {
String up = uname + ":" + pword;
String encoding;
// check to see if sun's Base64 encoder is available.
try {
Object encoder =
Class.forName("sun.misc.BASE64Encoder").newInstance();
encoding = (String)
encoder.getClass().getMethod("encode", new Class[] {byte[].class})
.invoke(encoder, new Object[] {up.getBytes()});
} catch (Exception ex) { // sun's base64 encoder isn't available
Base64Converter encoder = new Base64Converter();
encoding = encoder.encode(up.getBytes());
}
connection.setRequestProperty ("Authorization",
"Basic " + encoding);
}
//set up the URL connection
URLConnection connection = source.openConnection();
//modify the headers
//NB: things like user authentication could go in here too.
if (useTimestamp && hasTimestamp) {
connection.setIfModifiedSince(timestamp);
}
// prepare Java 1.1 style credentials
if (uname != null || pword != null) {
String up = uname + ":" + pword;
String encoding;
//we do not use the sun impl for portability,
//and always use our own implementation for consistent
//testing
Base64Converter encoder = new Base64Converter();
encoding = encoder.encode(up.getBytes());
connection.setRequestProperty ("Authorization",
"Basic " + encoding);
}
//connect to the remote site (may take some time)
connection.connect();
//next test for a 304 result (HTTP only)
if (connection instanceof HttpURLConnection) {
HttpURLConnection httpConnection
//connect to the remote site (may take some time)
connection.connect();
//next test for a 304 result (HTTP only)
if (connection instanceof HttpURLConnection) {
HttpURLConnection httpConnection
= (HttpURLConnection) connection;
if (httpConnection.getResponseCode()
if (httpConnection.getResponseCode()
== HttpURLConnection.HTTP_NOT_MODIFIED) {
//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");
return;
}
// test for 401 result (HTTP only)
if (httpConnection.getResponseCode()
== HttpURLConnection.HTTP_UNAUTHORIZED) {
String message = "HTTP Authorization failure";
if (ignoreErrors) {
log(message, Project.MSG_WARN);
return;
} else {
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.
InputStream is = null;
for (int i = 0; i < 3; i++) {
try {
is = connection.getInputStream();
break;
} catch (IOException ex) {
log("Error opening connection " + ex);
}
//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 false;
}
if (is == null) {
log("Can't get " + source + " to " + dest);
// test for 401 result (HTTP only)
if (httpConnection.getResponseCode()
== HttpURLConnection.HTTP_UNAUTHORIZED) {
String message = "HTTP Authorization failure";
if (ignoreErrors) {
return;
log(message, logLevel);
return false;
} else {
throw new BuildException(message);
}
throw new BuildException("Can't get " + source + " to " + dest,
getLocation());
}
FileOutputStream fos = new FileOutputStream(dest);
boolean finished = false;
}
//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.
InputStream is = null;
for (int i = 0; i < 3; i++) {
//this three attempt trick is to get round quirks in different
//Java implementations. Some of them take a few goes to bind
//property; we ignore the first couple of such failures.
try {
byte[] buffer = new byte[100 * 1024];
int length;
int dots = 0;
while ((length = is.read(buffer)) >= 0) {
fos.write(buffer, 0, length);
if (verbose) {
System.out.print(".");
if (dots++ > 50) {
System.out.flush();
dots = 0;
}
}
}
if (verbose) {
System.out.println();
}
finished = true;
} finally {
if (fos != null) {
fos.close();
}
is.close();
// we have started to (over)write dest, but failed.
// Try to delete the garbage we'd otherwise leave
// behind.
if (!finished) {
dest.delete();
}
is = connection.getInputStream();
break;
} catch (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 (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 (useTimestamp) {
long remoteTimestamp = connection.getLastModified();
if (verbose) {
Date t = new Date(remoteTimestamp);
log("last modified = " + t.toString()
FileOutputStream fos = new FileOutputStream(dest);
progress.beginDownload();
boolean finished = false;
try {
byte[] buffer = new byte[100 * 1024];
int length;
while ((length = is.read(buffer)) >= 0) {
fos.write(buffer, 0, length);
progress.onTick();
}
finished = true;
} finally {
FileUtils.close(fos);
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();
//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 (useTimestamp) {
long remoteTimestamp = connection.getLastModified();
if (verbose) {
Date t = new Date(remoteTimestamp);
log("last modified = " + t.toString()
+ ((remoteTimestamp == 0)
? " - using current time instead"
: ""));
}
if (remoteTimestamp != 0) {
FileUtils.newFileUtils()
.setFileLastModified(dest, remoteTimestamp);
}
? " - using current time instead"
: ""),logLevel);
}
} catch (IOException ioe) {
log("Error getting " + source + " to " + dest);
if (ignoreErrors) {
return;
if (remoteTimestamp != 0) {
FileUtils.newFileUtils()
.setFileLastModified(dest, remoteTimestamp);
}
throw new BuildException(ioe, getLocation());
}
//successful download
return true;
}
/**
* Set the URL to get.
*
@@ -317,7 +336,7 @@ public class Get extends Task {
*
*********************************************************************/
private static class Base64Converter {
protected static class Base64Converter {
public final char [ ] alphabet = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7
@@ -388,4 +407,89 @@ public class Get extends Task {
return new String(out);
}
}
public interface DownloadProgress {
/**
* begin a download
*/
public void beginDownload();
/**
* tick handler
*
*/
public void onTick();
/**
* end a download
*/
public void endDownload();
}
/**
* do nothing with progress info
*/
public static class NullProgress implements DownloadProgress {
/**
* begin a download
*/
public void beginDownload() {
}
/**
* tick handler
*
*/
public void onTick() {
}
/**
* end a download
*/
public void endDownload() {
}
}
/**
* verbose progress system prints to some output stream
*/
public static class VerboseProgress implements DownloadProgress {
private int dots = 0;
PrintStream out;
public VerboseProgress(PrintStream out) {
this.out = out;
}
/**
* begin a download
*/
public void beginDownload() {
dots=0;
}
/**
* tick handler
*
*/
public void onTick() {
out.print(".");
if (dots++ > 50) {
out.flush();
dots = 0;
}
}
/**
* end a download
*/
public void endDownload() {
out.println();
out.flush();
}
}
}