• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * Copyright 2003-2006 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 package org.jivesoftware.smackx.filetransfer;
21 
22 import org.jivesoftware.smack.XMPPException;
23 import org.jivesoftware.smack.packet.XMPPError;
24 
25 import java.io.*;
26 
27 /**
28  * Handles the sending of a file to another user. File transfer's in jabber have
29  * several steps and there are several methods in this class that handle these
30  * steps differently.
31  *
32  * @author Alexander Wenckus
33  *
34  */
35 public class OutgoingFileTransfer extends FileTransfer {
36 
37 	private static int RESPONSE_TIMEOUT = 60 * 1000;
38     private NegotiationProgress callback;
39 
40     /**
41      * Returns the time in milliseconds after which the file transfer
42      * negotiation process will timeout if the other user has not responded.
43      *
44      * @return Returns the time in milliseconds after which the file transfer
45      *         negotiation process will timeout if the remote user has not
46      *         responded.
47      */
getResponseTimeout()48     public static int getResponseTimeout() {
49         return RESPONSE_TIMEOUT;
50     }
51 
52 	/**
53 	 * Sets the time in milliseconds after which the file transfer negotiation
54 	 * process will timeout if the other user has not responded.
55 	 *
56 	 * @param responseTimeout
57 	 *            The timeout time in milliseconds.
58 	 */
setResponseTimeout(int responseTimeout)59 	public static void setResponseTimeout(int responseTimeout) {
60 		RESPONSE_TIMEOUT = responseTimeout;
61 	}
62 
63 	private OutputStream outputStream;
64 
65 	private String initiator;
66 
67 	private Thread transferThread;
68 
OutgoingFileTransfer(String initiator, String target, String streamID, FileTransferNegotiator transferNegotiator)69 	protected OutgoingFileTransfer(String initiator, String target,
70 			String streamID, FileTransferNegotiator transferNegotiator) {
71 		super(target, streamID, transferNegotiator);
72 		this.initiator = initiator;
73 	}
74 
setOutputStream(OutputStream stream)75 	protected void setOutputStream(OutputStream stream) {
76 		if (outputStream == null) {
77 			this.outputStream = stream;
78 		}
79 	}
80 
81 	/**
82 	 * Returns the output stream connected to the peer to transfer the file. It
83 	 * is only available after it has been successfully negotiated by the
84 	 * {@link StreamNegotiator}.
85 	 *
86 	 * @return Returns the output stream connected to the peer to transfer the
87 	 *         file.
88 	 */
getOutputStream()89 	protected OutputStream getOutputStream() {
90 		if (getStatus().equals(FileTransfer.Status.negotiated)) {
91 			return outputStream;
92 		} else {
93 			return null;
94 		}
95 	}
96 
97 	/**
98 	 * This method handles the negotiation of the file transfer and the stream,
99 	 * it only returns the created stream after the negotiation has been completed.
100 	 *
101 	 * @param fileName
102 	 *            The name of the file that will be transmitted. It is
103 	 *            preferable for this name to have an extension as it will be
104 	 *            used to determine the type of file it is.
105 	 * @param fileSize
106 	 *            The size in bytes of the file that will be transmitted.
107 	 * @param description
108 	 *            A description of the file that will be transmitted.
109 	 * @return The OutputStream that is connected to the peer to transmit the
110 	 *         file.
111 	 * @throws XMPPException
112 	 *             Thrown if an error occurs during the file transfer
113 	 *             negotiation process.
114 	 */
sendFile(String fileName, long fileSize, String description)115 	public synchronized OutputStream sendFile(String fileName, long fileSize,
116 			String description) throws XMPPException {
117 		if (isDone() || outputStream != null) {
118 			throw new IllegalStateException(
119 					"The negotation process has already"
120 							+ " been attempted on this file transfer");
121 		}
122 		try {
123 			setFileInfo(fileName, fileSize);
124 			this.outputStream = negotiateStream(fileName, fileSize, description);
125 		} catch (XMPPException e) {
126 			handleXMPPException(e);
127 			throw e;
128 		}
129 		return outputStream;
130 	}
131 
132 	/**
133 	 * This methods handles the transfer and stream negotiation process. It
134 	 * returns immediately and its progress will be updated through the
135 	 * {@link NegotiationProgress} callback.
136 	 *
137 	 * @param fileName
138 	 *            The name of the file that will be transmitted. It is
139 	 *            preferable for this name to have an extension as it will be
140 	 *            used to determine the type of file it is.
141 	 * @param fileSize
142 	 *            The size in bytes of the file that will be transmitted.
143 	 * @param description
144 	 *            A description of the file that will be transmitted.
145 	 * @param progress
146 	 *            A callback to monitor the progress of the file transfer
147 	 *            negotiation process and to retrieve the OutputStream when it
148 	 *            is complete.
149 	 */
sendFile(final String fileName, final long fileSize, final String description, final NegotiationProgress progress)150 	public synchronized void sendFile(final String fileName,
151 			final long fileSize, final String description,
152 			final NegotiationProgress progress)
153     {
154         if(progress == null) {
155             throw new IllegalArgumentException("Callback progress cannot be null.");
156         }
157         checkTransferThread();
158 		if (isDone() || outputStream != null) {
159 			throw new IllegalStateException(
160 					"The negotation process has already"
161 							+ " been attempted for this file transfer");
162 		}
163         setFileInfo(fileName, fileSize);
164         this.callback = progress;
165         transferThread = new Thread(new Runnable() {
166 			public void run() {
167 				try {
168 					OutgoingFileTransfer.this.outputStream = negotiateStream(
169 							fileName, fileSize, description);
170                     progress.outputStreamEstablished(OutgoingFileTransfer.this.outputStream);
171                 }
172                 catch (XMPPException e) {
173 					handleXMPPException(e);
174 				}
175 			}
176 		}, "File Transfer Negotiation " + streamID);
177 		transferThread.start();
178 	}
179 
checkTransferThread()180 	private void checkTransferThread() {
181 		if (transferThread != null && transferThread.isAlive() || isDone()) {
182 			throw new IllegalStateException(
183 					"File transfer in progress or has already completed.");
184 		}
185 	}
186 
187     /**
188 	 * This method handles the stream negotiation process and transmits the file
189 	 * to the remote user. It returns immediately and the progress of the file
190 	 * transfer can be monitored through several methods:
191 	 *
192 	 * <UL>
193 	 * <LI>{@link FileTransfer#getStatus()}
194 	 * <LI>{@link FileTransfer#getProgress()}
195 	 * <LI>{@link FileTransfer#isDone()}
196 	 * </UL>
197 	 *
198      * @param file the file to transfer to the remote entity.
199      * @param description a description for the file to transfer.
200 	 * @throws XMPPException
201 	 *             If there is an error during the negotiation process or the
202 	 *             sending of the file.
203 	 */
sendFile(final File file, final String description)204 	public synchronized void sendFile(final File file, final String description)
205 			throws XMPPException {
206 		checkTransferThread();
207 		if (file == null || !file.exists() || !file.canRead()) {
208 			throw new IllegalArgumentException("Could not read file");
209 		} else {
210 			setFileInfo(file.getAbsolutePath(), file.getName(), file.length());
211 		}
212 
213 		transferThread = new Thread(new Runnable() {
214 			public void run() {
215 				try {
216 					outputStream = negotiateStream(file.getName(), file
217 							.length(), description);
218 				} catch (XMPPException e) {
219 					handleXMPPException(e);
220 					return;
221 				}
222 				if (outputStream == null) {
223 					return;
224 				}
225 
226                 if (!updateStatus(Status.negotiated, Status.in_progress)) {
227 					return;
228 				}
229 
230 				InputStream inputStream = null;
231 				try {
232 					inputStream = new FileInputStream(file);
233 					writeToStream(inputStream, outputStream);
234 				} catch (FileNotFoundException e) {
235 					setStatus(FileTransfer.Status.error);
236 					setError(Error.bad_file);
237 					setException(e);
238 				} catch (XMPPException e) {
239 					setStatus(FileTransfer.Status.error);
240 					setException(e);
241 				} finally {
242 					try {
243 						if (inputStream != null) {
244 							inputStream.close();
245 						}
246 
247 						outputStream.flush();
248 						outputStream.close();
249 					} catch (IOException e) {
250                         /* Do Nothing */
251 					}
252 				}
253                 updateStatus(Status.in_progress, FileTransfer.Status.complete);
254 				}
255 
256 		}, "File Transfer " + streamID);
257 		transferThread.start();
258 	}
259 
260     /**
261 	 * This method handles the stream negotiation process and transmits the file
262 	 * to the remote user. It returns immediately and the progress of the file
263 	 * transfer can be monitored through several methods:
264 	 *
265 	 * <UL>
266 	 * <LI>{@link FileTransfer#getStatus()}
267 	 * <LI>{@link FileTransfer#getProgress()}
268 	 * <LI>{@link FileTransfer#isDone()}
269 	 * </UL>
270 	 *
271      * @param in the stream to transfer to the remote entity.
272      * @param fileName the name of the file that is transferred
273      * @param fileSize the size of the file that is transferred
274      * @param description a description for the file to transfer.
275 	 */
sendStream(final InputStream in, final String fileName, final long fileSize, final String description)276 	public synchronized void sendStream(final InputStream in, final String fileName, final long fileSize, final String description){
277 		checkTransferThread();
278 
279 		setFileInfo(fileName, fileSize);
280 		transferThread = new Thread(new Runnable() {
281 			public void run() {
282                 setFileInfo(fileName, fileSize);
283                 //Create packet filter
284                 try {
285 					outputStream = negotiateStream(fileName, fileSize, description);
286 				} catch (XMPPException e) {
287 					handleXMPPException(e);
288 					return;
289 				} catch (IllegalStateException e) {
290 					setStatus(FileTransfer.Status.error);
291 					setException(e);
292 				}
293 				if (outputStream == null) {
294 					return;
295 				}
296 
297                 if (!updateStatus(Status.negotiated, Status.in_progress)) {
298 					return;
299 				}
300 				try {
301 					writeToStream(in, outputStream);
302 				} catch (XMPPException e) {
303 					setStatus(FileTransfer.Status.error);
304 					setException(e);
305 				} catch (IllegalStateException e) {
306 					setStatus(FileTransfer.Status.error);
307 					setException(e);
308 				} finally {
309 					try {
310 						if (in != null) {
311 							in.close();
312 						}
313 
314 						outputStream.flush();
315 						outputStream.close();
316 					} catch (IOException e) {
317                         /* Do Nothing */
318 					}
319 				}
320                 updateStatus(Status.in_progress, FileTransfer.Status.complete);
321 				}
322 
323 		}, "File Transfer " + streamID);
324 		transferThread.start();
325 	}
326 
handleXMPPException(XMPPException e)327 	private void handleXMPPException(XMPPException e) {
328 		XMPPError error = e.getXMPPError();
329 		if (error != null) {
330 			int code = error.getCode();
331 			if (code == 403) {
332 				setStatus(Status.refused);
333 				return;
334 			}
335             else if (code == 400) {
336 				setStatus(Status.error);
337 				setError(Error.not_acceptable);
338             }
339             else {
340                 setStatus(FileTransfer.Status.error);
341             }
342         }
343 
344         setException(e);
345 	}
346 
347 	/**
348 	 * Returns the amount of bytes that have been sent for the file transfer. Or
349 	 * -1 if the file transfer has not started.
350 	 * <p>
351 	 * Note: This method is only useful when the {@link #sendFile(File, String)}
352 	 * method is called, as it is the only method that actually transmits the
353 	 * file.
354 	 *
355 	 * @return Returns the amount of bytes that have been sent for the file
356 	 *         transfer. Or -1 if the file transfer has not started.
357 	 */
getBytesSent()358 	public long getBytesSent() {
359 		return amountWritten;
360 	}
361 
negotiateStream(String fileName, long fileSize, String description)362 	private OutputStream negotiateStream(String fileName, long fileSize,
363 			String description) throws XMPPException {
364 		// Negotiate the file transfer profile
365 
366         if (!updateStatus(Status.initial, Status.negotiating_transfer)) {
367             throw new XMPPException("Illegal state change");
368         }
369 		StreamNegotiator streamNegotiator = negotiator.negotiateOutgoingTransfer(
370 				getPeer(), streamID, fileName, fileSize, description,
371 				RESPONSE_TIMEOUT);
372 
373 		if (streamNegotiator == null) {
374 			setStatus(Status.error);
375 			setError(Error.no_response);
376 			return null;
377 		}
378 
379         // Negotiate the stream
380         if (!updateStatus(Status.negotiating_transfer, Status.negotiating_stream)) {
381             throw new XMPPException("Illegal state change");
382         }
383 		outputStream = streamNegotiator.createOutgoingStream(streamID,
384                 initiator, getPeer());
385 
386         if (!updateStatus(Status.negotiating_stream, Status.negotiated)) {
387             throw new XMPPException("Illegal state change");
388 		}
389 		return outputStream;
390 	}
391 
cancel()392 	public void cancel() {
393 		setStatus(Status.cancelled);
394 	}
395 
396     @Override
updateStatus(Status oldStatus, Status newStatus)397     protected boolean updateStatus(Status oldStatus, Status newStatus) {
398         boolean isUpdated = super.updateStatus(oldStatus, newStatus);
399         if(callback != null && isUpdated) {
400             callback.statusUpdated(oldStatus, newStatus);
401         }
402         return isUpdated;
403     }
404 
405     @Override
setStatus(Status status)406     protected void setStatus(Status status) {
407         Status oldStatus = getStatus();
408         super.setStatus(status);
409         if(callback != null) {
410             callback.statusUpdated(oldStatus, status);
411         }
412     }
413 
414     @Override
setException(Exception exception)415     protected void setException(Exception exception) {
416         super.setException(exception);
417         if(callback != null) {
418             callback.errorEstablishingStream(exception);
419         }
420     }
421 
422     /**
423 	 * A callback class to retrieve the status of an outgoing transfer
424 	 * negotiation process.
425 	 *
426 	 * @author Alexander Wenckus
427 	 *
428 	 */
429 	public interface NegotiationProgress {
430 
431 		/**
432 		 * Called when the status changes
433          *
434          * @param oldStatus the previous status of the file transfer.
435          * @param newStatus the new status of the file transfer.
436          */
statusUpdated(Status oldStatus, Status newStatus)437 		void statusUpdated(Status oldStatus, Status newStatus);
438 
439 		/**
440 		 * Once the negotiation process is completed the output stream can be
441 		 * retrieved.
442          *
443          * @param stream the established stream which can be used to transfer the file to the remote
444          * entity
445 		 */
outputStreamEstablished(OutputStream stream)446 		void outputStreamEstablished(OutputStream stream);
447 
448         /**
449          * Called when an exception occurs during the negotiation progress.
450          *
451          * @param e the exception that occurred.
452          */
errorEstablishingStream(Exception e)453         void errorEstablishingStream(Exception e);
454     }
455 
456 }
457