6 * Copyright 2003-2006 Jive Software.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 package org
.jivesoftware
.smackx
.filetransfer
;
22 import org
.jivesoftware
.smack
.XMPPException
;
23 import org
.jivesoftware
.smack
.packet
.XMPPError
;
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
32 * @author Alexander Wenckus
35 public class OutgoingFileTransfer
extends FileTransfer
{
37 private static int RESPONSE_TIMEOUT
= 60 * 1000;
38 private NegotiationProgress callback
;
41 * Returns the time in milliseconds after which the file transfer
42 * negotiation process will timeout if the other user has not responded.
44 * @return Returns the time in milliseconds after which the file transfer
45 * negotiation process will timeout if the remote user has not
48 public static int getResponseTimeout() {
49 return RESPONSE_TIMEOUT
;
53 * Sets the time in milliseconds after which the file transfer negotiation
54 * process will timeout if the other user has not responded.
56 * @param responseTimeout
57 * The timeout time in milliseconds.
59 public static void setResponseTimeout(int responseTimeout
) {
60 RESPONSE_TIMEOUT
= responseTimeout
;
63 private OutputStream outputStream
;
65 private String initiator
;
67 private Thread transferThread
;
69 protected OutgoingFileTransfer(String initiator
, String target
,
70 String streamID
, FileTransferNegotiator transferNegotiator
) {
71 super(target
, streamID
, transferNegotiator
);
72 this.initiator
= initiator
;
75 protected void setOutputStream(OutputStream stream
) {
76 if (outputStream
== null) {
77 this.outputStream
= stream
;
82 * Returns the output stream connected to the peer to transfer the file. It
83 * is only available after it has been succesfully negotiated by the
84 * {@link StreamNegotiator}.
86 * @return Returns the output stream connected to the peer to transfer the
89 protected OutputStream
getOutputStream() {
90 if (getStatus().equals(FileTransfer
.Status
.negotiated
)) {
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.
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.
106 * The size in bytes of the file that will be transmitted.
108 * A description of the file that will be transmitted.
109 * @return The OutputStream that is connected to the peer to transmit the
111 * @throws XMPPException
112 * Thrown if an error occurs during the file transfer
113 * negotiation process.
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");
123 setFileInfo(fileName
, fileSize
);
124 this.outputStream
= negotiateStream(fileName
, fileSize
, description
);
125 } catch (XMPPException e
) {
126 handleXMPPException(e
);
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.
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.
142 * The size in bytes of the file that will be transmitted.
144 * A description of the file that will be transmitted.
146 * A callback to monitor the progress of the file transfer
147 * negotiation process and to retrieve the OutputStream when it
150 public synchronized void sendFile(final String fileName
,
151 final long fileSize
, final String description
,
152 final NegotiationProgress progress
)
154 if(progress
== null) {
155 throw new IllegalArgumentException("Callback progress cannot be null.");
157 checkTransferThread();
158 if (isDone() || outputStream
!= null) {
159 throw new IllegalStateException(
160 "The negotation process has already"
161 + " been attempted for this file transfer");
163 setFileInfo(fileName
, fileSize
);
164 this.callback
= progress
;
165 transferThread
= new Thread(new Runnable() {
168 OutgoingFileTransfer
.this.outputStream
= negotiateStream(
169 fileName
, fileSize
, description
);
170 progress
.outputStreamEstablished(OutgoingFileTransfer
.this.outputStream
);
172 catch (XMPPException e
) {
173 handleXMPPException(e
);
176 }, "File Transfer Negotiation " + streamID
);
177 transferThread
.start();
180 private void checkTransferThread() {
181 if (transferThread
!= null && transferThread
.isAlive() || isDone()) {
182 throw new IllegalStateException(
183 "File transfer in progress or has already completed.");
188 * This method handles the stream negotiation process and transmits the file
189 * to the remote user. It returns immediatly and the progress of the file
190 * transfer can be monitored through several methods:
193 * <LI>{@link FileTransfer#getStatus()}
194 * <LI>{@link FileTransfer#getProgress()}
195 * <LI>{@link FileTransfer#isDone()}
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.
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");
210 setFileInfo(file
.getAbsolutePath(), file
.getName(), file
.length());
213 transferThread
= new Thread(new Runnable() {
216 outputStream
= negotiateStream(file
.getName(), file
217 .length(), description
);
218 } catch (XMPPException e
) {
219 handleXMPPException(e
);
222 if (outputStream
== null) {
226 if (!updateStatus(Status
.negotiated
, Status
.in_progress
)) {
230 InputStream inputStream
= null;
232 inputStream
= new FileInputStream(file
);
233 writeToStream(inputStream
, outputStream
);
234 } catch (FileNotFoundException e
) {
235 setStatus(FileTransfer
.Status
.error
);
236 setError(Error
.bad_file
);
238 } catch (XMPPException e
) {
239 setStatus(FileTransfer
.Status
.error
);
243 if (inputStream
!= null) {
247 outputStream
.flush();
248 outputStream
.close();
249 } catch (IOException e
) {
253 updateStatus(Status
.in_progress
, FileTransfer
.Status
.complete
);
256 }, "File Transfer " + streamID
);
257 transferThread
.start();
261 * This method handles the stream negotiation process and transmits the file
262 * to the remote user. It returns immediatly and the progress of the file
263 * transfer can be monitored through several methods:
266 * <LI>{@link FileTransfer#getStatus()}
267 * <LI>{@link FileTransfer#getProgress()}
268 * <LI>{@link FileTransfer#isDone()}
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.
276 public synchronized void sendStream(final InputStream in
, final String fileName
, final long fileSize
, final String description
){
277 checkTransferThread();
279 setFileInfo(fileName
, fileSize
);
280 transferThread
= new Thread(new Runnable() {
282 //Create packet filter
284 outputStream
= negotiateStream(fileName
, fileSize
, description
);
285 } catch (XMPPException e
) {
286 handleXMPPException(e
);
289 if (outputStream
== null) {
293 if (!updateStatus(Status
.negotiated
, Status
.in_progress
)) {
297 writeToStream(in
, outputStream
);
298 } catch (XMPPException e
) {
299 setStatus(FileTransfer
.Status
.error
);
307 outputStream
.flush();
308 outputStream
.close();
309 } catch (IOException e
) {
313 updateStatus(Status
.in_progress
, FileTransfer
.Status
.complete
);
316 }, "File Transfer " + streamID
);
317 transferThread
.start();
320 private void handleXMPPException(XMPPException e
) {
321 XMPPError error
= e
.getXMPPError();
323 int code
= error
.getCode();
325 setStatus(Status
.refused
);
328 else if (code
== 400) {
329 setStatus(Status
.error
);
330 setError(Error
.not_acceptable
);
333 setStatus(FileTransfer
.Status
.error
);
341 * Returns the amount of bytes that have been sent for the file transfer. Or
342 * -1 if the file transfer has not started.
344 * Note: This method is only useful when the {@link #sendFile(File, String)}
345 * method is called, as it is the only method that actualy transmits the
348 * @return Returns the amount of bytes that have been sent for the file
349 * transfer. Or -1 if the file transfer has not started.
351 public long getBytesSent() {
352 return amountWritten
;
355 private OutputStream
negotiateStream(String fileName
, long fileSize
,
356 String description
) throws XMPPException
{
357 // Negotiate the file transfer profile
359 if (!updateStatus(Status
.initial
, Status
.negotiating_transfer
)) {
360 throw new XMPPException("Illegal state change");
362 StreamNegotiator streamNegotiator
= negotiator
.negotiateOutgoingTransfer(
363 getPeer(), streamID
, fileName
, fileSize
, description
,
366 if (streamNegotiator
== null) {
367 setStatus(Status
.error
);
368 setError(Error
.no_response
);
372 // Negotiate the stream
373 if (!updateStatus(Status
.negotiating_transfer
, Status
.negotiating_stream
)) {
374 throw new XMPPException("Illegal state change");
376 outputStream
= streamNegotiator
.createOutgoingStream(streamID
,
377 initiator
, getPeer());
379 if (!updateStatus(Status
.negotiating_stream
, Status
.negotiated
)) {
380 throw new XMPPException("Illegal state change");
385 public void cancel() {
386 setStatus(Status
.cancelled
);
390 protected boolean updateStatus(Status oldStatus
, Status newStatus
) {
391 boolean isUpdated
= super.updateStatus(oldStatus
, newStatus
);
392 if(callback
!= null && isUpdated
) {
393 callback
.statusUpdated(oldStatus
, newStatus
);
399 protected void setStatus(Status status
) {
400 Status oldStatus
= getStatus();
401 super.setStatus(status
);
402 if(callback
!= null) {
403 callback
.statusUpdated(oldStatus
, status
);
408 protected void setException(Exception exception
) {
409 super.setException(exception
);
410 if(callback
!= null) {
411 callback
.errorEstablishingStream(exception
);
416 * A callback class to retrive the status of an outgoing transfer
417 * negotiation process.
419 * @author Alexander Wenckus
422 public interface NegotiationProgress
{
425 * Called when the status changes
427 * @param oldStatus the previous status of the file transfer.
428 * @param newStatus the new status of the file transfer.
430 void statusUpdated(Status oldStatus
, Status newStatus
);
433 * Once the negotiation process is completed the output stream can be
436 * @param stream the established stream which can be used to transfer the file to the remote
439 void outputStreamEstablished(OutputStream stream
);
442 * Called when an exception occurs during the negotiation progress.
444 * @param e the exception that occured.
446 void errorEstablishingStream(Exception e
);