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