/*************************************************************************** * Copyright 2006-2016 by Christian Ihle * * contact@kouchat.net * * * * This file is part of KouChat. * * * * KouChat is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 3 of * * the License, or (at your option) any later version. * * * * KouChat 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with KouChat. * * If not, see <http://www.gnu.org/licenses/>. * ***************************************************************************/ package net.usikkert.kouchat.net; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; import net.usikkert.kouchat.Constants; import net.usikkert.kouchat.event.FileTransferListener; import net.usikkert.kouchat.misc.User; import net.usikkert.kouchat.util.ByteCounter; /** * This is a class for receiving files from other users. * * <p>To receive a file, a server socket has to be opened, * to wait for incoming transfers.</p> * * @author Christian Ihle */ public class FileReceiver implements FileTransfer { /** The logger. */ private static final Logger LOG = Logger.getLogger(FileReceiver.class.getName()); /** The user sending the file. */ private final User user; /** The file size in bytes. */ private final long size; /** The file from the user. */ private File file; /** The original file name. */ private final String originalFileName; /** The unique ID of this file transfer. */ private final int id; /** Keeps count of the transfer speed. */ private final ByteCounter bCounter; /** Percent of the file received. */ private int percent; /** Number of bytes received. */ private long transferred; /** If the file was successfully received. */ private boolean received; /** If the file transfer is canceled. */ private boolean cancel; /** If the client has accepted to receive the file. */ private boolean accepted; /** If the client has rejected the file. */ private boolean rejected; /** The file transfer listener. */ private FileTransferListener listener; /** The server socket waiting for an incoming connection. */ private ServerSocket sSock; /** The socket connection to the other user. */ private Socket sock; /** The output stream to the file. */ private FileOutputStream fos; /** The input stream from the other user. */ private InputStream is; /** * Constructor. Creates a new file receiver. * * @param user The user which sends the file. * @param file The file the user is sending. * @param size The size of the file, in bytes. * @param id The unique ID of this file transfer. */ public FileReceiver(final User user, final File file, final long size, final int id) { this.user = user; this.file = file; this.size = size; this.id = id; this.originalFileName = file.getName(); bCounter = new ByteCounter(); } /** * Starts a server connection which the sender can use to connect * for transferring the file, and returns the opened port. * * @return The port which the sender can connect to. * @throws ServerException If the server could not be started. */ public int startServer() throws ServerException { int port = Constants.NETWORK_FILE_TRANSFER_PORT; boolean done = false; int counter = 0; while (!done && counter < 50) { try { sSock = new ServerSocket(port); final TimeoutThread tt = new TimeoutThread(); tt.start(); done = true; } catch (final IOException e) { LOG.log(Level.WARNING, "Could not open " + port, e); port++; } finally { counter++; } } if (!done) { throw new ServerException("Could not start server"); } return port; } /** * Waits for an incoming connection, then receives the * file from the other user. * * @return If the file transfer was successful. */ public boolean transfer() { listener.statusConnecting(); received = false; cancel = false; try { if (sSock != null) { sock = sSock.accept(); listener.statusTransferring(); fos = new FileOutputStream(file); is = sock.getInputStream(); final byte[] b = new byte[1024]; transferred = 0; percent = 0; int tmpTransferred = 0; int tmpPercent = 0; int transCounter = 0; bCounter.prepare(); while (!cancel && (tmpTransferred = is.read(b)) != -1) { fos.write(b, 0, tmpTransferred); transferred += tmpTransferred; percent = (int) ((transferred * 100) / size); bCounter.addBytes(tmpTransferred); transCounter++; if (percent > tmpPercent || transCounter >= 250) { transCounter = 0; tmpPercent = percent; listener.transferUpdate(); } } if (!cancel && transferred == size) { received = true; listener.statusCompleted(); } else { listener.statusFailed(); } } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString()); listener.statusFailed(); } finally { stopReceiver(); cleanupConnections(); } return received; } /** * Sets all connections to null. */ private void cleanupConnections() { is = null; fos = null; sock = null; sSock = null; } /** * Closes the connection to the user. */ private void stopReceiver() { try { if (is != null) { is.close(); } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString(), e); } try { if (fos != null) { fos.flush(); } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString(), e); } try { if (fos != null) { fos.close(); } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString(), e); } try { if (sock != null) { sock.close(); } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString(), e); } try { if (sSock != null) { sSock.close(); } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString(), e); } } /** * Checks if the file transfer has been canceled. * * @return If the file transfer has been canceled. */ @Override public boolean isCanceled() { return cancel; } /** * Cancels the file transfer. */ @Override public void cancel() { cancel = true; stopReceiver(); if (listener != null) { listener.statusFailed(); } } /** * The percent of the file transfer that is completed. * * @return Percent completed. */ @Override public int getPercent() { return percent; } /** * Checks if the file transfer is complete. * * @return If the file transfer is complete. */ @Override public boolean isTransferred() { return received; } /** * Gets the file that is being transferred. * * @return The file. */ public File getFile() { return file; } /** * Changes the file to save to. * * @param file The new file to save to. */ public void setFile(final File file) { this.file = file; } /** * Gets the file name of the original file from the other user. * As opposed to the current file which might have been renamed. * * @return The original file name. */ public String getOriginalFileName() { return originalFileName; } /** * The other user, which sends a file. * * @return The other user. */ @Override public User getUser() { return user; } /** * Number of bytes transferred. * * @return Bytes transferred. */ @Override public long getTransferred() { return transferred; } /** * Gets the size of the file being transferred, in bytes. * * @return The file size. */ @Override public long getFileSize() { return size; } /** * Gets the name of the file being transferred. * * @return The name of the file. */ @Override public String getFileName() { return file.getName(); } /** * Gets the direction, which is receive. * * @return Receive, the direction of the file transfer. */ @Override public Direction getDirection() { return Direction.RECEIVE; } /** * Gets the number of bytes transferred per second. * * @return The speed in bytes per second. */ @Override public long getSpeed() { return bCounter.getBytesPerSec(); } /** * Gets the ID of this file transfer. The ID is unique during the session, and starts with 1. * * @return The unique ID of this file transfer. */ @Override public int getId() { return id; } /** * Registers a file transfer listener, which will receive updates * when certain events happen in the progression of the file transfer. * * @param listener The listener to register. */ @Override public void registerListener(final FileTransferListener listener) { this.listener = listener; listener.statusWaiting(); } /** * If the client has accepted to receive the file. * * @return If the file is accepted. */ public boolean isAccepted() { return accepted; } /** * Accept the file transfer. */ public void accept() { accepted = true; } /** * If the client has rejected the file. * * @return If the file is rejected. */ public boolean isRejected() { return rejected; } /** * Reject the file transfer. */ public void reject() { rejected = true; } /** * A thread for closing the server connection if no client * has connected within 15 seconds. * * <p>This does not mean that the user only has 15 seconds to decide * where to save the file. This timer is started after the user has * decided, and waits for an automated response from the sender. * If nothing has happened to the sender, the response should be very quick.</p> */ private class TimeoutThread extends Thread { /** * Constructor. Sets the name of the thread. */ TimeoutThread() { setName("TimeoutThread"); } /** * The thread. Sleeps for 15 seconds, and then closes the * server connection if it is not already closed. */ @Override public void run() { try { sleep(15000); } catch (final InterruptedException e) { LOG.log(Level.SEVERE, e.toString(), e); } try { if (sSock != null) { sSock.close(); sSock = null; } } catch (final IOException e) { LOG.log(Level.SEVERE, e.toString(), e); } } } }