git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@405300 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -410,6 +410,8 @@ Other changes: | |||
| * Handling of ' ', '#' in CLASSPATH and '#' in -lib (cannot use ' ' | |||
| in -lib on UNIX at the moment). Bugzilla Report 39295. | |||
| * <scp> now optionally supports the sftp protocol. Bugzilla Report 39373. | |||
| Changes from Ant 1.6.4 to Ant 1.6.5 | |||
| =================================== | |||
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * Copyright 2003-2005 The Apache Software Foundation | |||
| * Copyright 2003-2006 The Apache Software Foundation | |||
| * | |||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||
| * you may not use this file except in compliance with the License. | |||
| @@ -21,6 +21,10 @@ import com.jcraft.jsch.Channel; | |||
| import com.jcraft.jsch.ChannelExec; | |||
| import com.jcraft.jsch.JSchException; | |||
| import com.jcraft.jsch.Session; | |||
| import com.jcraft.jsch.ChannelSftp; | |||
| import com.jcraft.jsch.SftpATTRS; | |||
| import com.jcraft.jsch.SftpException; | |||
| import com.jcraft.jsch.SftpProgressMonitor; | |||
| import java.io.IOException; | |||
| import java.io.OutputStream; | |||
| @@ -74,6 +78,17 @@ public abstract class AbstractSshMessage { | |||
| return channel; | |||
| } | |||
| /** | |||
| * Open an ssh sftp channel. | |||
| * @return the channel | |||
| * @throws JSchException on error | |||
| */ | |||
| protected ChannelSftp openSftpChannel() throws JSchException { | |||
| ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); | |||
| return channel; | |||
| } | |||
| /** | |||
| * Send an ack. | |||
| * @param out the output stream to use | |||
| @@ -213,4 +228,39 @@ public abstract class AbstractSshMessage { | |||
| return percent; | |||
| } | |||
| private ProgressMonitor monitor = null; | |||
| protected SftpProgressMonitor getProgressMonitor(){ | |||
| if (monitor == null) { | |||
| monitor = new ProgressMonitor(); | |||
| } | |||
| return monitor; | |||
| } | |||
| private class ProgressMonitor implements SftpProgressMonitor { | |||
| private long initFileSize = 0; | |||
| private long totalLength = 0; | |||
| private int percentTransmitted = 0; | |||
| public void init(int op, String src, String dest, long max) { | |||
| initFileSize = max; | |||
| totalLength = 0; | |||
| percentTransmitted = 0; | |||
| } | |||
| public boolean count(long len) { | |||
| totalLength += len; | |||
| percentTransmitted = trackProgress(initFileSize, | |||
| totalLength, | |||
| percentTransmitted); | |||
| return true; | |||
| } | |||
| public void end() { | |||
| } | |||
| public long getTotalLength() { | |||
| return totalLength; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * Copyright 2003-2005 The Apache Software Foundation | |||
| * Copyright 2003-2006 The Apache Software Foundation | |||
| * | |||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||
| * you may not use this file except in compliance with the License. | |||
| @@ -44,6 +44,7 @@ public class Scp extends SSHBase { | |||
| private String toUri; | |||
| private List fileSets = null; | |||
| private boolean isFromRemote, isToRemote; | |||
| private boolean isSftp = false; | |||
| /** | |||
| * Sets the file to be transferred. This can either be a remote | |||
| @@ -141,6 +142,15 @@ public class Scp extends SSHBase { | |||
| this.isToRemote = true; | |||
| } | |||
| /** | |||
| * Setting this to true to use sftp protocol. | |||
| * | |||
| * @param yesOrNo if true sftp protocol will be used. | |||
| */ | |||
| public void setSftp(boolean yesOrNo) { | |||
| isSftp = yesOrNo; | |||
| } | |||
| /** | |||
| * Adds a FileSet tranfer to remote host. NOTE: Either | |||
| * addFileSet() or setFile() are required. But, not both. | |||
| @@ -213,10 +223,18 @@ public class Scp extends SSHBase { | |||
| Session session = null; | |||
| try { | |||
| session = openSession(); | |||
| ScpFromMessage message = | |||
| new ScpFromMessage(getVerbose(), session, file, | |||
| getProject().resolveFile(toPath), | |||
| fromSshUri.endsWith("*")); | |||
| ScpFromMessage message = null; | |||
| if (!isSftp){ | |||
| message = | |||
| new ScpFromMessage(getVerbose(), session, file, | |||
| getProject().resolveFile(toPath), | |||
| fromSshUri.endsWith("*")); | |||
| } else{ | |||
| message = | |||
| new ScpFromMessageBySftp(getVerbose(), session, file, | |||
| getProject().resolveFile(toPath), | |||
| fromSshUri.endsWith("*")); | |||
| } | |||
| log("Receiving file: " + file); | |||
| message.setLogListener(this); | |||
| message.execute(); | |||
| @@ -243,8 +261,14 @@ public class Scp extends SSHBase { | |||
| } | |||
| if (!list.isEmpty()) { | |||
| session = openSession(); | |||
| ScpToMessage message = new ScpToMessage(getVerbose(), session, | |||
| list, file); | |||
| ScpToMessage message = null; | |||
| if (!isSftp){ | |||
| message = new ScpToMessage(getVerbose(), session, | |||
| list, file); | |||
| } else{ | |||
| message = new ScpToMessageBySftp(getVerbose(), session, | |||
| list, file); | |||
| } | |||
| message.setLogListener(this); | |||
| message.execute(); | |||
| } | |||
| @@ -262,9 +286,17 @@ public class Scp extends SSHBase { | |||
| Session session = null; | |||
| try { | |||
| session = openSession(); | |||
| ScpToMessage message = | |||
| new ScpToMessage(getVerbose(), session, | |||
| getProject().resolveFile(fromPath), file); | |||
| ScpToMessage message = null; | |||
| if (!isSftp){ | |||
| message = | |||
| new ScpToMessage(getVerbose(), session, | |||
| getProject().resolveFile(fromPath), file); | |||
| } else{ | |||
| message = | |||
| new ScpToMessageBySftp(getVerbose(), session, | |||
| getProject().resolveFile(fromPath), | |||
| file); | |||
| } | |||
| message.setLogListener(this); | |||
| message.execute(); | |||
| } finally { | |||
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * Copyright 2003-2005 The Apache Software Foundation | |||
| * Copyright 2003-2006 The Apache Software Foundation | |||
| * | |||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||
| * you may not use this file except in compliance with the License. | |||
| @@ -40,6 +40,24 @@ public class ScpFromMessage extends AbstractSshMessage { | |||
| private File localFile; | |||
| private boolean isRecursive = false; | |||
| /** | |||
| * Constructor for ScpFromMessage | |||
| * @param session the ssh session to use | |||
| */ | |||
| public ScpFromMessage(Session session) { | |||
| super(session); | |||
| } | |||
| /** | |||
| * Constructor for ScpFromMessage | |||
| * @param verbose if true do verbose logging | |||
| * @param session the ssh session to use | |||
| * @since Ant 1.6.2 | |||
| */ | |||
| public ScpFromMessage(boolean verbose, Session session) { | |||
| super(verbose, session); | |||
| } | |||
| /** | |||
| * Constructor for ScpFromMessage. | |||
| * @param verbose if true log extra information | |||
| @@ -0,0 +1,173 @@ | |||
| /* | |||
| * Copyright 2006 The Apache Software Foundation | |||
| * | |||
| * Licensed 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 | |||
| * | |||
| * http://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.optional.ssh; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.io.EOFException; | |||
| import java.io.InputStream; | |||
| import java.io.OutputStream; | |||
| import java.io.FileOutputStream; | |||
| import java.io.ByteArrayOutputStream; | |||
| import com.jcraft.jsch.JSchException; | |||
| import com.jcraft.jsch.Session; | |||
| import com.jcraft.jsch.Channel; | |||
| import com.jcraft.jsch.ChannelSftp; | |||
| import com.jcraft.jsch.SftpException; | |||
| import com.jcraft.jsch.SftpATTRS; | |||
| import com.jcraft.jsch.SftpProgressMonitor; | |||
| /** | |||
| * A helper object representing an scp download. | |||
| */ | |||
| public class ScpFromMessageBySftp extends ScpFromMessage { | |||
| private String remoteFile; | |||
| private File localFile; | |||
| private boolean isRecursive = false; | |||
| private boolean verbose = false; | |||
| /** | |||
| * 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 | |||
| * @since Ant 1.7 | |||
| */ | |||
| public ScpFromMessageBySftp(boolean verbose, | |||
| Session session, | |||
| String aRemoteFile, | |||
| File aLocalFile, | |||
| boolean recursive) { | |||
| super(verbose, session); | |||
| this.verbose = verbose; | |||
| this.remoteFile = aRemoteFile; | |||
| this.localFile = aLocalFile; | |||
| this.isRecursive = recursive; | |||
| } | |||
| /** | |||
| * Constructor for ScpFromMessageBySftp. | |||
| * @param session the Scp session to use | |||
| * @param aRemoteFile the remote file name | |||
| * @param aLocalFile the local file | |||
| * @param recursive if true use recursion | |||
| */ | |||
| public ScpFromMessageBySftp(Session session, | |||
| String aRemoteFile, | |||
| File aLocalFile, | |||
| boolean recursive) { | |||
| this(false, session, aRemoteFile, aLocalFile, recursive); | |||
| } | |||
| /** | |||
| * Carry out the transfer. | |||
| * @throws IOException on i/o errors | |||
| * @throws JSchException on errors detected by scp | |||
| */ | |||
| public void execute() throws IOException, JSchException { | |||
| ChannelSftp channel = openSftpChannel(); | |||
| try { | |||
| channel.connect(); | |||
| try { | |||
| SftpATTRS attrs = channel.stat(remoteFile); | |||
| if (attrs.isDir() && !remoteFile.endsWith("/")) { | |||
| remoteFile=remoteFile+"/"; | |||
| } | |||
| } catch(SftpException ee) { | |||
| } | |||
| getDir(channel, remoteFile, localFile); | |||
| } catch(SftpException e) { | |||
| throw new JSchException(e.toString()); | |||
| } finally { | |||
| if (channel != null) { | |||
| channel.disconnect(); | |||
| } | |||
| } | |||
| log("done\n"); | |||
| } | |||
| private void getDir(ChannelSftp channel, | |||
| String remoteFile, | |||
| File localFile) throws IOException, SftpException { | |||
| String pwd=remoteFile; | |||
| if (remoteFile.lastIndexOf('/')!=-1) { | |||
| if (remoteFile.length()>1) { | |||
| pwd=remoteFile.substring(0, remoteFile.lastIndexOf('/')); | |||
| } | |||
| } | |||
| channel.cd(pwd); | |||
| if (!localFile.exists()) { | |||
| localFile.mkdirs(); | |||
| } | |||
| java.util.Vector files = channel.ls(remoteFile); | |||
| for(int i = 0; i < files.size(); i++){ | |||
| ChannelSftp.LsEntry le = (ChannelSftp.LsEntry) files.elementAt(i); | |||
| String name = le.getFilename(); | |||
| if (le.getAttrs().isDir()) { | |||
| if (name.equals(".") || name.equals("..")) { | |||
| continue; | |||
| } | |||
| getDir(channel, | |||
| channel.pwd() + "/" + name + "/", | |||
| new File(localFile, le.getFilename())); | |||
| } else{ | |||
| getFile(channel, le, localFile); | |||
| } | |||
| } | |||
| channel.cd(".."); | |||
| } | |||
| private void getFile(ChannelSftp channel, | |||
| ChannelSftp.LsEntry le, | |||
| File localFile) throws IOException, SftpException { | |||
| String remoteFile = le.getFilename(); | |||
| if (!localFile.exists()) { | |||
| String path = localFile.getAbsolutePath(); | |||
| int i = 0; | |||
| if ((i = path.lastIndexOf(File.pathSeparator)) != -1) { | |||
| if (path.length()>File.pathSeparator.length()) { | |||
| new File(path.substring(0, i)).mkdirs(); | |||
| } | |||
| } | |||
| } | |||
| if (localFile.isDirectory()) { | |||
| localFile=new File(localFile, remoteFile); | |||
| } | |||
| long startTime = System.currentTimeMillis(); | |||
| long totalLength = le.getAttrs().getSize(); | |||
| SftpProgressMonitor monitor = null; | |||
| boolean trackProgress = getVerbose() && totalLength > 102400; | |||
| if (trackProgress){ | |||
| monitor = getProgressMonitor(); | |||
| } | |||
| try{ | |||
| log("Receiving: " + remoteFile + " : " + le.getAttrs().getSize()); | |||
| channel.get(remoteFile, localFile.getAbsolutePath(), monitor); | |||
| } finally{ | |||
| long endTime = System.currentTimeMillis(); | |||
| logStats(startTime, endTime, (int)totalLength); | |||
| } | |||
| } | |||
| } | |||
| @@ -39,6 +39,24 @@ public class ScpToMessage extends AbstractSshMessage { | |||
| private String remotePath; | |||
| private List directoryList; | |||
| /** | |||
| * Constructor for ScpToMessage | |||
| * @param session the ssh session to use | |||
| */ | |||
| public ScpToMessage(Session session) { | |||
| super(session); | |||
| } | |||
| /** | |||
| * Constructor for ScpToMessage | |||
| * @param verbose if true do verbose logging | |||
| * @param session the ssh session to use | |||
| * @since Ant 1.7 | |||
| */ | |||
| public ScpToMessage(boolean verbose, Session session) { | |||
| super(verbose, session); | |||
| } | |||
| /** | |||
| * Constructor for a local file to remote. | |||
| * @param verbose if true do verbose logging | |||
| @@ -0,0 +1,240 @@ | |||
| /* | |||
| * Copyright 2006 The Apache Software Foundation | |||
| * | |||
| * Licensed 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 | |||
| * | |||
| * http://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.optional.ssh; | |||
| import com.jcraft.jsch.Channel; | |||
| import com.jcraft.jsch.Session; | |||
| import com.jcraft.jsch.ChannelSftp; | |||
| import com.jcraft.jsch.JSchException; | |||
| import com.jcraft.jsch.SftpException; | |||
| import com.jcraft.jsch.SftpProgressMonitor; | |||
| import com.jcraft.jsch.SftpATTRS; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.FileInputStream; | |||
| import java.io.OutputStream; | |||
| import java.util.List; | |||
| import java.util.Iterator; | |||
| public class ScpToMessageBySftp extends ScpToMessage/*AbstractSshMessage*/ { | |||
| private File localFile; | |||
| private String remotePath; | |||
| private List directoryList; | |||
| /** | |||
| * Constructor for a local file to remote. | |||
| * @param verbose if true do verbose logging | |||
| * @param session the scp session to use | |||
| * @param aLocalFile the local file | |||
| * @param aRemotePath the remote path | |||
| * @since Ant 1.7 | |||
| */ | |||
| public ScpToMessageBySftp(boolean verbose, | |||
| Session session, | |||
| File aLocalFile, | |||
| String aRemotePath) { | |||
| this(verbose, session, aRemotePath); | |||
| this.localFile = aLocalFile; | |||
| } | |||
| /** | |||
| * Constructor for a local directories to remote. | |||
| * @param verbose if true do verbose logging | |||
| * @param session the scp session to use | |||
| * @param aDirectoryList a list of directories | |||
| * @param aRemotePath the remote path | |||
| * @since Ant 1.7 | |||
| */ | |||
| public ScpToMessageBySftp(boolean verbose, | |||
| Session session, | |||
| List aDirectoryList, | |||
| String aRemotePath) { | |||
| this(verbose, session, aRemotePath); | |||
| this.directoryList = aDirectoryList; | |||
| } | |||
| /** | |||
| * Constructor for ScpToMessage. | |||
| * @param verbose if true do verbose logging | |||
| * @param session the scp session to use | |||
| * @param aRemotePath the remote path | |||
| * @since Ant 1.6.2 | |||
| */ | |||
| private ScpToMessageBySftp(boolean verbose, | |||
| Session session, | |||
| String aRemotePath) { | |||
| super(verbose, session); | |||
| this.remotePath = aRemotePath; | |||
| } | |||
| /** | |||
| * Constructor for ScpToMessage. | |||
| * @param session the scp session to use | |||
| * @param aLocalFile the local file | |||
| * @param aRemotePath the remote path | |||
| */ | |||
| public ScpToMessageBySftp(Session session, | |||
| File aLocalFile, | |||
| String aRemotePath) { | |||
| this(false, session, aLocalFile, aRemotePath); | |||
| } | |||
| /** | |||
| * Constructor for ScpToMessage. | |||
| * @param session the scp session to use | |||
| * @param aDirectoryList a list of directories | |||
| * @param aRemotePath the remote path | |||
| */ | |||
| public ScpToMessageBySftp(Session session, | |||
| List aDirectoryList, | |||
| String aRemotePath) { | |||
| this(false, session, aDirectoryList, aRemotePath); | |||
| } | |||
| /** | |||
| * Carry out the transfer. | |||
| * @throws IOException on i/o errors | |||
| * @throws JSchException on errors detected by scp | |||
| */ | |||
| public void execute() throws IOException, JSchException { | |||
| if (directoryList != null) { | |||
| doMultipleTransfer(); | |||
| } | |||
| if (localFile != null) { | |||
| doSingleTransfer(); | |||
| } | |||
| log("done.\n"); | |||
| } | |||
| private void doSingleTransfer() throws IOException, JSchException { | |||
| ChannelSftp channel = openSftpChannel(); | |||
| try { | |||
| channel.connect(); | |||
| try{ | |||
| sendFileToRemote(channel, localFile, remotePath); | |||
| } | |||
| catch(SftpException e){ | |||
| throw new JSchException(e.toString()); | |||
| } | |||
| } finally { | |||
| if (channel != null) { | |||
| channel.disconnect(); | |||
| } | |||
| } | |||
| } | |||
| private void doMultipleTransfer() throws IOException, JSchException { | |||
| ChannelSftp channel = openSftpChannel(); | |||
| try { | |||
| channel.connect(); | |||
| try{ | |||
| channel.cd(remotePath); | |||
| for (Iterator i = directoryList.iterator(); i.hasNext();) { | |||
| Directory current = (Directory) i.next(); | |||
| sendDirectory(channel, current); | |||
| } | |||
| } | |||
| catch(SftpException e){ | |||
| throw new JSchException(e.toString()); | |||
| } | |||
| } finally { | |||
| if (channel != null) { | |||
| channel.disconnect(); | |||
| } | |||
| } | |||
| } | |||
| private void sendDirectory(ChannelSftp channel, | |||
| Directory current) | |||
| throws IOException, SftpException { | |||
| for (Iterator fileIt = current.filesIterator(); fileIt.hasNext();) { | |||
| sendFileToRemote(channel, (File) fileIt.next(), null); | |||
| } | |||
| for (Iterator dirIt = current.directoryIterator(); dirIt.hasNext();) { | |||
| Directory dir = (Directory) dirIt.next(); | |||
| sendDirectoryToRemote(channel, dir); | |||
| } | |||
| } | |||
| private void sendDirectoryToRemote(ChannelSftp channel, | |||
| Directory directory) | |||
| throws IOException, SftpException { | |||
| String dir=directory.getDirectory().getName(); | |||
| try{ | |||
| channel.stat(dir); | |||
| } | |||
| catch(SftpException e){ | |||
| // dir does not exist. | |||
| if (e.id==ChannelSftp.SSH_FX_NO_SUCH_FILE) { | |||
| channel.mkdir(dir); | |||
| } | |||
| } | |||
| channel.cd(dir); | |||
| sendDirectory(channel, directory); | |||
| channel.cd(".."); | |||
| } | |||
| private void sendFileToRemote(ChannelSftp channel, | |||
| File localFile, | |||
| String remotePath) | |||
| throws IOException, SftpException { | |||
| long filesize = localFile.length(); | |||
| if (remotePath==null) { | |||
| remotePath=localFile.getName(); | |||
| } | |||
| long startTime = System.currentTimeMillis(); | |||
| long totalLength = filesize; | |||
| // only track progress for files larger than 100kb in verbose mode | |||
| boolean trackProgress = getVerbose() && filesize > 102400; | |||
| SftpProgressMonitor monitor = null; | |||
| if (trackProgress){ | |||
| monitor = getProgressMonitor(); | |||
| } | |||
| try{ | |||
| if (this.getVerbose()) { | |||
| log("Sending: " + localFile.getName() + " : " + filesize); | |||
| } | |||
| channel.put(localFile.getAbsolutePath(), remotePath, monitor); | |||
| } | |||
| finally { | |||
| if (this.getVerbose()) { | |||
| long endTime = System.currentTimeMillis(); | |||
| logStats(startTime, endTime, (int) totalLength); | |||
| } | |||
| } | |||
| } | |||
| public File getLocalFile() { | |||
| return localFile; | |||
| } | |||
| public String getRemotePath() { | |||
| return remotePath; | |||
| } | |||
| } | |||