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 } 187 188 protected void setStatus(Status status) { 189 synchronized (statusMonitor) { 190 // CHECKSTYLE:OFF 191 this.status = status; 192 } 193 // CHECKSTYLE:ON 194 } 195 196 protected boolean updateStatus(Status oldStatus, Status newStatus) { 197 synchronized (statusMonitor) { 198 if (oldStatus != status) { 199 return false; 200 } 201 status = newStatus; 202 return true; 203 } 204 } 205 206 protected void writeToStream(final InputStream in, final OutputStream out) 207 throws IOException { 208 final byte[] b = new byte[BUFFER_SIZE]; 209 int count = 0; 210 amountWritten = 0; 211 212 while ((count = in.read(b)) > 0 && !getStatus().equals(Status.cancelled)) { 213 out.write(b, 0, count); 214 amountWritten += count; 215 } 216 217 // When the amount of data written does not equal the expected amount, and 218 // the transfer was not explicitly cancelled, register an error (unless another 219 // error has already been logged). 220 if (!getStatus().equals(Status.cancelled) && getError() == null 221 && amountWritten != fileSize) { 222 setStatus(Status.error); 223 this.error = Error.connection; 224 } 225 } 226 227 /** 228 * A class to represent the current status of the file transfer. 229 * 230 * @author Alexander Wenckus 231 * 232 */ 233 public enum Status { 234 235 /** 236 * An error occurred during the transfer. 237 * 238 * @see FileTransfer#getError() 239 */ 240 error("Error"), 241 242 /** 243 * The initial status of the file transfer. 244 */ 245 initial("Initial"), 246 247 /** 248 * The file transfer is being negotiated with the peer. The party 249 * Receiving the file has the option to accept or refuse a file transfer 250 * request. If they accept, then the process of stream negotiation will 251 * begin. If they refuse the file will not be transferred. 252 * 253 * @see #negotiating_stream 254 */ 255 negotiating_transfer("Negotiating Transfer"), 256 257 /** 258 * The peer has refused the file transfer request halting the file 259 * transfer negotiation process. 260 */ 261 refused("Refused"), 262 263 /** 264 * The stream to transfer the file is being negotiated over the chosen 265 * stream type. After the stream negotiating process is complete the 266 * status becomes negotiated. 267 * 268 * @see #negotiated 269 */ 270 negotiating_stream("Negotiating Stream"), 271 272 /** 273 * After the stream negotiation has completed the intermediate state 274 * between the time when the negotiation is finished and the actual 275 * transfer begins. 276 */ 277 negotiated("Negotiated"), 278 279 /** 280 * The transfer is in progress. 281 * 282 * @see FileTransfer#getProgress() 283 */ 284 in_progress("In Progress"), 285 286 /** 287 * The transfer has completed successfully. 288 */ 289 complete("Complete"), 290 291 /** 292 * The file transfer was cancelled. 293 */ 294 cancelled("Cancelled"); 295 296 private final String status; 297 298 Status(String status) { 299 this.status = status; 300 } 301 302 @Override 303 public String toString() { 304 return status; 305 } 306 } 307 308 /** 309 * Return the length of bytes written out to the stream. 310 * @return the amount in bytes written out. 311 */ 312 public long getAmountWritten() { 313 return amountWritten; 314 } 315 316 @SuppressWarnings("JavaLangClash") 317 public enum Error { 318 /** 319 * The peer did not find any of the provided stream mechanisms 320 * acceptable. 321 */ 322 not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."), 323 324 /** 325 * The provided file to transfer does not exist or could not be read. 326 */ 327 bad_file("The provided file to transfer does not exist or could not be read."), 328 329 /** 330 * The remote user did not respond or the connection timed out. 331 */ 332 no_response("The remote user did not respond or the connection timed out."), 333 334 /** 335 * An error occurred over the socket connected to send the file. 336 */ 337 connection("An error occurred over the socket connected to send the file."), 338 339 /** 340 * An error occurred while sending or receiving the file. 341 */ 342 stream("An error occurred while sending or receiving the file."); 343 344 private final String msg; 345 346 Error(String msg) { 347 this.msg = msg; 348 } 349 350 /** 351 * Returns a String representation of this error. 352 * 353 * @return Returns a String representation of this error. 354 */ 355 public String getMessage() { 356 return msg; 357 } 358 359 @Override 360 public String toString() { 361 return msg; 362 } 363 } 364 365}