[FIX] SMACK-263: Set file info in all send* methods
[Smack.git] / source / org / jivesoftware / smackx / filetransfer / OutgoingFileTransfer.java
blob74418c2ad3523cd8673faa339eeb6f16c8c2b74e
1 /**
2 * $RCSfile$
3 * $Revision: $
4 * $Date: $
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;
25 import java.io.*;
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.
32 * @author Alexander Wenckus
35 public class OutgoingFileTransfer extends FileTransfer {
37 private static int RESPONSE_TIMEOUT = 60 * 1000;
38 private NegotiationProgress callback;
40 /**
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
46 * responded.
48 public static int getResponseTimeout() {
49 return RESPONSE_TIMEOUT;
52 /**
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;
81 /**
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
87 * file.
89 protected OutputStream getOutputStream() {
90 if (getStatus().equals(FileTransfer.Status.negotiated)) {
91 return outputStream;
92 } else {
93 return null;
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.
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.
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");
122 try {
123 setFileInfo(fileName, fileSize);
124 this.outputStream = negotiateStream(fileName, fileSize, description);
125 } catch (XMPPException e) {
126 handleXMPPException(e);
127 throw e;
129 return outputStream;
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.
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.
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() {
166 public void run() {
167 try {
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:
192 * <UL>
193 * <LI>{@link FileTransfer#getStatus()}
194 * <LI>{@link FileTransfer#getProgress()}
195 * <LI>{@link FileTransfer#isDone()}
196 * </UL>
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");
209 } else {
210 setFileInfo(file.getAbsolutePath(), file.getName(), file.length());
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;
222 if (outputStream == null) {
223 return;
226 if (!updateStatus(Status.negotiated, Status.in_progress)) {
227 return;
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();
247 outputStream.flush();
248 outputStream.close();
249 } catch (IOException e) {
250 /* Do Nothing */
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:
265 * <UL>
266 * <LI>{@link FileTransfer#getStatus()}
267 * <LI>{@link FileTransfer#getProgress()}
268 * <LI>{@link FileTransfer#isDone()}
269 * </UL>
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() {
281 public void run() {
282 //Create packet filter
283 try {
284 outputStream = negotiateStream(fileName, fileSize, description);
285 } catch (XMPPException e) {
286 handleXMPPException(e);
287 return;
289 if (outputStream == null) {
290 return;
293 if (!updateStatus(Status.negotiated, Status.in_progress)) {
294 return;
296 try {
297 writeToStream(in, outputStream);
298 } catch (XMPPException e) {
299 setStatus(FileTransfer.Status.error);
300 setException(e);
301 } finally {
302 try {
303 if (in != null) {
304 in.close();
307 outputStream.flush();
308 outputStream.close();
309 } catch (IOException e) {
310 /* Do Nothing */
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();
322 if (error != null) {
323 int code = error.getCode();
324 if (code == 403) {
325 setStatus(Status.refused);
326 return;
328 else if (code == 400) {
329 setStatus(Status.error);
330 setError(Error.not_acceptable);
332 else {
333 setStatus(FileTransfer.Status.error);
337 setException(e);
341 * Returns the amount of bytes that have been sent for the file transfer. Or
342 * -1 if the file transfer has not started.
343 * <p>
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
346 * file.
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,
364 RESPONSE_TIMEOUT);
366 if (streamNegotiator == null) {
367 setStatus(Status.error);
368 setError(Error.no_response);
369 return null;
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");
382 return outputStream;
385 public void cancel() {
386 setStatus(Status.cancelled);
389 @Override
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);
395 return isUpdated;
398 @Override
399 protected void setStatus(Status status) {
400 Status oldStatus = getStatus();
401 super.setStatus(status);
402 if(callback != null) {
403 callback.statusUpdated(oldStatus, status);
407 @Override
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
434 * retrieved.
436 * @param stream the established stream which can be used to transfer the file to the remote
437 * entity
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);