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}