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        // When the amount of data written does not equal the expected amount, and
218        // the transfer was not explicitly cancelled, register an error (unless another
219        // error has already been logged).
220        if (!getStatus().equals(Status.cancelled) && getError() == null
221                && amountWritten != fileSize) {
222            setStatus(Status.error);
223            this.error = Error.connection;
224        }
225    }
226
227    /**
228     * A class to represent the current status of the file transfer.
229     *
230     * @author Alexander Wenckus
231     *
232     */
233    public enum Status {
234
235        /**
236         * An error occurred during the transfer.
237         *
238         * @see FileTransfer#getError()
239         */
240        error("Error"),
241
242        /**
243         * The initial status of the file transfer.
244         */
245        initial("Initial"),
246
247        /**
248         * The file transfer is being negotiated with the peer. The party
249         * Receiving the file has the option to accept or refuse a file transfer
250         * request. If they accept, then the process of stream negotiation will
251         * begin. If they refuse the file will not be transferred.
252         *
253         * @see #negotiating_stream
254         */
255        negotiating_transfer("Negotiating Transfer"),
256
257        /**
258         * The peer has refused the file transfer request halting the file
259         * transfer negotiation process.
260         */
261        refused("Refused"),
262
263        /**
264         * The stream to transfer the file is being negotiated over the chosen
265         * stream type. After the stream negotiating process is complete the
266         * status becomes negotiated.
267         *
268         * @see #negotiated
269         */
270        negotiating_stream("Negotiating Stream"),
271
272        /**
273         * After the stream negotiation has completed the intermediate state
274         * between the time when the negotiation is finished and the actual
275         * transfer begins.
276         */
277        negotiated("Negotiated"),
278
279        /**
280         * The transfer is in progress.
281         *
282         * @see FileTransfer#getProgress()
283         */
284        in_progress("In Progress"),
285
286        /**
287         * The transfer has completed successfully.
288         */
289        complete("Complete"),
290
291        /**
292         * The file transfer was cancelled.
293         */
294        cancelled("Cancelled");
295
296        private final String status;
297
298        Status(String status) {
299            this.status = status;
300        }
301
302        @Override
303        public String toString() {
304            return status;
305        }
306    }
307
308    /**
309     * Return the length of bytes written out to the stream.
310     * @return the amount in bytes written out.
311     */
312    public long getAmountWritten() {
313        return amountWritten;
314    }
315
316    @SuppressWarnings("JavaLangClash")
317    public enum Error {
318        /**
319         * The peer did not find any of the provided stream mechanisms
320         * acceptable.
321         */
322        not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."),
323
324        /**
325         * The provided file to transfer does not exist or could not be read.
326         */
327        bad_file("The provided file to transfer does not exist or could not be read."),
328
329        /**
330         * The remote user did not respond or the connection timed out.
331         */
332        no_response("The remote user did not respond or the connection timed out."),
333
334        /**
335         * An error occurred over the socket connected to send the file.
336         */
337        connection("An error occurred over the socket connected to send the file."),
338
339        /**
340         * An error occurred while sending or receiving the file.
341         */
342        stream("An error occurred while sending or receiving the file.");
343
344        private final String msg;
345
346        Error(String msg) {
347            this.msg = msg;
348        }
349
350        /**
351         * Returns a String representation of this error.
352         *
353         * @return Returns a String representation of this error.
354         */
355        public String getMessage() {
356            return msg;
357        }
358
359        @Override
360        public String toString() {
361            return msg;
362        }
363    }
364
365}