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
023/**
024 * Contains the generic file information and progress related to a particular
025 * file transfer.
026 *
027 * @author Alexander Wenckus
028 *
029 */
030public abstract class FileTransfer {
031
032        private String fileName;
033
034        private String filePath;
035
036        private long fileSize;
037
038        private String peer;
039
040        private Status status = Status.initial;
041
042    private final Object statusMonitor = new Object();
043
044        protected FileTransferNegotiator negotiator;
045
046        protected String streamID;
047
048        protected long amountWritten = -1;
049
050        private Error error;
051
052        private Exception exception;
053
054    /**
055     * Buffer size between input and output
056     */
057    private static final int BUFFER_SIZE = 8192;
058
059    protected FileTransfer(String peer, String streamID,
060                        FileTransferNegotiator negotiator) {
061                this.peer = peer;
062                this.streamID = streamID;
063                this.negotiator = negotiator;
064        }
065
066        protected void setFileInfo(String fileName, long fileSize) {
067                this.fileName = fileName;
068                this.fileSize = fileSize;
069        }
070
071        protected void setFileInfo(String path, String fileName, long fileSize) {
072                this.filePath = path;
073                this.fileName = fileName;
074                this.fileSize = fileSize;
075        }
076
077        /**
078         * Returns the size of the file being transfered.
079         *
080         * @return Returns the size of the file being transfered.
081         */
082        public long getFileSize() {
083                return fileSize;
084        }
085
086        /**
087         * Returns the name of the file being transfered.
088         *
089         * @return Returns the name of the file being transfered.
090         */
091        public String getFileName() {
092                return fileName;
093        }
094
095        /**
096         * Returns the local path of the file.
097         *
098         * @return Returns the local path of the file.
099         */
100        public String getFilePath() {
101                return filePath;
102        }
103
104        /**
105         * Returns the JID of the peer for this file transfer.
106         *
107         * @return Returns the JID of the peer for this file transfer.
108         */
109        public String getPeer() {
110                return peer;
111        }
112
113        /**
114         * Returns the progress of the file transfer as a number between 0 and 1.
115         *
116         * @return Returns the progress of the file transfer as a number between 0
117         *         and 1.
118         */
119        public double getProgress() {
120        if (amountWritten <= 0 || fileSize <= 0) {
121            return 0;
122        }
123        return (double) amountWritten / (double) fileSize;
124        }
125
126        /**
127         * Returns true if the transfer has been cancelled, if it has stopped because
128         * of a an error, or the transfer completed successfully.
129         *
130         * @return Returns true if the transfer has been cancelled, if it has stopped
131         *         because of a an error, or the transfer completed successfully.
132         */
133        public boolean isDone() {
134                return status == Status.cancelled || status == Status.error
135                                || status == Status.complete || status == Status.refused;
136        }
137
138        /**
139         * Returns the current status of the file transfer.
140         *
141         * @return Returns the current status of the file transfer.
142         */
143        public Status getStatus() {
144                return status;
145        }
146
147        protected void setError(Error type) {
148                this.error = type;
149        }
150
151        /**
152         * When {@link #getStatus()} returns that there was an {@link Status#error}
153         * during the transfer, the type of error can be retrieved through this
154         * method.
155         *
156         * @return Returns the type of error that occurred if one has occurred.
157         */
158        public Error getError() {
159                return error;
160        }
161
162        /**
163         * If an exception occurs asynchronously it will be stored for later
164         * retrieval. If there is an error there maybe an exception set.
165         *
166         * @return The exception that occurred or null if there was no exception.
167         * @see #getError()
168         */
169        public Exception getException() {
170                return exception;
171        }
172
173    public String getStreamID() {
174        return streamID;
175    }
176
177        /**
178         * Cancels the file transfer.
179         */
180        public abstract void cancel();
181
182        protected void setException(Exception exception) {
183                this.exception = exception;
184        }
185
186        protected void setStatus(Status status) {
187        synchronized (statusMonitor) {
188                    this.status = status;
189            }
190    }
191
192    protected boolean updateStatus(Status oldStatus, Status newStatus) {
193        synchronized (statusMonitor) {
194            if (oldStatus != status) {
195                return false;
196            }
197            status = newStatus;
198            return true;
199        }
200    }
201
202    protected void writeToStream(final InputStream in, final OutputStream out)
203                    throws IOException
204    {
205                final byte[] b = new byte[BUFFER_SIZE];
206                int count = 0;
207                amountWritten = 0;
208
209        while ((count = in.read(b)) > 0 && !getStatus().equals(Status.cancelled)) {
210            out.write(b, 0, count);
211            amountWritten += count;
212        }
213
214                // the connection was likely terminated abruptly if these are not equal
215                if (!getStatus().equals(Status.cancelled) && getError() == Error.none
216                                && amountWritten != fileSize) {
217            setStatus(Status.error);
218                        this.error = Error.connection;
219                }
220        }
221
222        /**
223         * A class to represent the current status of the file transfer.
224         *
225         * @author Alexander Wenckus
226         *
227         */
228        public enum Status {
229
230                /**
231                 * An error occurred during the transfer.
232                 *
233                 * @see FileTransfer#getError()
234                 */
235                error("Error"),
236
237                /**
238         * The initial status of the file transfer.
239         */
240        initial("Initial"),
241
242        /**
243                 * The file transfer is being negotiated with the peer. The party
244                 * Receiving the file has the option to accept or refuse a file transfer
245                 * request. If they accept, then the process of stream negotiation will
246                 * begin. If they refuse the file will not be transfered.
247                 *
248                 * @see #negotiating_stream
249                 */
250                negotiating_transfer("Negotiating Transfer"),
251
252                /**
253                 * The peer has refused the file transfer request halting the file
254                 * transfer negotiation process.
255                 */
256                refused("Refused"),
257
258                /**
259                 * The stream to transfer the file is being negotiated over the chosen
260                 * stream type. After the stream negotiating process is complete the
261                 * status becomes negotiated.
262                 *
263                 * @see #negotiated
264                 */
265                negotiating_stream("Negotiating Stream"),
266
267                /**
268                 * After the stream negotiation has completed the intermediate state
269                 * between the time when the negotiation is finished and the actual
270                 * transfer begins.
271                 */
272                negotiated("Negotiated"),
273
274                /**
275                 * The transfer is in progress.
276                 *
277                 * @see FileTransfer#getProgress()
278                 */
279                in_progress("In Progress"),
280
281                /**
282                 * The transfer has completed successfully.
283                 */
284                complete("Complete"),
285
286                /**
287                 * The file transfer was cancelled
288                 */
289                cancelled("Cancelled");
290
291        private String status;
292
293        private Status(String status) {
294            this.status = status;
295        }
296
297        public String toString() {
298            return status;
299        }
300    }
301
302    /**
303     * Return the length of bytes written out to the stream.
304     * @return the amount in bytes written out.
305     */
306    public long getAmountWritten(){
307        return amountWritten;
308    }
309
310    public enum Error {
311                /**
312                 * No error
313                 */
314                none("No error"),
315
316                /**
317                 * The peer did not find any of the provided stream mechanisms
318                 * acceptable.
319                 */
320                not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."),
321
322                /**
323                 * The provided file to transfer does not exist or could not be read.
324                 */
325                bad_file("The provided file to transfer does not exist or could not be read."),
326
327                /**
328                 * The remote user did not respond or the connection timed out.
329                 */
330                no_response("The remote user did not respond or the connection timed out."),
331
332                /**
333                 * An error occurred over the socket connected to send the file.
334                 */
335                connection("An error occured over the socket connected to send the file."),
336
337                /**
338                 * An error occurred while sending or receiving the file
339                 */
340                stream("An error occured while sending or recieving the file.");
341
342                private final String msg;
343
344                private Error(String msg) {
345                        this.msg = msg;
346                }
347
348                /**
349                 * Returns a String representation of this error.
350                 *
351                 * @return Returns a String representation of this error.
352                 */
353                public String getMessage() {
354                        return msg;
355                }
356
357                public String toString() {
358                        return msg;
359                }
360        }
361
362}