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}