/*
* pjftp FTP server.
* Copyright (C) 2012 Dmitriy Simbiriatin <dmitriy.simbiriatin@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ds.pjftp.main;
import ds.pjftp.utils.NotNull;
import ds.pjftp.representation.Representations;
import ds.pjftp.transmission.TransmissionMode;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.DirectoryStream;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import ds.pjftp.transmission.TransmissionModes;
import ds.pjftp.utils.IOUtils;
import ds.pjftp.utils.PassivePort;
import ds.pjftp.utils.DateUtils;
import ds.pjftp.representation.Representation;
import static ds.pjftp.utils.IOUtils.NEW_LINE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
/**
* This class handles file transfer.
*/
public class ServerDTP {
/**
* Connection timeout in passive mode (10 seconds).
*/
public static final int PASV_TIMEOUT = 10000;
/**
* Default size for buffer, that holds info about the file.
*/
public static final int FILE_INFO_BUF = 100;
/**
* Client data socket.
*/
private Socket dataClient;
/**
* Server data socket.
*/
private ServerSocket dataServer;
/**
* Address of data socket.
*/
private InetSocketAddress addr;
/**
* DTP representation. By default Image representation is used.
*/
private Representation representation = Representations.get('A');
/**
* Transmission mode used in DTP. By default Stream mode is used.
*/
private TransmissionMode transmissionMode = TransmissionModes.get('S');
/**
* Client's session in which DTP works.
*/
private final ClientSession session;
/**
* Constructs ServerDTP object.
* @param session client session.
*/
public ServerDTP(@NotNull final ClientSession session) {
this.session = session;
}
/**
* Changes address of data socket.
* @param addr new address.
*/
public void setInetAddr(final InetSocketAddress addr) {
this.addr = addr;
}
/**
* Returns an address of data socket.
* @return an address of data socket.
*/
public InetSocketAddress getInetAddr() {
return addr;
}
/**
* Changes current transmission mode of DTP server.
* @param transmissionMode new transmission mode.
*/
public void setTransmissionMode(final TransmissionMode transmissionMode) {
this.transmissionMode = transmissionMode;
}
/**
* Returns current representation.
* @return current representation.
*/
public Representation getRepresentation() {
return representation;
}
/**
* Changes current representation of DTP server.
* @param representation new representation.
*/
public void setRepresentation(final Representation representation) {
this.representation = representation;
}
/**
* Generates address for data socket. Used in passive mode.
* Searches for available port, where data socket can be connected.
* @return address, which can be used for connection.
*/
public InetSocketAddress genInetAddr() {
try {
final int port = PassivePort.getAvailable();
if (port == -1) {
return null;
}
final InetAddress addr = InetAddress.getLocalHost();
return new InetSocketAddress(addr, port);
} catch (UnknownHostException ex) {
return null;
}
}
/**
* Initiates data connection in active mode.
*/
public void initActiveConnection() throws IOException {
dataClient = new Socket(addr.getAddress(), addr.getPort());
}
/**
* Initiates data connection in passive mode.
*/
public void initPassiveConnection() throws IOException {
dataServer = new ServerSocket(addr.getPort());
dataServer.setSoTimeout(PASV_TIMEOUT);
}
/**
* Opens data connection.
*/
public void openConnection() throws IOException {
session.replyWithSpace(150, "Opening connection in {} mode",
representation);
// We've initiated passive connection.
if (dataClient == null) {
dataClient = dataServer.accept();
}
}
/**
* Closes all opened data sockets.
*/
public void closeConnection() {
session.replyWithSpace(226, "Closing data connection");
IOUtils.close(dataServer);
IOUtils.close(dataClient);
addr = null;
dataServer = null;
dataClient = null;
}
/**
* Sends specified file to a client.
* @param file file to be send.
* @throws IOException if failed to send the file.
*/
public void sendFile(@NotNull final Path file) throws IOException {
try (InputStream input = Files.newInputStream(file, READ)) {
transmissionMode.send(input, dataClient);
}
}
/**
* Receives file from client.
* @param file place, where file should be saved.
* @throws IOException if failed to receive the file.
*/
public void receiveFile(@NotNull final Path file) throws IOException {
try (OutputStream output = Files.newOutputStream(file, CREATE, WRITE)) {
transmissionMode.receive(dataClient, output);
}
}
/**
* Lists files, available in specified directory. (prints only file names without additional information).
* @param path directory to be listed.
* @throws IOException if failed to list the files.
*/
public void listFiles(@NotNull final Path path) throws IOException {
final OutputStream output = representation.getOutputStream(dataClient);
try (PrintWriter writer = IOUtils.getWriter(output)) {
try (DirectoryStream<Path> dir = Files.newDirectoryStream(path)) {
for (Path file : dir) {
final String name = file.getFileName().toString();
writer.print(name + NEW_LINE);
writer.flush();
}
}
}
}
/**
* Lists files, available in specified directory (prints detailed info for each file).
* @param path directory to be listed.
* @throws IOException if failed to list the files.
*/
public void listFilesWithInfo(@NotNull final Path path) throws IOException {
final OutputStream output = representation.getOutputStream(dataClient);
try (PrintWriter writer = IOUtils.getWriter(output)) {
// Specified parameter can be a directory or just a file.
// We gonna check this.
if (Files.isDirectory(path, NOFOLLOW_LINKS)) {
// Listing files in the directory.
try (DirectoryStream<Path> dir = Files.newDirectoryStream(path)) {
for (Path file : dir) {
listFile(file, writer);
}
writer.flush();
}
} else {
// Listing info just for the specified file.
listFile(path, writer);
writer.flush();
}
}
}
/**
* Prints detailed information for specified file.
*/
private void listFile(@NotNull final Path path, @NotNull final PrintWriter writer) throws IOException {
final BasicFileAttributeView view = Files.getFileAttributeView(
path, BasicFileAttributeView.class);
final BasicFileAttributes attrs = view.readAttributes();
final String timeStr = DateUtils.format(attrs.lastModifiedTime());
// As not all the file systems support POSIX file permissions,
// we don't try to retrieve them + retrieving such information
// for each file, can entail performance degradation.
final StringBuilder infoStr = new StringBuilder(FILE_INFO_BUF);
infoStr.append(attrs.isDirectory() ? 'd' : '-');
infoStr.append("rwxrwxrwx 0 ftp ftp ");
infoStr.append(attrs.size());
infoStr.append(' ');
infoStr.append(timeStr);
infoStr.append(' ');
infoStr.append(path.getFileName());
infoStr.append(NEW_LINE);
writer.print(infoStr.toString());
}
}