/*
* Copyright 2012 jMethods, Inc.
*
* 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 com.myjavaworld.ftp;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.NoRouteToHostException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.ParseException;
import javax.net.SocketFactory;
import javax.swing.event.EventListenerList;
/**
* A <code>DataConnection</code> object is used to transfer data over the data
* connection in an FTP process. <code>FTPClient</code> object will initiate a
* <code>DataConnection</code> based on the commands the send to the remote
* host. For more details about data connection, refer to the FTP protocol
* specification (RFC 959).
*/
public class DataConnection implements FTPConstants {
/**
* A reference to the <code>FTPClient</code> that created this <code>
* DataConnection</code>.
*/
protected FTPClient client = null;
/**
* A <code>ServerSocket</code> object used in active mode of data transfers.
*/
protected ServerSocket server = null;
/**
* A Socket that represents a data connection.
*/
protected Socket socket = null;
/**
* An InputStream object for reading binary data either from the remote host
* or from the local file system.
*/
protected InputStream in = null;
/**
* An OutputStream object used to write binary data to the remote host or to
* the local file system.
*/
protected OutputStream out = null;
/**
* A List of registered listeners that are interested in receiving
* notifications about the activities of this <code>DataConnection</code>.
*/
protected EventListenerList listenerList = null;
/**
* A flag for aborting the data transfer.
*/
protected boolean abort = false;
/**
* Constructs a <code>DataConnection</code> object.
*
* @param client
* <code>FTPClient</code> that created this data connection.
*/
public DataConnection(FTPClient client) {
this.client = client;
this.listenerList = client.getListenerList();
this.abort = false;
}
/**
* Binds a server socket on the local host on a free port. This server
* socket is used for transmitting data in active mode.
*
* @return the port number to which this server is bound to.
* @exception ConnectionException
* If could not bind a server.
*/
public synchronized int bind() throws ConnectionException {
try {
server = new ServerSocket(0, 0, client.getLocalAddress());
return server.getLocalPort();
} catch (IOException exp) {
throw new ConnectionException(exp.toString());
}
}
/**
* Listens for connections. This method blocks until a connection is made or
* a timeout occurs.
*
* @exception ConnectionException
* If a network or IO error occurs.
*/
public synchronized void accept() throws ConnectionException {
try {
server.setSoTimeout(client.getTimeout());
} catch (SocketException exp) {
// Let's ignore this.
}
try {
socket = server.accept();
} catch (IOException exp) {
throw new ConnectionException(exp.toString());
}
try {
socket.setSoTimeout(client.getTimeout());
} catch (SocketException exp) {
// Let's ignore this.
}
try {
socket.setSendBufferSize(client.getBufferSize());
socket.setReceiveBufferSize(client.getBufferSize());
} catch (SocketException exp) {
// Let's ignore this.
}
}
/**
* Connects to the specified IP address to the specified port number. This
* method is called by the FTPClients if they prefer to transfer data in
* passive mode.
*
* @param ipAddress
* IP address of the remote host in xxx.xxx.xxx.xxx format.
* @param port
* Port number to connect to.
* @exception ConnectionException
* if a network or IO error occurs.
*/
public synchronized void connect(String ipAddress, int port)
throws ConnectionException {
try {
if (client.isPassiveIPSubstitutionEnabled()) {
connect(client.getRemoteAddress(), port);
} else {
connect(InetAddress.getByName(ipAddress), port);
}
} catch (UnknownHostException exp) {
throw new ConnectionException(exp.toString());
}
}
/**
* Connects to the specified <code>InetAddress</code> to the specified port
* number. This method is called by the FTPClients if they prefer to
* transfer data in passive mode.
*
* @param address
* Internet address of the remote host.
* @param port
* Port number to connect to.
* @exception ConnectionException
* if a network or IO error occurs.
*/
public synchronized void connect(InetAddress address, int port)
throws ConnectionException {
try {
SocketFactory factory = new CustomSocketFactory(client);
socket = factory.createSocket(address, port);
// socket = new Socket(address, port);
try {
socket.setSoTimeout(client.getTimeout());
} catch (SocketException exp) {
// Let's ignore this.
}
try {
socket.setSendBufferSize(client.getBufferSize());
socket.setReceiveBufferSize(client.getBufferSize());
} catch (SocketException exp) {
// Let's ignore this.
}
} catch (UnknownHostException exp) {
throw new ConnectionException(exp.toString());
} catch (NoRouteToHostException exp) {
throw new ConnectionException(exp.toString());
} catch (SocketException exp) {
throw new ConnectionException(exp.toString());
} catch (IOException exp) {
throw new ConnectionException(exp.toString());
}
}
/**
* Closes this data connection and open streams, if any.
*/
public void close() {
FTPUtil.close(in);
FTPUtil.close(out);
FTPUtil.close(socket);
FTPUtil.close(server);
}
/**
* Sets the abort flag to <code>true</code>.
*/
public void abort() {
this.abort = true;
}
/**
* Checks to see if the abort flag was set.
*
* @return <code>true</code>, if the abort request was made.
* <code>false</code>, otherwise.
*/
public boolean isAborted() {
return abort;
}
/**
* Parses the data received over this data connection to an array of
* <code>RemoteFile</code> objects.
*
* @param dir
* The remote directory for which the listing is being done.
* @return An array of <code>RemoteFile</code> objects which are supposedly
* the children of the specified directory <code>dir</code>.
* @exception ConnectionException
* If a network or IO error occurs.
* @exception ParseException
* if The data can not be parsed to a <code>RemoteFile</code>
* object.
*/
public RemoteFile[] list(RemoteFile dir) throws ConnectionException,
ParseException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()), client.getBufferSize());
ListParser parser = client.getListParser();
return parser.parse(dir, reader);
} catch (IOException exp) {
throw new ConnectionException(exp.toString());
} finally {
FTPUtil.close(reader);
close();
}
}
/**
* Reads the stream over this data connection and saves it in the specified
* local file <code>destination</code>.
*
* @param destination
* The local file to which the contents of the stream are to be
* stored.
* @param append
* Whether or not to append the contents of the stream to the
* local file.
* @exception IOException
* if an IO error occurs.
*/
public void download(File destination, boolean append) throws IOException {
abort = false;
download(destination, append, (client.getType() == TYPE_ASCII));
}
/**
* Sends the contents of the specified local file <code>src</code> to the
* remote host.
*
* @param source
* The source for whose contents are to be sent to the remote
* host.
* @param skip
* If the value of this parameter is greater than zero, these
* many bytes will be skipped before sending the data.
* @exception IOException
* if an IO error occurs.
*/
public void upload(File source, long skip) throws IOException {
abort = false;
upload(source, skip, (client.getType() == TYPE_ASCII));
}
/**
* Stores the contents of the stream to the local file <code>destination
* </code> in BINARY format.
*
* @param destination
* Local file to which the contents read over the stream are to
* be saved.
* @param append
* If this flag is true, The local file will be opened in append
* mode. Otherwise, The local file will be overwritten.
* @exception IOException
* if an IO error occurs.
*/
private void download(File destination, boolean append, boolean ascii)
throws IOException {
long totalBytes = 0L;
try {
int bufferSize = client.getBufferSize();
in = new BufferedInputStream(socket.getInputStream(), bufferSize);
if (ascii) {
in = new FromNetASCIIInputStream(in);
}
out = new BufferedOutputStream(new FileOutputStream(
destination.getAbsolutePath(), append), bufferSize);
byte[] bytes = new byte[bufferSize];
int bytesRead = 0;
fireDataTransferStarted(new DataConnectionEvent(client,
DataConnectionEvent.RECEIVE, totalBytes));
while ((bytesRead = in.read(bytes)) != -1) {
if (abort) {
fireDataTransferAborted(new DataConnectionEvent(this,
DataConnectionEvent.RECEIVE, totalBytes));
break;
}
out.write(bytes, 0, bytesRead);
totalBytes += bytesRead;
fireDataTransferProgress(new DataConnectionEvent(client,
DataConnectionEvent.RECEIVE, totalBytes));
}
} finally {
fireDataTransferFinished(new DataConnectionEvent(client,
DataConnectionEvent.RECEIVE, totalBytes));
close();
}
}
/**
* Sends the contents of the specified local file <code>src</code> to the
* remote host over this data connection in BINARY format.
*
* @param source
* Local file whose contents are to be sent to the remote host.
* @param skip
* number of bytes to skip in the local file.
* @exception IOException
* if an IO error occurs.
*/
private void upload(File source, long skip, boolean ascii)
throws IOException {
long totalBytes = 0L;
try {
int bufferSize = client.getBufferSize();
in = new BufferedInputStream(new FileInputStream(source),
bufferSize);
if (ascii) {
in = new ToNetASCIIInputStream(in);
}
out = new BufferedOutputStream(socket.getOutputStream(), bufferSize);
byte[] bytes = new byte[bufferSize];
int bytesRead = 0;
if (skip > 0) {
in.skip(skip);
}
fireDataTransferStarted(new DataConnectionEvent(client,
DataConnectionEvent.SEND, totalBytes));
while ((bytesRead = in.read(bytes)) != -1) {
if (abort) {
fireDataTransferAborted(new DataConnectionEvent(this,
DataConnectionEvent.RECEIVE, totalBytes));
break;
}
out.write(bytes, 0, bytesRead);
out.flush();
totalBytes += bytesRead;
fireDataTransferProgress(new DataConnectionEvent(client,
DataConnectionEvent.SEND, totalBytes));
}
} finally {
fireDataTransferFinished(new DataConnectionEvent(client,
DataConnectionEvent.SEND, totalBytes));
close();
}
}
/**
* Notifies the registered listeners that the data transfer has started.
*
* @param evt
* <code>DataConnectionEvent</code>.
*/
protected void fireDataTransferStarted(DataConnectionEvent evt) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == DataConnectionListener.class) {
((DataConnectionListener) listeners[i + 1])
.dataTransferStarted(evt);
}
}
}
/**
* Notifies the registered listeners that the data transfer has finished.
*
* @param evt
* <code>DataConnectionEvent</code>.
*/
protected void fireDataTransferFinished(DataConnectionEvent evt) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == DataConnectionListener.class) {
((DataConnectionListener) listeners[i + 1])
.dataTransferFinished(evt);
}
}
}
/**
* Notifies the registered listeners that the data transfer has aborted.
*
* @param evt
* <code>DataConnectionEvent</code>.
*/
protected void fireDataTransferAborted(DataConnectionEvent evt) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == DataConnectionListener.class) {
((DataConnectionListener) listeners[i + 1])
.dataTransferAborted(evt);
}
}
}
/**
* Notifies the registered listeners that the data transfer is in progress.
*
* @param evt
* <code>DataConnectionEvent</code>.
*/
protected void fireDataTransferProgress(DataConnectionEvent evt) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == DataConnectionListener.class) {
((DataConnectionListener) listeners[i + 1])
.dataTransferProgress(evt);
}
}
}
/**
* Writes the given message to the standard output.
*
* @param message
* The message to be written.
*/
protected void stdout(String message) {
System.out.println(message);
}
}