001/** 002 * 003 * Copyright 2003-2006 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.filetransfer; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022 023import org.jxmpp.jid.Jid; 024 025/** 026 * Contains the generic file information and progress related to a particular 027 * file transfer. 028 * 029 * @author Alexander Wenckus 030 * 031 */ 032public abstract class FileTransfer { 033 034 private String fileName; 035 036 private String filePath; 037 038 private long fileSize; 039 040 private Jid peer; 041 042 private Status status = Status.initial; 043 044 private final Object statusMonitor = new Object(); 045 046 protected FileTransferNegotiator negotiator; 047 048 protected String streamID; 049 050 protected long amountWritten = -1; 051 052 private Error error; 053 054 private Exception exception; 055 056 /** 057 * Buffer size between input and output 058 */ 059 private static final int BUFFER_SIZE = 8192; 060 061 protected FileTransfer(Jid peer, String streamID, 062 FileTransferNegotiator negotiator) { 063 this.peer = peer; 064 this.streamID = streamID; 065 this.negotiator = negotiator; 066 } 067 068 protected void setFileInfo(String fileName, long fileSize) { 069 this.fileName = fileName; 070 this.fileSize = fileSize; 071 } 072 073 protected void setFileInfo(String path, String fileName, long fileSize) { 074 this.filePath = path; 075 this.fileName = fileName; 076 this.fileSize = fileSize; 077 } 078 079 /** 080 * Returns the size of the file being transferred. 081 * 082 * @return Returns the size of the file being transferred. 083 */ 084 public long getFileSize() { 085 return fileSize; 086 } 087 088 /** 089 * Returns the name of the file being transferred. 090 * 091 * @return Returns the name of the file being transferred. 092 */ 093 public String getFileName() { 094 return fileName; 095 } 096 097 /** 098 * Returns the local path of the file. 099 * 100 * @return Returns the local path of the file. 101 */ 102 public String getFilePath() { 103 return filePath; 104 } 105 106 /** 107 * Returns the JID of the peer for this file transfer. 108 * 109 * @return Returns the JID of the peer for this file transfer. 110 */ 111 public Jid getPeer() { 112 return peer; 113 } 114 115 /** 116 * Returns the progress of the file transfer as a number between 0 and 1. 117 * 118 * @return Returns the progress of the file transfer as a number between 0 119 * and 1. 120 */ 121 public double getProgress() { 122 if (amountWritten <= 0 || fileSize <= 0) { 123 return 0; 124 } 125 return (double) amountWritten / (double) fileSize; 126 } 127 128 /** 129 * Returns true if the transfer has been cancelled, if it has stopped because 130 * of a an error, or the transfer completed successfully. 131 * 132 * @return Returns true if the transfer has been cancelled, if it has stopped 133 * because of a an error, or the transfer completed successfully. 134 */ 135 public boolean isDone() { 136 return status == Status.cancelled || status == Status.error 137 || status == Status.complete || status == Status.refused; 138 } 139 140 /** 141 * Returns the current status of the file transfer. 142 * 143 * @return Returns the current status of the file transfer. 144 */ 145 public Status getStatus() { 146 return status; 147 } 148 149 protected void setError(Error type) { 150 this.error = type; 151 } 152 153 /** 154 * When {@link #getStatus()} returns that there was an {@link Status#error} 155 * during the transfer, the type of error can be retrieved through this 156 * method. 157 * 158 * @return Returns the type of error that occurred if one has occurred. 159 */ 160 public Error getError() { 161 return error; 162 } 163 164 /** 165 * If an exception occurs asynchronously it will be stored for later 166 * retrieval. If there is an error there maybe an exception set. 167 * 168 * @return The exception that occurred or null if there was no exception. 169 * @see #getError() 170 */ 171 public Exception getException() { 172 return exception; 173 } 174 175 public String getStreamID() { 176 return streamID; 177 } 178 179 /** 180 * Cancels the file transfer. 181 */ 182 public abstract void cancel(); 183 184 protected void setException(Exception exception) { 185 this.exception = exception; 186 Status currentStatus = getStatus(); 187 if (currentStatus != Status.error) { 188 updateStatus(currentStatus, Status.error); 189 } 190 } 191 192 protected void setStatus(Status status) { 193 synchronized (statusMonitor) { 194 // CHECKSTYLE:OFF 195 this.status = status; 196 } 197 // CHECKSTYLE:ON 198 } 199 200 protected boolean updateStatus(Status oldStatus, Status newStatus) { 201 synchronized (statusMonitor) { 202 if (oldStatus != status) { 203 return false; 204 } 205 status = newStatus; 206 return true; 207 } 208 } 209 210 protected void writeToStream(final InputStream in, final OutputStream out) 211 throws IOException { 212 final byte[] b = new byte[BUFFER_SIZE]; 213 int count = 0; 214 amountWritten = 0; 215 216 while ((count = in.read(b)) > 0 && !getStatus().equals(Status.cancelled)) { 217 out.write(b, 0, count); 218 amountWritten += count; 219 } 220 221 // When the amount of data written does not equal the expected amount, and 222 // the transfer was not explicitly cancelled, register an error (unless another 223 // error has already been logged). 224 if (!getStatus().equals(Status.cancelled) && getError() == null 225 && amountWritten != fileSize) { 226 setStatus(Status.error); 227 this.error = Error.connection; 228 } 229 } 230 231 /** 232 * A class to represent the current status of the file transfer. 233 * 234 * @author Alexander Wenckus 235 * 236 */ 237 public enum Status { 238 239 /** 240 * An error occurred during the transfer. 241 * 242 * @see FileTransfer#getError() 243 */ 244 error("Error"), 245 246 /** 247 * The initial status of the file transfer. 248 */ 249 initial("Initial"), 250 251 /** 252 * The file transfer is being negotiated with the peer. The party 253 * Receiving the file has the option to accept or refuse a file transfer 254 * request. If they accept, then the process of stream negotiation will 255 * begin. If they refuse the file will not be transferred. 256 * 257 * @see #negotiating_stream 258 */ 259 negotiating_transfer("Negotiating Transfer"), 260 261 /** 262 * The peer has refused the file transfer request halting the file 263 * transfer negotiation process. 264 */ 265 refused("Refused"), 266 267 /** 268 * The stream to transfer the file is being negotiated over the chosen 269 * stream type. After the stream negotiating process is complete the 270 * status becomes negotiated. 271 * 272 * @see #negotiated 273 */ 274 negotiating_stream("Negotiating Stream"), 275 276 /** 277 * After the stream negotiation has completed the intermediate state 278 * between the time when the negotiation is finished and the actual 279 * transfer begins. 280 */ 281 negotiated("Negotiated"), 282 283 /** 284 * The transfer is in progress. 285 * 286 * @see FileTransfer#getProgress() 287 */ 288 in_progress("In Progress"), 289 290 /** 291 * The transfer has completed successfully. 292 */ 293 complete("Complete"), 294 295 /** 296 * The file transfer was cancelled. 297 */ 298 cancelled("Cancelled"); 299 300 private final String status; 301 302 Status(String status) { 303 this.status = status; 304 } 305 306 @Override 307 public String toString() { 308 return status; 309 } 310 } 311 312 /** 313 * Return the length of bytes written out to the stream. 314 * @return the amount in bytes written out. 315 */ 316 public long getAmountWritten() { 317 return amountWritten; 318 } 319 320 @SuppressWarnings("JavaLangClash") 321 public enum Error { 322 /** 323 * The peer did not find any of the provided stream mechanisms 324 * acceptable. 325 */ 326 not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."), 327 328 /** 329 * The provided file to transfer does not exist or could not be read. 330 */ 331 bad_file("The provided file to transfer does not exist or could not be read."), 332 333 /** 334 * The remote user did not respond or the connection timed out. 335 */ 336 no_response("The remote user did not respond or the connection timed out."), 337 338 /** 339 * An error occurred over the socket connected to send the file. 340 */ 341 connection("An error occurred over the socket connected to send the file."), 342 343 /** 344 * An error occurred while sending or receiving the file. 345 */ 346 stream("An error occurred while sending or receiving the file."); 347 348 private final String msg; 349 350 Error(String msg) { 351 this.msg = msg; 352 } 353 354 /** 355 * Returns a String representation of this error. 356 * 357 * @return Returns a String representation of this error. 358 */ 359 public String getMessage() { 360 return msg; 361 } 362 363 @Override 364 public String toString() { 365 return msg; 366 } 367 } 368 369}