git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@677187 13f79535-47bb-0310-9956-ffa450edef68master
@@ -236,6 +236,7 @@ Roman Ivashin | |||||
Ronen Mashal | Ronen Mashal | ||||
Russell Gold | Russell Gold | ||||
Sam Ruby | Sam Ruby | ||||
Sandra Metz | |||||
Scott Carlson | Scott Carlson | ||||
Scott Ellsworth | Scott Ellsworth | ||||
Scott M. Stirling | Scott M. Stirling | ||||
@@ -168,6 +168,10 @@ Other changes: | |||||
* <sshexec> now supports input in a way similar to <exec> | * <sshexec> now supports input in a way similar to <exec> | ||||
Bugzilla report 39197. | Bugzilla report 39197. | ||||
* <scp> can now preserve the file modification time when downloading | |||||
files. | |||||
Bugzilla Issue 33939. | |||||
Changes from Ant 1.7.0 TO Ant 1.7.1 | Changes from Ant 1.7.0 TO Ant 1.7.1 | ||||
============================================= | ============================================= | ||||
@@ -955,6 +955,10 @@ | |||||
<first>Sam</first> | <first>Sam</first> | ||||
<last>Ruby</last> | <last>Ruby</last> | ||||
</name> | </name> | ||||
<name> | |||||
<first>Sandra</first> | |||||
<last>Metz</last> | |||||
</name> | |||||
<name> | <name> | ||||
<first>Scott</first> | <first>Scott</first> | ||||
<last>Carlson</last> | <last>Carlson</last> | ||||
@@ -174,6 +174,15 @@ for more information. This task has been tested with jsch-0.1.2 and later.</p> | |||||
server that doesn't support scp1. <em>since Ant 1.7</em></td> | server that doesn't support scp1. <em>since Ant 1.7</em></td> | ||||
<td valign="top" align="center">No; defaults to false.</td> | <td valign="top" align="center">No; defaults to false.</td> | ||||
</tr> | </tr> | ||||
<tr> | |||||
<td valign="top">preserveLastModified</td> | |||||
<td valign="top">Determines whether the last modification | |||||
timestamp of downloaded files is preserved. It only works when | |||||
transferring from a remote to a local system and probably doesn't | |||||
work with a server that doesn't support SSH2. <em>since Ant | |||||
1.8.0</em></td> | |||||
<td valign="top" align="center">No; defaults to false.</td> | |||||
</tr> | |||||
</table> | </table> | ||||
<h3>Parameters specified as nested elements</h3> | <h3>Parameters specified as nested elements</h3> | ||||
@@ -49,6 +49,7 @@ public class Scp extends SSHBase { | |||||
private String fromUri; | private String fromUri; | ||||
private String toUri; | private String toUri; | ||||
private boolean preserveLastModified = false; | |||||
private List fileSets = null; | private List fileSets = null; | ||||
private boolean isFromRemote, isToRemote; | private boolean isFromRemote, isToRemote; | ||||
private boolean isSftp = false; | private boolean isSftp = false; | ||||
@@ -116,6 +117,15 @@ public class Scp extends SSHBase { | |||||
this.isToRemote = false; | this.isToRemote = false; | ||||
} | } | ||||
/** | |||||
* Sets flag to determine if file timestamp from | |||||
* remote system is to be preserved during copy. | |||||
* @since Ant 1.8.0 | |||||
*/ | |||||
public void setPreservelastmodified(boolean yesOrNo) { | |||||
this.preserveLastModified = yesOrNo; | |||||
} | |||||
/** | /** | ||||
* Similiar to {@link #setTodir setTodir} but explicitly states | * Similiar to {@link #setTodir setTodir} but explicitly states | ||||
* that the directory is a remote. | * that the directory is a remote. | ||||
@@ -231,12 +241,14 @@ public class Scp extends SSHBase { | |||||
message = | message = | ||||
new ScpFromMessage(getVerbose(), session, file, | new ScpFromMessage(getVerbose(), session, file, | ||||
getProject().resolveFile(toPath), | getProject().resolveFile(toPath), | ||||
fromSshUri.endsWith("*")); | |||||
fromSshUri.endsWith("*"), | |||||
preserveLastModified); | |||||
} else { | } else { | ||||
message = | message = | ||||
new ScpFromMessageBySftp(getVerbose(), session, file, | new ScpFromMessageBySftp(getVerbose(), session, file, | ||||
getProject().resolveFile(toPath), | getProject().resolveFile(toPath), | ||||
fromSshUri.endsWith("*")); | |||||
fromSshUri.endsWith("*"), | |||||
preserveLastModified); | |||||
} | } | ||||
log("Receiving file: " + file); | log("Receiving file: " + file); | ||||
message.setLogListener(this); | message.setLogListener(this); | ||||
@@ -27,7 +27,11 @@ import java.io.FileOutputStream; | |||||
import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||
import com.jcraft.jsch.JSchException; | import com.jcraft.jsch.JSchException; | ||||
import com.jcraft.jsch.Session; | import com.jcraft.jsch.Session; | ||||
import com.jcraft.jsch.SftpATTRS; | |||||
import com.jcraft.jsch.SftpException; | |||||
import com.jcraft.jsch.Channel; | import com.jcraft.jsch.Channel; | ||||
import com.jcraft.jsch.ChannelSftp; | |||||
import org.apache.tools.ant.util.FileUtils; | |||||
/** | /** | ||||
* A helper object representing an scp download. | * A helper object representing an scp download. | ||||
@@ -41,6 +45,7 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
private String remoteFile; | private String remoteFile; | ||||
private File localFile; | private File localFile; | ||||
private boolean isRecursive = false; | private boolean isRecursive = false; | ||||
private boolean preserveLastModified = false; | |||||
/** | /** | ||||
* Constructor for ScpFromMessage | * Constructor for ScpFromMessage | ||||
@@ -74,10 +79,7 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
String aRemoteFile, | String aRemoteFile, | ||||
File aLocalFile, | File aLocalFile, | ||||
boolean recursive) { | boolean recursive) { | ||||
super(verbose, session); | |||||
this.remoteFile = aRemoteFile; | |||||
this.localFile = aLocalFile; | |||||
this.isRecursive = recursive; | |||||
this(false, session, aRemoteFile, aLocalFile, recursive, false); | |||||
} | } | ||||
/** | /** | ||||
@@ -94,6 +96,30 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
this(false, session, aRemoteFile, aLocalFile, recursive); | this(false, session, aRemoteFile, aLocalFile, recursive); | ||||
} | } | ||||
/** | |||||
* Constructor for ScpFromMessage. | |||||
* @param verbose if true log extra information | |||||
* @param session the Scp session to use | |||||
* @param aRemoteFile the remote file name | |||||
* @param aLocalFile the local file | |||||
* @param recursive if true use recursion (-r option to scp) | |||||
* @param preservceLastModified whether to preserve file | |||||
* modification times | |||||
* @since Ant 1.8.0 | |||||
*/ | |||||
public ScpFromMessage(boolean verbose, | |||||
Session session, | |||||
String aRemoteFile, | |||||
File aLocalFile, | |||||
boolean recursive, | |||||
boolean preserveLastModified) { | |||||
super(verbose, session); | |||||
this.remoteFile = aRemoteFile; | |||||
this.localFile = aLocalFile; | |||||
this.isRecursive = recursive; | |||||
this.preserveLastModified = preserveLastModified; | |||||
} | |||||
/** | /** | ||||
* Carry out the transfer. | * Carry out the transfer. | ||||
* @throws IOException on i/o errors | * @throws IOException on i/o errors | ||||
@@ -123,9 +149,14 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
log("done\n"); | log("done\n"); | ||||
} | } | ||||
protected boolean getPreserveLastModified() { | |||||
return preserveLastModified; | |||||
} | |||||
private void startRemoteCpProtocol(InputStream in, | private void startRemoteCpProtocol(InputStream in, | ||||
OutputStream out, | OutputStream out, | ||||
File localFile) throws IOException { | |||||
File localFile) | |||||
throws IOException, JSchException { | |||||
File startFile = localFile; | File startFile = localFile; | ||||
while (true) { | while (true) { | ||||
// C0644 filesize filename - header for a regular file | // C0644 filesize filename - header for a regular file | ||||
@@ -147,7 +178,7 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
parseAndFetchFile(serverResponse, startFile, out, in); | parseAndFetchFile(serverResponse, startFile, out, in); | ||||
} else if (serverResponse.charAt(0) == 'D') { | } else if (serverResponse.charAt(0) == 'D') { | ||||
startFile = parseAndCreateDirectory(serverResponse, | startFile = parseAndCreateDirectory(serverResponse, | ||||
startFile); | |||||
startFile); | |||||
sendAck(out); | sendAck(out); | ||||
} else if (serverResponse.charAt(0) == 'E') { | } else if (serverResponse.charAt(0) == 'E') { | ||||
startFile = startFile.getParentFile(); | startFile = startFile.getParentFile(); | ||||
@@ -178,7 +209,8 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
private void parseAndFetchFile(String serverResponse, | private void parseAndFetchFile(String serverResponse, | ||||
File localFile, | File localFile, | ||||
OutputStream out, | OutputStream out, | ||||
InputStream in) throws IOException { | |||||
InputStream in) | |||||
throws IOException, JSchException { | |||||
int start = 0; | int start = 0; | ||||
int end = serverResponse.indexOf(" ", start + 1); | int end = serverResponse.indexOf(" ", start + 1); | ||||
start = end + 1; | start = end + 1; | ||||
@@ -197,7 +229,8 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
private void fetchFile(File localFile, | private void fetchFile(File localFile, | ||||
long filesize, | long filesize, | ||||
OutputStream out, | OutputStream out, | ||||
InputStream in) throws IOException { | |||||
InputStream in) | |||||
throws IOException, JSchException { | |||||
byte[] buf = new byte[BUFFER_SIZE]; | byte[] buf = new byte[BUFFER_SIZE]; | ||||
sendAck(out); | sendAck(out); | ||||
@@ -241,6 +274,37 @@ public class ScpFromMessage extends AbstractSshMessage { | |||||
fos.flush(); | fos.flush(); | ||||
fos.close(); | fos.close(); | ||||
} | } | ||||
if (getPreserveLastModified()) { | |||||
setLastModified(localFile); | |||||
} | |||||
} | } | ||||
private void setLastModified(File localFile) throws JSchException { | |||||
SftpATTRS fileAttributes = null; | |||||
String remotePath = null; | |||||
ChannelSftp channel = openSftpChannel(); | |||||
channel.connect(); | |||||
try { | |||||
fileAttributes = channel.lstat(remoteDir(remoteFile) | |||||
+ localFile.getName()); | |||||
} catch (SftpException e) { | |||||
throw new JSchException("failed to stat remote file", e); | |||||
} | |||||
FileUtils.getFileUtils().setFileLastModified(localFile, | |||||
((long) fileAttributes | |||||
.getMTime()) | |||||
* 1000); | |||||
} | |||||
/** | |||||
* returns the directory part of the remote file, if any. | |||||
*/ | |||||
private static String remoteDir(String remoteFile) { | |||||
int index = remoteFile.lastIndexOf("/"); | |||||
if (index < 0) { | |||||
index = remoteFile.lastIndexOf("\\"); | |||||
} | |||||
return index > -1 ? remoteFile.substring(0, index + 1) : ""; | |||||
} | |||||
} | } |
@@ -28,6 +28,8 @@ import com.jcraft.jsch.SftpException; | |||||
import com.jcraft.jsch.SftpATTRS; | import com.jcraft.jsch.SftpATTRS; | ||||
import com.jcraft.jsch.SftpProgressMonitor; | import com.jcraft.jsch.SftpProgressMonitor; | ||||
import org.apache.tools.ant.util.FileUtils; | |||||
/** | /** | ||||
* A helper object representing an scp download. | * A helper object representing an scp download. | ||||
*/ | */ | ||||
@@ -54,11 +56,7 @@ public class ScpFromMessageBySftp extends ScpFromMessage { | |||||
String aRemoteFile, | String aRemoteFile, | ||||
File aLocalFile, | File aLocalFile, | ||||
boolean recursive) { | boolean recursive) { | ||||
super(verbose, session); | |||||
this.verbose = verbose; | |||||
this.remoteFile = aRemoteFile; | |||||
this.localFile = aLocalFile; | |||||
this.isRecursive = recursive; | |||||
this(verbose, session, aRemoteFile, aLocalFile, recursive, false); | |||||
} | } | ||||
/** | /** | ||||
@@ -75,6 +73,31 @@ public class ScpFromMessageBySftp extends ScpFromMessage { | |||||
this(false, session, aRemoteFile, aLocalFile, recursive); | this(false, session, aRemoteFile, aLocalFile, recursive); | ||||
} | } | ||||
/** | |||||
* Constructor for ScpFromMessageBySftp. | |||||
* @param verbose if true log extra information | |||||
* @param session the Scp session to use | |||||
* @param aRemoteFile the remote file name | |||||
* @param aLocalFile the local file | |||||
* @param recursive if true use recursion | |||||
* @param preservceLastModified whether to preserve file | |||||
* modification times | |||||
* @since Ant 1.8.0 | |||||
*/ | |||||
public ScpFromMessageBySftp(boolean verbose, | |||||
Session session, | |||||
String aRemoteFile, | |||||
File aLocalFile, | |||||
boolean recursive, | |||||
boolean preserveLastModified) { | |||||
super(verbose, session, aRemoteFile, aLocalFile, recursive, | |||||
preserveLastModified); | |||||
this.verbose = verbose; | |||||
this.remoteFile = aRemoteFile; | |||||
this.localFile = aLocalFile; | |||||
this.isRecursive = recursive; | |||||
} | |||||
/** | /** | ||||
* Carry out the transfer. | * Carry out the transfer. | ||||
* @throws IOException on i/o errors | * @throws IOException on i/o errors | ||||
@@ -171,5 +194,11 @@ public class ScpFromMessageBySftp extends ScpFromMessage { | |||||
long endTime = System.currentTimeMillis(); | long endTime = System.currentTimeMillis(); | ||||
logStats(startTime, endTime, (int) totalLength); | logStats(startTime, endTime, (int) totalLength); | ||||
} | } | ||||
if (getPreserveLastModified()) { | |||||
FileUtils.getFileUtils().setFileLastModified(localFile, | |||||
((long) le.getAttrs() | |||||
.getMTime()) | |||||
* 1000); | |||||
} | |||||
} | } | ||||
} | } |