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); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||