Shuyi Chen | d7955ce | 2013-05-22 14:51:55 -0700 | [diff] [blame] | 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 | }
|