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 // the connection was likely terminated abruptly if these are not equal 218 if (!getStatus().equals(Status.cancelled) && getError() == Error.none 219 && amountWritten != fileSize) { 220 setStatus(Status.error); 221 this.error = Error.connection; 222 } 223 } 224 225 /** 226 * A class to represent the current status of the file transfer. 227 * 228 * @author Alexander Wenckus 229 * 230 */ 231 public enum Status { 232 233 /** 234 * An error occurred during the transfer. 235 * 236 * @see FileTransfer#getError() 237 */ 238 error("Error"), 239 240 /** 241 * The initial status of the file transfer. 242 */ 243 initial("Initial"), 244 245 /** 246 * The file transfer is being negotiated with the peer. The party 247 * Receiving the file has the option to accept or refuse a file transfer 248 * request. If they accept, then the process of stream negotiation will 249 * begin. If they refuse the file will not be transferred. 250 * 251 * @see #negotiating_stream 252 */ 253 negotiating_transfer("Negotiating Transfer"), 254 255 /** 256 * The peer has refused the file transfer request halting the file 257 * transfer negotiation process. 258 */ 259 refused("Refused"), 260 261 /** 262 * The stream to transfer the file is being negotiated over the chosen 263 * stream type. After the stream negotiating process is complete the 264 * status becomes negotiated. 265 * 266 * @see #negotiated 267 */ 268 negotiating_stream("Negotiating Stream"), 269 270 /** 271 * After the stream negotiation has completed the intermediate state 272 * between the time when the negotiation is finished and the actual 273 * transfer begins. 274 */ 275 negotiated("Negotiated"), 276 277 /** 278 * The transfer is in progress. 279 * 280 * @see FileTransfer#getProgress() 281 */ 282 in_progress("In Progress"), 283 284 /** 285 * The transfer has completed successfully. 286 */ 287 complete("Complete"), 288 289 /** 290 * The file transfer was cancelled. 291 */ 292 cancelled("Cancelled"); 293 294 private final String status; 295 296 Status(String status) { 297 this.status = status; 298 } 299 300 @Override 301 public String toString() { 302 return status; 303 } 304 } 305 306 /** 307 * Return the length of bytes written out to the stream. 308 * @return the amount in bytes written out. 309 */ 310 public long getAmountWritten() { 311 return amountWritten; 312 } 313 314 @SuppressWarnings("JavaLangClash") 315 public enum Error { 316 /** 317 * No error. 318 */ 319 none("No error"), 320 321 /** 322 * The peer did not find any of the provided stream mechanisms 323 * acceptable. 324 */ 325 not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."), 326 327 /** 328 * The provided file to transfer does not exist or could not be read. 329 */ 330 bad_file("The provided file to transfer does not exist or could not be read."), 331 332 /** 333 * The remote user did not respond or the connection timed out. 334 */ 335 no_response("The remote user did not respond or the connection timed out."), 336 337 /** 338 * An error occurred over the socket connected to send the file. 339 */ 340 connection("An error occurred over the socket connected to send the file."), 341 342 /** 343 * An error occurred while sending or receiving the file. 344 */ 345 stream("An error occurred while sending or receiving the file."); 346 347 private final String msg; 348 349 Error(String msg) { 350 this.msg = msg; 351 } 352 353 /** 354 * Returns a String representation of this error. 355 * 356 * @return Returns a String representation of this error. 357 */ 358 public String getMessage() { 359 return msg; 360 } 361 362 @Override 363 public String toString() { 364 return msg; 365 } 366 } 367 368}