blob: bba6c386551cd492ba26fd7df9ade26e95bb403e [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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 */
20package org.jivesoftware.smackx.filetransfer;
21
22import org.jivesoftware.smack.XMPPException;
23import org.jivesoftware.smack.packet.XMPPError;
24
25import 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 */
35public 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 */
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 */
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
69 protected OutgoingFileTransfer(String initiator, String target,
70 String streamID, FileTransferNegotiator transferNegotiator) {
71 super(target, streamID, transferNegotiator);
72 this.initiator = initiator;
73 }
74
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 */
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 */
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 */
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
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 */
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 */
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
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 */
358 public long getBytesSent() {
359 return amountWritten;
360 }
361
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
392 public void cancel() {
393 setStatus(Status.cancelled);
394 }
395
396 @Override
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
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
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 */
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 */
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 */
453 void errorEstablishingStream(Exception e);
454 }
455
456}