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 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 27 28 /** 29 * Contains the generic file information and progress related to a particular 30 * file transfer. 31 * 32 * @author Alexander Wenckus 33 * 34 */ 35 public abstract class FileTransfer { 36 37 private String fileName; 38 39 private String filePath; 40 41 private long fileSize; 42 43 private String peer; 44 45 private Status status = Status.initial; 46 47 private final Object statusMonitor = new Object(); 48 49 protected FileTransferNegotiator negotiator; 50 51 protected String streamID; 52 53 protected long amountWritten = -1; 54 55 private Error error; 56 57 private Exception exception; 58 59 /** 60 * Buffer size between input and output 61 */ 62 private static final int BUFFER_SIZE = 8192; 63 64 protected FileTransfer(String peer, String streamID, 65 FileTransferNegotiator negotiator) { 66 this.peer = peer; 67 this.streamID = streamID; 68 this.negotiator = negotiator; 69 } 70 71 protected void setFileInfo(String fileName, long fileSize) { 72 this.fileName = fileName; 73 this.fileSize = fileSize; 74 } 75 76 protected void setFileInfo(String path, String fileName, long fileSize) { 77 this.filePath = path; 78 this.fileName = fileName; 79 this.fileSize = fileSize; 80 } 81 82 /** 83 * Returns the size of the file being transfered. 84 * 85 * @return Returns the size of the file being transfered. 86 */ 87 public long getFileSize() { 88 return fileSize; 89 } 90 91 /** 92 * Returns the name of the file being transfered. 93 * 94 * @return Returns the name of the file being transfered. 95 */ 96 public String getFileName() { 97 return fileName; 98 } 99 100 /** 101 * Returns the local path of the file. 102 * 103 * @return Returns the local path of the file. 104 */ 105 public String getFilePath() { 106 return filePath; 107 } 108 109 /** 110 * Returns the JID of the peer for this file transfer. 111 * 112 * @return Returns the JID of the peer for this file transfer. 113 */ 114 public String getPeer() { 115 return peer; 116 } 117 118 /** 119 * Returns the progress of the file transfer as a number between 0 and 1. 120 * 121 * @return Returns the progress of the file transfer as a number between 0 122 * and 1. 123 */ 124 public double getProgress() { 125 if (amountWritten <= 0 || fileSize <= 0) { 126 return 0; 127 } 128 return (double) amountWritten / (double) fileSize; 129 } 130 131 /** 132 * Returns true if the transfer has been cancelled, if it has stopped because 133 * of a an error, or the transfer completed successfully. 134 * 135 * @return Returns true if the transfer has been cancelled, if it has stopped 136 * because of a an error, or the transfer completed successfully. 137 */ 138 public boolean isDone() { 139 return status == Status.cancelled || status == Status.error 140 || status == Status.complete || status == Status.refused; 141 } 142 143 /** 144 * Returns the current status of the file transfer. 145 * 146 * @return Returns the current status of the file transfer. 147 */ 148 public Status getStatus() { 149 return status; 150 } 151 152 protected void setError(Error type) { 153 this.error = type; 154 } 155 156 /** 157 * When {@link #getStatus()} returns that there was an {@link Status#error} 158 * during the transfer, the type of error can be retrieved through this 159 * method. 160 * 161 * @return Returns the type of error that occurred if one has occurred. 162 */ 163 public Error getError() { 164 return error; 165 } 166 167 /** 168 * If an exception occurs asynchronously it will be stored for later 169 * retrieval. If there is an error there maybe an exception set. 170 * 171 * @return The exception that occurred or null if there was no exception. 172 * @see #getError() 173 */ 174 public Exception getException() { 175 return exception; 176 } 177 178 public String getStreamID() { 179 return streamID; 180 } 181 182 /** 183 * Cancels the file transfer. 184 */ 185 public abstract void cancel(); 186 187 protected void setException(Exception exception) { 188 this.exception = exception; 189 } 190 191 protected void setStatus(Status status) { 192 synchronized (statusMonitor) { 193 this.status = status; 194 } 195 } 196 197 protected boolean updateStatus(Status oldStatus, Status newStatus) { 198 synchronized (statusMonitor) { 199 if (oldStatus != status) { 200 return false; 201 } 202 status = newStatus; 203 return true; 204 } 205 } 206 207 protected void writeToStream(final InputStream in, final OutputStream out) 208 throws XMPPException 209 { 210 final byte[] b = new byte[BUFFER_SIZE]; 211 int count = 0; 212 amountWritten = 0; 213 214 do { 215 // write to the output stream 216 try { 217 out.write(b, 0, count); 218 } catch (IOException e) { 219 throw new XMPPException("error writing to output stream", e); 220 } 221 222 amountWritten += count; 223 224 // read more bytes from the input stream 225 try { 226 count = in.read(b); 227 } catch (IOException e) { 228 throw new XMPPException("error reading from input stream", e); 229 } 230 } while (count != -1 && !getStatus().equals(Status.cancelled)); 231 232 // the connection was likely terminated abrubtly if these are not equal 233 if (!getStatus().equals(Status.cancelled) && getError() == Error.none 234 && amountWritten != fileSize) { 235 setStatus(Status.error); 236 this.error = Error.connection; 237 } 238 } 239 240 /** 241 * A class to represent the current status of the file transfer. 242 * 243 * @author Alexander Wenckus 244 * 245 */ 246 public enum Status { 247 248 /** 249 * An error occurred during the transfer. 250 * 251 * @see FileTransfer#getError() 252 */ 253 error("Error"), 254 255 /** 256 * The initial status of the file transfer. 257 */ 258 initial("Initial"), 259 260 /** 261 * The file transfer is being negotiated with the peer. The party 262 * Receiving the file has the option to accept or refuse a file transfer 263 * request. If they accept, then the process of stream negotiation will 264 * begin. If they refuse the file will not be transfered. 265 * 266 * @see #negotiating_stream 267 */ 268 negotiating_transfer("Negotiating Transfer"), 269 270 /** 271 * The peer has refused the file transfer request halting the file 272 * transfer negotiation process. 273 */ 274 refused("Refused"), 275 276 /** 277 * The stream to transfer the file is being negotiated over the chosen 278 * stream type. After the stream negotiating process is complete the 279 * status becomes negotiated. 280 * 281 * @see #negotiated 282 */ 283 negotiating_stream("Negotiating Stream"), 284 285 /** 286 * After the stream negotiation has completed the intermediate state 287 * between the time when the negotiation is finished and the actual 288 * transfer begins. 289 */ 290 negotiated("Negotiated"), 291 292 /** 293 * The transfer is in progress. 294 * 295 * @see FileTransfer#getProgress() 296 */ 297 in_progress("In Progress"), 298 299 /** 300 * The transfer has completed successfully. 301 */ 302 complete("Complete"), 303 304 /** 305 * The file transfer was cancelled 306 */ 307 cancelled("Cancelled"); 308 309 private String status; 310 311 private Status(String status) { 312 this.status = status; 313 } 314 315 public String toString() { 316 return status; 317 } 318 } 319 320 /** 321 * Return the length of bytes written out to the stream. 322 * @return the amount in bytes written out. 323 */ 324 public long getAmountWritten(){ 325 return amountWritten; 326 } 327 328 public enum Error { 329 /** 330 * No error 331 */ 332 none("No error"), 333 334 /** 335 * The peer did not find any of the provided stream mechanisms 336 * acceptable. 337 */ 338 not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."), 339 340 /** 341 * The provided file to transfer does not exist or could not be read. 342 */ 343 bad_file("The provided file to transfer does not exist or could not be read."), 344 345 /** 346 * The remote user did not respond or the connection timed out. 347 */ 348 no_response("The remote user did not respond or the connection timed out."), 349 350 /** 351 * An error occurred over the socket connected to send the file. 352 */ 353 connection("An error occured over the socket connected to send the file."), 354 355 /** 356 * An error occurred while sending or receiving the file 357 */ 358 stream("An error occured while sending or recieving the file."); 359 360 private final String msg; 361 362 private Error(String msg) { 363 this.msg = msg; 364 } 365 366 /** 367 * Returns a String representation of this error. 368 * 369 * @return Returns a String representation of this error. 370 */ 371 public String getMessage() { 372 return msg; 373 } 374 375 public String toString() { 376 return msg; 377 } 378 } 379 380 } 381