001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional.ssh;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.EOFException;
024: import java.io.InputStream;
025: import java.io.OutputStream;
026: import java.io.FileOutputStream;
027: import java.io.ByteArrayOutputStream;
028: import com.jcraft.jsch.JSchException;
029: import com.jcraft.jsch.Session;
030: import com.jcraft.jsch.Channel;
031:
032: /**
033: * A helper object representing an scp download.
034: */
035: public class ScpFromMessage extends AbstractSshMessage {
036:
037: private static final byte LINE_FEED = 0x0a;
038: private static final int BUFFER_SIZE = 1024;
039:
040: private String remoteFile;
041: private File localFile;
042: private boolean isRecursive = false;
043:
044: /**
045: * Constructor for ScpFromMessage
046: * @param session the ssh session to use
047: */
048: public ScpFromMessage(Session session) {
049: super (session);
050: }
051:
052: /**
053: * Constructor for ScpFromMessage
054: * @param verbose if true do verbose logging
055: * @param session the ssh session to use
056: * @since Ant 1.7
057: */
058: public ScpFromMessage(boolean verbose, Session session) {
059: super (verbose, session);
060: }
061:
062: /**
063: * Constructor for ScpFromMessage.
064: * @param verbose if true log extra information
065: * @param session the Scp session to use
066: * @param aRemoteFile the remote file name
067: * @param aLocalFile the local file
068: * @param recursive if true use recursion (-r option to scp)
069: * @since Ant 1.6.2
070: */
071: public ScpFromMessage(boolean verbose, Session session,
072: String aRemoteFile, File aLocalFile, boolean recursive) {
073: super (verbose, session);
074: this .remoteFile = aRemoteFile;
075: this .localFile = aLocalFile;
076: this .isRecursive = recursive;
077: }
078:
079: /**
080: * Constructor for ScpFromMessage.
081: * @param session the Scp session to use
082: * @param aRemoteFile the remote file name
083: * @param aLocalFile the local file
084: * @param recursive if true use recursion (-r option to scp)
085: */
086: public ScpFromMessage(Session session, String aRemoteFile,
087: File aLocalFile, boolean recursive) {
088: this (false, session, aRemoteFile, aLocalFile, recursive);
089: }
090:
091: /**
092: * Carry out the transfer.
093: * @throws IOException on i/o errors
094: * @throws JSchException on errors detected by scp
095: */
096: public void execute() throws IOException, JSchException {
097: String command = "scp -f ";
098: if (isRecursive) {
099: command += "-r ";
100: }
101: command += remoteFile;
102: Channel channel = openExecChannel(command);
103: try {
104: // get I/O streams for remote scp
105: OutputStream out = channel.getOutputStream();
106: InputStream in = channel.getInputStream();
107:
108: channel.connect();
109:
110: sendAck(out);
111: startRemoteCpProtocol(in, out, localFile);
112: } finally {
113: if (channel != null) {
114: channel.disconnect();
115: }
116: }
117: log("done\n");
118: }
119:
120: private void startRemoteCpProtocol(InputStream in,
121: OutputStream out, File localFile) throws IOException {
122: File startFile = localFile;
123: while (true) {
124: // C0644 filesize filename - header for a regular file
125: // T time 0 time 0\n - present if perserve time.
126: // D directory - this is the header for a directory.
127: ByteArrayOutputStream stream = new ByteArrayOutputStream();
128: while (true) {
129: int read = in.read();
130: if (read < 0) {
131: return;
132: }
133: if ((byte) read == LINE_FEED) {
134: break;
135: }
136: stream.write(read);
137: }
138: String serverResponse = stream.toString("UTF-8");
139: if (serverResponse.charAt(0) == 'C') {
140: parseAndFetchFile(serverResponse, startFile, out, in);
141: } else if (serverResponse.charAt(0) == 'D') {
142: startFile = parseAndCreateDirectory(serverResponse,
143: startFile);
144: sendAck(out);
145: } else if (serverResponse.charAt(0) == 'E') {
146: startFile = startFile.getParentFile();
147: sendAck(out);
148: } else if (serverResponse.charAt(0) == '\01'
149: || serverResponse.charAt(0) == '\02') {
150: // this indicates an error.
151: throw new IOException(serverResponse.substring(1));
152: }
153: }
154: }
155:
156: private File parseAndCreateDirectory(String serverResponse,
157: File localFile) {
158: int start = serverResponse.indexOf(" ");
159: // appears that the next token is not used and it's zero.
160: start = serverResponse.indexOf(" ", start + 1);
161: String directoryName = serverResponse.substring(start + 1);
162: if (localFile.isDirectory()) {
163: File dir = new File(localFile, directoryName);
164: dir.mkdir();
165: log("Creating: " + dir);
166: return dir;
167: }
168: return null;
169: }
170:
171: private void parseAndFetchFile(String serverResponse,
172: File localFile, OutputStream out, InputStream in)
173: throws IOException {
174: int start = 0;
175: int end = serverResponse.indexOf(" ", start + 1);
176: start = end + 1;
177: end = serverResponse.indexOf(" ", start + 1);
178: long filesize = Long.parseLong(serverResponse.substring(start,
179: end));
180: String filename = serverResponse.substring(end + 1);
181: log("Receiving: " + filename + " : " + filesize);
182: File transferFile = (localFile.isDirectory()) ? new File(
183: localFile, filename) : localFile;
184: fetchFile(transferFile, filesize, out, in);
185: waitForAck(in);
186: sendAck(out);
187: }
188:
189: private void fetchFile(File localFile, long filesize,
190: OutputStream out, InputStream in) throws IOException {
191: byte[] buf = new byte[BUFFER_SIZE];
192: sendAck(out);
193:
194: // read a content of lfile
195: FileOutputStream fos = new FileOutputStream(localFile);
196: int length;
197: long totalLength = 0;
198: long startTime = System.currentTimeMillis();
199:
200: // only track progress for files larger than 100kb in verbose mode
201: boolean trackProgress = getVerbose() && filesize > 102400;
202: // since filesize keeps on decreasing we have to store the
203: // initial filesize
204: long initFilesize = filesize;
205: int percentTransmitted = 0;
206:
207: try {
208: while (true) {
209: length = in.read(buf, 0,
210: (BUFFER_SIZE < filesize) ? BUFFER_SIZE
211: : (int) filesize);
212: if (length < 0) {
213: throw new EOFException("Unexpected end of stream.");
214: }
215: fos.write(buf, 0, length);
216: filesize -= length;
217: totalLength += length;
218: if (filesize == 0) {
219: break;
220: }
221:
222: if (trackProgress) {
223: percentTransmitted = trackProgress(initFilesize,
224: totalLength, percentTransmitted);
225: }
226: }
227: } finally {
228: long endTime = System.currentTimeMillis();
229: logStats(startTime, endTime, totalLength);
230: fos.flush();
231: fos.close();
232: }
233: }
234:
235: }
|