OutgoingFileTransfer.java
- /**
- *
- * Copyright 2003-2006 Jive Software.
- *
- * 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.jivesoftware.smackx.filetransfer;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import org.jivesoftware.smack.SmackException;
- import org.jivesoftware.smack.SmackException.IllegalStateChangeException;
- import org.jivesoftware.smack.XMPPException;
- import org.jivesoftware.smack.XMPPException.XMPPErrorException;
- import org.jivesoftware.smack.packet.XMPPError;
- import org.jxmpp.jid.Jid;
- /**
- * Handles the sending of a file to another user. File transfer's in jabber have
- * several steps and there are several methods in this class that handle these
- * steps differently.
- *
- * @author Alexander Wenckus
- *
- */
- public class OutgoingFileTransfer extends FileTransfer {
- private static final Logger LOGGER = Logger.getLogger(OutgoingFileTransfer.class.getName());
- private static int RESPONSE_TIMEOUT = 60 * 1000;
- private NegotiationProgress callback;
- /**
- * Returns the time in milliseconds after which the file transfer
- * negotiation process will timeout if the other user has not responded.
- *
- * @return Returns the time in milliseconds after which the file transfer
- * negotiation process will timeout if the remote user has not
- * responded.
- */
- public static int getResponseTimeout() {
- return RESPONSE_TIMEOUT;
- }
- /**
- * Sets the time in milliseconds after which the file transfer negotiation
- * process will timeout if the other user has not responded.
- *
- * @param responseTimeout
- * The timeout time in milliseconds.
- */
- public static void setResponseTimeout(int responseTimeout) {
- RESPONSE_TIMEOUT = responseTimeout;
- }
- private OutputStream outputStream;
- private Jid initiator;
- private Thread transferThread;
- protected OutgoingFileTransfer(Jid initiator, Jid target,
- String streamID, FileTransferNegotiator transferNegotiator) {
- super(target, streamID, transferNegotiator);
- this.initiator = initiator;
- }
- protected void setOutputStream(OutputStream stream) {
- if (outputStream == null) {
- this.outputStream = stream;
- }
- }
- /**
- * Returns the output stream connected to the peer to transfer the file. It
- * is only available after it has been successfully negotiated by the
- * {@link StreamNegotiator}.
- *
- * @return Returns the output stream connected to the peer to transfer the
- * file.
- */
- protected OutputStream getOutputStream() {
- if (getStatus().equals(FileTransfer.Status.negotiated)) {
- return outputStream;
- } else {
- return null;
- }
- }
- /**
- * This method handles the negotiation of the file transfer and the stream,
- * it only returns the created stream after the negotiation has been completed.
- *
- * @param fileName
- * The name of the file that will be transmitted. It is
- * preferable for this name to have an extension as it will be
- * used to determine the type of file it is.
- * @param fileSize
- * The size in bytes of the file that will be transmitted.
- * @param description
- * A description of the file that will be transmitted.
- * @return The OutputStream that is connected to the peer to transmit the
- * file.
- * @throws XMPPException
- * Thrown if an error occurs during the file transfer
- * negotiation process.
- * @throws SmackException if there was no response from the server.
- * @throws InterruptedException
- */
- public synchronized OutputStream sendFile(String fileName, long fileSize,
- String description) throws XMPPException, SmackException, InterruptedException {
- if (isDone() || outputStream != null) {
- throw new IllegalStateException(
- "The negotation process has already"
- + " been attempted on this file transfer");
- }
- try {
- setFileInfo(fileName, fileSize);
- this.outputStream = negotiateStream(fileName, fileSize, description);
- } catch (XMPPErrorException e) {
- handleXMPPException(e);
- throw e;
- }
- return outputStream;
- }
- /**
- * This methods handles the transfer and stream negotiation process. It
- * returns immediately and its progress will be updated through the
- * {@link NegotiationProgress} callback.
- *
- * @param fileName
- * The name of the file that will be transmitted. It is
- * preferable for this name to have an extension as it will be
- * used to determine the type of file it is.
- * @param fileSize
- * The size in bytes of the file that will be transmitted.
- * @param description
- * A description of the file that will be transmitted.
- * @param progress
- * A callback to monitor the progress of the file transfer
- * negotiation process and to retrieve the OutputStream when it
- * is complete.
- */
- public synchronized void sendFile(final String fileName,
- final long fileSize, final String description,
- final NegotiationProgress progress)
- {
- if(progress == null) {
- throw new IllegalArgumentException("Callback progress cannot be null.");
- }
- checkTransferThread();
- if (isDone() || outputStream != null) {
- throw new IllegalStateException(
- "The negotation process has already"
- + " been attempted for this file transfer");
- }
- setFileInfo(fileName, fileSize);
- this.callback = progress;
- transferThread = new Thread(new Runnable() {
- public void run() {
- try {
- OutgoingFileTransfer.this.outputStream = negotiateStream(
- fileName, fileSize, description);
- progress.outputStreamEstablished(OutgoingFileTransfer.this.outputStream);
- }
- catch (XMPPErrorException e) {
- handleXMPPException(e);
- }
- catch (Exception e) {
- setException(e);
- }
- }
- }, "File Transfer Negotiation " + streamID);
- transferThread.start();
- }
- private void checkTransferThread() {
- if (transferThread != null && transferThread.isAlive() || isDone()) {
- throw new IllegalStateException(
- "File transfer in progress or has already completed.");
- }
- }
- /**
- * This method handles the stream negotiation process and transmits the file
- * to the remote user. It returns immediately and the progress of the file
- * transfer can be monitored through several methods:
- *
- * <UL>
- * <LI>{@link FileTransfer#getStatus()}
- * <LI>{@link FileTransfer#getProgress()}
- * <LI>{@link FileTransfer#isDone()}
- * </UL>
- *
- * @param file the file to transfer to the remote entity.
- * @param description a description for the file to transfer.
- * @throws SmackException
- * If there is an error during the negotiation process or the
- * sending of the file.
- */
- public synchronized void sendFile(final File file, final String description)
- throws SmackException {
- checkTransferThread();
- if (file == null || !file.exists() || !file.canRead()) {
- throw new IllegalArgumentException("Could not read file");
- } else {
- setFileInfo(file.getAbsolutePath(), file.getName(), file.length());
- }
- transferThread = new Thread(new Runnable() {
- public void run() {
- try {
- outputStream = negotiateStream(file.getName(), file
- .length(), description);
- } catch (XMPPErrorException e) {
- handleXMPPException(e);
- return;
- }
- catch (Exception e) {
- setException(e);
- }
- if (outputStream == null) {
- return;
- }
- if (!updateStatus(Status.negotiated, Status.in_progress)) {
- return;
- }
- InputStream inputStream = null;
- try {
- inputStream = new FileInputStream(file);
- writeToStream(inputStream, outputStream);
- } catch (FileNotFoundException e) {
- setStatus(FileTransfer.Status.error);
- setError(Error.bad_file);
- setException(e);
- } catch (IOException e) {
- setStatus(FileTransfer.Status.error);
- setException(e);
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Closing input stream", e);
- }
- }
- try {
- outputStream.close();
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Closing output stream", e);
- }
- }
- updateStatus(Status.in_progress, FileTransfer.Status.complete);
- }
- }, "File Transfer " + streamID);
- transferThread.start();
- }
- /**
- * This method handles the stream negotiation process and transmits the file
- * to the remote user. It returns immediately and the progress of the file
- * transfer can be monitored through several methods:
- *
- * <UL>
- * <LI>{@link FileTransfer#getStatus()}
- * <LI>{@link FileTransfer#getProgress()}
- * <LI>{@link FileTransfer#isDone()}
- * </UL>
- *
- * @param in the stream to transfer to the remote entity.
- * @param fileName the name of the file that is transferred
- * @param fileSize the size of the file that is transferred
- * @param description a description for the file to transfer.
- */
- public synchronized void sendStream(final InputStream in, final String fileName, final long fileSize, final String description){
- checkTransferThread();
- setFileInfo(fileName, fileSize);
- transferThread = new Thread(new Runnable() {
- public void run() {
- //Create packet filter
- try {
- outputStream = negotiateStream(fileName, fileSize, description);
- } catch (XMPPErrorException e) {
- handleXMPPException(e);
- return;
- }
- catch (Exception e) {
- setException(e);
- }
- if (outputStream == null) {
- return;
- }
- if (!updateStatus(Status.negotiated, Status.in_progress)) {
- return;
- }
- try {
- writeToStream(in, outputStream);
- } catch (IOException e) {
- setStatus(FileTransfer.Status.error);
- setException(e);
- } finally {
- try {
- if (in != null) {
- in.close();
- }
- outputStream.flush();
- outputStream.close();
- } catch (IOException e) {
- /* Do Nothing */
- }
- }
- updateStatus(Status.in_progress, FileTransfer.Status.complete);
- }
- }, "File Transfer " + streamID);
- transferThread.start();
- }
- private void handleXMPPException(XMPPErrorException e) {
- XMPPError error = e.getXMPPError();
- if (error != null) {
- switch (error.getCondition()) {
- case forbidden:
- setStatus(Status.refused);
- return;
- case bad_request:
- setStatus(Status.error);
- setError(Error.not_acceptable);
- break;
- default:
- setStatus(FileTransfer.Status.error);
- }
- }
- setException(e);
- }
- /**
- * Returns the amount of bytes that have been sent for the file transfer. Or
- * -1 if the file transfer has not started.
- * <p>
- * Note: This method is only useful when the {@link #sendFile(File, String)}
- * method is called, as it is the only method that actually transmits the
- * file.
- *
- * @return Returns the amount of bytes that have been sent for the file
- * transfer. Or -1 if the file transfer has not started.
- */
- public long getBytesSent() {
- return amountWritten;
- }
- private OutputStream negotiateStream(String fileName, long fileSize,
- String description) throws SmackException, XMPPException, InterruptedException {
- // Negotiate the file transfer profile
- if (!updateStatus(Status.initial, Status.negotiating_transfer)) {
- throw new IllegalStateChangeException();
- }
- StreamNegotiator streamNegotiator = negotiator.negotiateOutgoingTransfer(
- getPeer(), streamID, fileName, fileSize, description,
- RESPONSE_TIMEOUT);
- // Negotiate the stream
- if (!updateStatus(Status.negotiating_transfer, Status.negotiating_stream)) {
- throw new IllegalStateChangeException();
- }
- outputStream = streamNegotiator.createOutgoingStream(streamID,
- initiator, getPeer());
- if (!updateStatus(Status.negotiating_stream, Status.negotiated)) {
- throw new IllegalStateChangeException();
- }
- return outputStream;
- }
- public void cancel() {
- setStatus(Status.cancelled);
- }
- @Override
- protected boolean updateStatus(Status oldStatus, Status newStatus) {
- boolean isUpdated = super.updateStatus(oldStatus, newStatus);
- if(callback != null && isUpdated) {
- callback.statusUpdated(oldStatus, newStatus);
- }
- return isUpdated;
- }
- @Override
- protected void setStatus(Status status) {
- Status oldStatus = getStatus();
- super.setStatus(status);
- if(callback != null) {
- callback.statusUpdated(oldStatus, status);
- }
- }
- @Override
- protected void setException(Exception exception) {
- super.setException(exception);
- if(callback != null) {
- callback.errorEstablishingStream(exception);
- }
- }
- /**
- * A callback class to retrieve the status of an outgoing transfer
- * negotiation process.
- *
- * @author Alexander Wenckus
- *
- */
- public interface NegotiationProgress {
- /**
- * Called when the status changes
- *
- * @param oldStatus the previous status of the file transfer.
- * @param newStatus the new status of the file transfer.
- */
- void statusUpdated(Status oldStatus, Status newStatus);
- /**
- * Once the negotiation process is completed the output stream can be
- * retrieved.
- *
- * @param stream the established stream which can be used to transfer the file to the remote
- * entity
- */
- void outputStreamEstablished(OutputStream stream);
- /**
- * Called when an exception occurs during the negotiation progress.
- *
- * @param e the exception that occurred.
- */
- void errorEstablishingStream(Exception e);
- }
- }